-
Notifications
You must be signed in to change notification settings - Fork 2
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
gytisgreitai
committed
Apr 30, 2019
0 parents
commit 3c0a86e
Showing
13 changed files
with
908 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,4 @@ | ||
__pycache__ | ||
.vscode | ||
openhab/heatpump-pow.items | ||
openhab/heatpump-pow.rules |
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,6 @@ | ||
deploy: | ||
scp *.py 192.168.3.4:/opt/zehnder-can-mqtt | ||
ohitems: | ||
cd openhab && python3 items.py > comofair.items | ||
|
||
.PHONY: deploy ohitems |
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,150 @@ | ||
import serial | ||
from time import sleep | ||
import logging | ||
import sys | ||
|
||
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) | ||
logger = logging.getLogger('comfoair-can') | ||
|
||
class CN1FAddr: | ||
def __init__(self, SrcAddr, DstAddr, Address, MultiMsg, A8000, A10000, SeqNr): | ||
self.SrcAddr = SrcAddr | ||
self.DstAddr = DstAddr | ||
self.Address = Address | ||
self.MultiMsg = MultiMsg | ||
self.A8000 = A8000 | ||
self.A10000 = A10000 | ||
self.SeqNr = SeqNr | ||
|
||
def id(self): | ||
addr = 0x0 | ||
addr |= self.SrcAddr << 0 | ||
addr |= self.DstAddr << 6 | ||
|
||
addr |= self.Address <<12 | ||
addr |= self.MultiMsg<<14 | ||
addr |= self.A8000 <<15 | ||
addr |= self.A10000 <<16 | ||
addr |= self.SeqNr <<17 | ||
addr |= 0x1F <<24 | ||
|
||
return addr | ||
def hex(self): | ||
return hex(self.id())[2:] | ||
|
||
def bytes(self): | ||
return bytes.fromhex(self.hex()) | ||
|
||
class CANInterface: | ||
|
||
START_BYTE_1 = 0x55 | ||
START_BYTE_2 = 0XAA | ||
|
||
COMFOAIR_ADDRESS = 1 | ||
|
||
send_sequence_nr = 0 | ||
|
||
def __init__(self, device, baudrate): | ||
self.device = device | ||
self.baudrate = baudrate | ||
|
||
def open(self): | ||
self.sp = serial.Serial(self.device, self.baudrate) | ||
self._send_magic_init_packet() | ||
|
||
def read(self, callback): | ||
frame = bytearray() | ||
while True: | ||
if self.sp.in_waiting != 0: | ||
new_byte = self._get_single_byte() | ||
if new_byte == self.START_BYTE_1: | ||
next_byte = self._get_single_byte() | ||
if next_byte == self.START_BYTE_2: | ||
id = frame[1:5] | ||
data =frame[5:] | ||
id_hex = str() + format(int.from_bytes(id, byteorder='little'), '#10X') | ||
pdid = (int(id_hex, 16)>>14)&0x7ff | ||
frame = bytearray() | ||
callback(pdid, data) | ||
else: | ||
frame.append(new_byte) | ||
frame.append(next_byte) | ||
else: | ||
frame.append(new_byte) | ||
else: | ||
sleep(1) | ||
|
||
def send(self, data): | ||
num_bytes = len(data) | ||
can_id = CN1FAddr(0x11, self.COMFOAIR_ADDRESS, 1, num_bytes>8, 0, 1, self.send_sequence_nr) | ||
self.send_sequence_nr = (self.send_sequence_nr + 1)&0x3 | ||
if len(data) > 8: | ||
datagrams = int(len(data)/7) | ||
if len(data) == datagrams*7: | ||
datagrams -= 1 | ||
for n in range(datagrams): | ||
self._write_to_can(can_id.bytes(), [n]+data[n*7:n*7+7]) | ||
n+=1 | ||
restlen = len(data)-n*7 | ||
self._write_to_can(can_id.bytes(), [n|0x80]+data[n*7:n*7+restlen]) | ||
else: | ||
self._write_to_can(can_id.bytes(), data) | ||
|
||
#----- private | ||
def _get_single_byte(self): | ||
return int.from_bytes(self.sp.read(size=1), byteorder='little') | ||
|
||
|
||
def _write_to_can(self, id, data): | ||
num_bytes=len(data) | ||
send_buf = bytearray([0xAA,0xE0|num_bytes,]) | ||
for byte in reversed(id): | ||
send_buf.append(byte) | ||
for byte in data: | ||
send_buf.append(byte) | ||
send_buf.append(0x55) | ||
self.sp.write(send_buf) | ||
|
||
|
||
def _send_magic_init_packet(self): | ||
send_buf = bytearray() | ||
send_buf.append(0xAA) | ||
send_buf.append(0x55) | ||
#Pack mystery byte | ||
send_buf.append(0x12) | ||
#Pack byte indicating CAN bus speed | ||
send_buf.append(0x09) | ||
#Pack frame type byte | ||
#use extended | ||
send_buf.append(0x02) | ||
#Filter not supported | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
#Mask not supported | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
#Hardcode mode to Normal? Set to 0x01 to get loopback mode | ||
send_buf.append(0x00) | ||
#Send magic byte (may have to be 0x01?) | ||
send_buf.append(0x01) | ||
#Send more magic bytes | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
send_buf.append(0x00) | ||
|
||
#Calculate checksum | ||
checksum = 0 | ||
for idx in range(0,18): | ||
checksum += int(send_buf[idx]) | ||
checksum = checksum % 255 | ||
|
||
send_buf.append(checksum) | ||
self.sp.write(send_buf) | ||
logger.info('init config done') | ||
|
||
|
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,52 @@ | ||
import paho.mqtt.client as mqtt | ||
import logging | ||
import sys | ||
|
||
from USBCAN import CANInterface | ||
import mapping | ||
from time import sleep | ||
|
||
def on_mqtt_connect(client, userdata, flags, rc): | ||
logger.info('ubscribing to comfoair/action') | ||
client.subscribe('comfoair/action') | ||
|
||
def on_mqtt_message(client, userdata, msg): | ||
action =str(msg.payload.decode('utf-8')) | ||
logger.info('got mqtt message %s %s', msg.topic, action) | ||
if action in mapping.commands: | ||
command = mapping.commands[action] | ||
logger.info('action ok, executing: %s', action) | ||
try: | ||
for i in range(3): | ||
can.send(command) | ||
sleep(2) | ||
except Exception as e: | ||
logger.error('failed in send %s', e) | ||
else: | ||
logger.error('action not found %s', action) | ||
|
||
def process_can_message(pdid, data): | ||
if pdid in mapping.data: | ||
pdid_config = mapping.data[pdid] | ||
value = pdid_config.get('transformation')(data) | ||
if pdid_config.get('ok'): | ||
name = pdid_config.get('name') | ||
mqtt_client.publish('comfoair/status/' + name, value) | ||
else: | ||
logger.info('not ok, not pushing %s %s', pdid, value) | ||
elif pdid != 0: | ||
logger.info('pid not found %s %s', pdid, data) | ||
|
||
logger = logging.getLogger('comfoair-main') | ||
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) | ||
|
||
logger.info('starting up') | ||
mqtt_client = mqtt.Client() | ||
mqtt_client.connect('192.168.3.4', 1883, 60) | ||
mqtt_client.on_connect = on_mqtt_connect | ||
mqtt_client.on_message = on_mqtt_message | ||
mqtt_client.loop_start() | ||
|
||
can = CANInterface('/dev/ttyUSB0', 2000000) | ||
can.open() | ||
can.read(process_can_message) |
Oops, something went wrong.