Add berry TWAI example

This commit is contained in:
Theo Arends 2025-01-05 15:17:43 +01:00
parent 0d0b9de474
commit 2263305bee
1 changed files with 192 additions and 0 deletions

View File

@ -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)