Skip to content

Commit

Permalink
initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
gytisgreitai committed Apr 30, 2019
0 parents commit 3c0a86e
Show file tree
Hide file tree
Showing 13 changed files with 908 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__
.vscode
openhab/heatpump-pow.items
openhab/heatpump-pow.rules
6 changes: 6 additions & 0 deletions Makefile
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
150 changes: 150 additions & 0 deletions USBCAN.py
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')


52 changes: 52 additions & 0 deletions main.py
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)
Loading

0 comments on commit 3c0a86e

Please sign in to comment.