-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
192 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
# | ||
# class `twai` | ||
# | ||
# When loaded by `preinit.be` it wil: | ||
# - configure TWAI/CAN driver speed and mode when enabled by GPIOs `TWAI Tx` and `TWAI Rx` | ||
# - decode known Remeha Calenta Ace boiler codes | ||
# - send every second changed values to Domoticz Idx | ||
# - provide JSON message at teleperiod time | ||
# - update GUI | ||
# | ||
|
||
class twai_cls | ||
var active, first_teleperiod, pressure_next # (bool, bool, bool) | ||
var ch_active, dz_ch_active # (bool, bool) | ||
var dhw_active, dz_dhw_active # (bool, bool) | ||
var pump_active, dz_pump_active # (bool, bool) | ||
var twai_speed, twai_mode # (int, int) | ||
var am012_status, dz_am012_status # (int, int) | ||
var am014_substatus, dz_am014_substatus # (int, int) | ||
var am024_power # (int) | ||
var pressure, dz_pressure # (float, float) | ||
var flow_temp, dz_flow_temp # (float, float) | ||
var setpoint_temp, dz_setpoint_temp # (float, float) | ||
|
||
def init() | ||
self.twai_speed = 7 # 0 = 25K, 1 = 50K, 2 = 100K, 3 = 125K, 4 = 250K, 5 = 500K, 6 = 800K, 7 = 1Mbits | ||
self.twai_mode = 2 # 0 = TWAI_MODE_NORMAL, 1 = TWAI_MODE_NO_ACK, 2 = TWAI_MODE_LISTEN_ONLY | ||
self.active = 0 | ||
self.first_teleperiod = 0 | ||
self.am012_status = 0 | ||
self.dz_am012_status = 0 | ||
self.am014_substatus = 0 | ||
self.dz_am014_substatus = 0 | ||
self.am024_power = 0 | ||
self.pressure_next = 0 | ||
self.pressure = 0 | ||
self.dz_pressure = 0 | ||
self.setpoint_temp = 0 | ||
self.dz_setpoint_temp = 0 | ||
self.flow_temp = 0 | ||
self.dz_flow_temp = 0 | ||
self.ch_active = 0 | ||
self.dz_ch_active = 0 | ||
self.dhw_active = 0 | ||
self.dz_dhw_active = 0 | ||
self.pump_active = 0 | ||
self.dz_pump_active = 0 | ||
end | ||
|
||
#---------------------------------------------------------------------------------------------- | ||
Allow TWAI driver configuration on restart (if this file is installed by preinit.be) | ||
----------------------------------------------------------------------------------------------# | ||
def config(bus) | ||
# if bus != 1 return nil end # Exit if not my bus | ||
return self.twai_mode << 3 | self.twai_speed # Initial configure TWAI driver | ||
end | ||
|
||
#---------------------------------------------------------------------------------------------- | ||
Decodes Remeha Calenta Ace CAN-bus messages | ||
----------------------------------------------------------------------------------------------# | ||
def decode(param, ident, data1, data2) | ||
var bus = param & 0xF # Bus number (1..3) | ||
# if bus != 1 return nil end # Exit if not my bus | ||
var len = param >> 4 & 0xF # Number of data bytes (0..8) | ||
var extended = ident >> 31 & 0x1 # Extended identifier flag (0..1) | ||
if extended == 1 return nil end # Remeha uses 11-bit Standard Frame Format | ||
var id = ident & 0x1fffffff | ||
if id == 0x076 # Incremental counter from 0 to 255 | ||
# 13:27:59.485 TWA: Bus1 param 00000011, ident 00000076, data1 00000029, data2 00000000 | ||
# tasmota.log(f"RMH: 0x{id:03x} Count {data1}", 3) | ||
# elif id == 0x080 # Heartbeat every second | ||
# 13:28:00.115 TWA: Bus1 param 00000001, ident 00000080, data1 00000000, data2 00000000 | ||
elif id == 0x100 # Date and Time | ||
# 13:28:05.977 TWA: Bus1 param 00000061, ident 00000100, data1 02E3D288, data2 00003A82 | ||
var epoch = 441763200 + (data2 * 24 * 60 * 60) + (data1 / 1000) | ||
# tasmota.log(f"RMH: 0x{id:03x} Time {tasmota.time_str(epoch)}", 3) | ||
elif id == 0x1C1 # Many different data1/2 | ||
if data1 & 0x00ffffff == 0x503f41 # Next time it's pressure | ||
# 13:28:01.981 TWA: Bus1 param 00000081, ident 000001C1, data1 00503F41, data2 00000028 | ||
self.pressure_next = 1 | ||
elif self.pressure_next == 1 | ||
# 13:28:01.983 TWA: Bus1 param 00000081, ident 000001C1, data1 21012300, data2 16EE1909 | ||
self.pressure = (data2 & 0xff00)/2560.0 # This must be pressure | ||
self.pressure_next = 0 | ||
end | ||
elif id == 0x382 | ||
# 13:28:00.140 TWA: Bus1 param 00000081, ident 00000382, data1 0716BC64, data2 00FFFFFF | ||
self.am024_power = data1 & 0xff # Relative power | ||
self.setpoint_temp = (data1 & 0xffff00)/25600.0 # Setpoint | ||
# tasmota.log(f"RMH: 0x{id:03x} Busy {self.am024_power}%, Setpoint {self.setpoint_temp}", 3) | ||
elif id == 0x282 | ||
# 13:28:00.141 TWA: Bus1 param 00000051, ident 00000282, data1 1B16EE01, data2 00000000 | ||
self.flow_temp = (data1 & 0xffff00)/25600.0 | ||
# tasmota.log(f"RMH: 0x{id:03x} DHW temp {self.flow_temp}", 3) | ||
elif id == 0x481 # Status information | ||
# 13:28:00.561 TWA: Bus1 param 00000071, ident 00000481, data1 FFFF1E03, data2 002101FF | ||
self.am012_status = data1 & 0xff | ||
self.am014_substatus = (data1 >> 8) &0xff | ||
self.dhw_active = (data2 >> 20) &1 | ||
self.ch_active = (data2 >> 21) &1 | ||
self.pump_active = (data2 >> 16) &1 | ||
# elif id == 0x7E5 # Heartbeat every 2 seconds | ||
# 13:27:57.515 TWA: Bus1 param 00000081, ident 000007E5, data1 00000B51, data2 00008000 | ||
else | ||
return | ||
end | ||
self.active = 1 # At least one valid decode | ||
end | ||
|
||
#---------------------------------------------------------------------------------------------- | ||
Send changed values to Domoticz every second | ||
As many datagrams can occur sending at teleperiod time takes too long | ||
Also only send if changed to reduce TWAI wait time | ||
----------------------------------------------------------------------------------------------# | ||
def every_second() | ||
if !self.first_teleperiod return nil end # Exit if there is no MQTT connection | ||
|
||
if self.dz_pressure != self.pressure # Pressure | ||
self.dzsend(1, 523, self.pressure) | ||
end | ||
self.dz_pressure = self.pressure | ||
|
||
if self.dz_setpoint_temp != self.setpoint_temp # Setpoint temp | ||
self.dzsend(1, 525, self.setpoint_temp) | ||
end | ||
self.dz_setpoint_temp = self.setpoint_temp | ||
|
||
if self.dz_flow_temp != self.flow_temp # Flow temp | ||
self.dzsend(1, 526, self.flow_temp) | ||
end | ||
self.dz_flow_temp = self.flow_temp | ||
|
||
if self.dz_am012_status != self.am012_status # Status | ||
self.dzsend(1, 536, self.am012_status) | ||
end | ||
self.dz_am012_status = self.am012_status | ||
|
||
if self.dz_am014_substatus != self.am014_substatus # Substatus | ||
self.dzsend(1, 537, self.am014_substatus) | ||
end | ||
self.dz_am014_substatus = self.am014_substatus | ||
|
||
if self.dz_dhw_active != self.dhw_active # Domestic Hot Water active | ||
self.dzsend(3, 552, self.dhw_active) | ||
end | ||
self.dz_dhw_active = self.dhw_active | ||
|
||
if self.dz_ch_active != self.ch_active # Central Heating active | ||
self.dzsend(3, 553, self.ch_active) | ||
end | ||
self.dz_ch_active = self.ch_active | ||
end | ||
|
||
def dzsend(option, domoticz_idx, value) | ||
tasmota.cmd('_DzSend' .. option .. ' ' .. domoticz_idx .. ',' .. value) | ||
# tasmota.cmd('Fake' .. option .. ' ' .. domoticz_idx .. ',' .. value) | ||
end | ||
|
||
#---------------------------------------------------------------------------------------------- | ||
Add sensor value to teleperiod | ||
----------------------------------------------------------------------------------------------# | ||
def json_append() | ||
if !self.active return nil end # Exit if never decoded something | ||
import string | ||
var msg = string.format(",\"Calenta\":{\"AM012\":%i,\"AM014\":%i,\"Pressure\":%.1f,\"Setpoint\":%.1f,\"Flow\":%.1f}", | ||
self.am012_status, self.am014_substatus, self.pressure, self.setpoint_temp, self.flow_temp) | ||
tasmota.response_append(msg) | ||
self.first_teleperiod = 1 | ||
end | ||
|
||
#---------------------------------------------------------------------------------------------- | ||
Display sensor value in the web UI | ||
----------------------------------------------------------------------------------------------# | ||
def web_sensor() | ||
if !self.active return nil end # Exit if never decoded something | ||
import string | ||
var msg = string.format("{s}CH / DHW Active{m}%i / %i{e}".. | ||
"{s}AM012 / AM014 State{m}%i / %i{e}".. | ||
"{s}AM024 Relative Power{m}%i %%{e}".. | ||
"{s}Pressure{m}%.1f{e}".. | ||
"{s}Setpoint Temperature{m}%.1f{e}".. | ||
"{s}Flow Temperature{m}%.1f{e}", | ||
self.ch_active, self.dhw_active, | ||
self.am012_status, self.am014_substatus, | ||
self.am024_power, self.pressure, self.setpoint_temp, self.flow_temp) | ||
tasmota.web_send_decimal(msg) | ||
end | ||
end | ||
|
||
twai = twai_cls() | ||
tasmota.add_driver(twai) |