summaryrefslogtreecommitdiff
path: root/ethcan.py
diff options
context:
space:
mode:
authorChristian Franke <nobody@nowhere.ws>2013-03-17 12:42:09 +0100
committerChristian Franke <nobody@nowhere.ws>2013-03-17 12:47:40 +0100
commit625fc3e4fd50451b950c3d1e2bf2356fb74dcefa (patch)
treed10c9a187b505b881c3bc29c18dabbcf6bf5e8f7 /ethcan.py
parent76c0532b5638eda76e71a748832290adb985f21a (diff)
Add the other scripts (funny code is funny)
Diffstat (limited to 'ethcan.py')
-rw-r--r--ethcan.py277
1 files changed, 277 insertions, 0 deletions
diff --git a/ethcan.py b/ethcan.py
new file mode 100644
index 0000000..2449933
--- /dev/null
+++ b/ethcan.py
@@ -0,0 +1,277 @@
+import socket, select
+from socket import AF_PACKET, SOCK_RAW, htons, ntohs
+from struct import pack, unpack
+from time import time, sleep, strftime
+import json
+import os, traceback
+import hub_ctrl, usb
+import contextlib
+
+def ts():
+ return strftime('%Y-%m-%d %H:%M:%S')
+
+class SubCANDevice(object):
+ def __init__(s, addr, name, dsize = 1):
+ s.addr = addr
+ s.name = name
+ s.dsize = dsize
+ s.lastval = None
+ s.lastupd = None
+ s.lastchg = None
+ s.actorval = None
+ s.actorupd = None
+ s.actorchg = None
+
+class SubCANBool(SubCANDevice):
+ def __init__(s, addr, name, falseval, trueval):
+ SubCANDevice.__init__(s, addr, name, 1)
+ s.vals = [falseval, trueval]
+ def __str__(s):
+ if s.lastval is None:
+ return 'None'
+ return s.vals[s.lastval & 1]
+ def dict(s):
+ rv = {}
+ if s.lastval is not None:
+ rv['raw'] = s.lastval
+ rv['value'] = bool(s.lastval & 1)
+ rv['text'] = s.vals[s.lastval & 1]
+ rv['ts'] = int(s.lastupd)
+ rv['tschg'] = int(s.lastchg)
+ return rv
+
+class SubCANDALI(SubCANDevice):
+ def __str__(s):
+ if s.lastval is None or s.actorval is None:
+ return ''
+ return 'set: %02x actual: %02x' % (s.actorval, s.lastval)
+ def dict(s):
+ rv = {}
+ if s.lastval is not None:
+ rv['actual'] = s.lastval
+ rv['actual_ts'] = int(s.lastupd)
+ rv['actual_tschg'] = int(s.lastchg)
+ if s.actorval is not None:
+ rv['set'] = s.actorval
+ rv['set_ts'] = int(s.actorupd)
+ rv['set_tschg'] = int(s.actorchg)
+ return rv
+
+devices = [
+ SubCANBool(0x100, 'door.right', 'open', 'closed'),
+ SubCANBool(0x101, 'door.left', 'open', 'closed'),
+ SubCANBool(0x102, 'door.light', 'triggered', 'normal'),
+ SubCANBool(0x103, 'door.lock', 'open', 'closed'),
+ SubCANDALI(0x441, 'dali.lounge_buehne'),
+ SubCANDALI(0x442, 'dali.lounge_buero'),
+ SubCANDALI(0x443, 'dali.lounge_bar'),
+ SubCANDALI(0x444, 'dali.lounge_durchreiche'),
+# SubCANDALI(0x47f, 'dali.lswitch'),
+]
+def find_dev(addr):
+ for dev in devices:
+ if addr in range(dev.addr, dev.addr + dev.dsize):
+ return dev
+ return None
+
+class SubCANFrame(object):
+ def __init__(s, frame):
+ s.frame = frame
+ s.dstaddr = frame.eid & 0xfff
+
+ def process(s):
+ data = s.frame.payload
+ addr = s.dstaddr
+ while len(data) > 0:
+ dev = find_dev(addr)
+ if dev is None:
+ addr += 1
+ data = data[1:]
+ else:
+ val = None
+ devdata = data[:dev.dsize]
+ if dev.dsize == 1:
+ (val, ) = unpack('>B', devdata)
+ elif dev.dsize == 2:
+ (val, ) = unpack('>H', devdata)
+
+ s.do_process(dev, devdata, val)
+ addr += dev.dsize
+ data = data[dev.dsize:]
+
+ @classmethod
+ def create(s, frame):
+ for fclass in s.frametypes:
+ if frame.sid == fclass.matchsid:
+ return fclass(frame)
+ return None
+
+class SensorFrame(SubCANFrame):
+ matchsid = 0xe60
+ def do_process(s, dev, data, val):
+ dev.lastupd = s.frame.ts
+ if val != dev.lastval:
+ dev.lastchg = s.frame.ts
+ dev.lastval = val
+
+class ActorFrame(SubCANFrame):
+ matchsid = 0xcc0
+ def do_process(s, dev, data, val):
+ dev.actorupd = s.frame.ts
+ if val != dev.actorval:
+ dev.actorchg = s.frame.ts
+ dev.actorval = val
+
+SubCANFrame.frametypes = [SensorFrame, ActorFrame]
+
+class MacAddr(object):
+ def __init__(s, mac):
+ s.mac = mac
+ def __str__(s):
+ return ':'.join(['%02x' % (ord(c),) for c in s.mac])
+
+class NotACANFrame(Exception):
+ pass
+class InvalidCANFrame(Exception):
+ pass
+
+from collections import namedtuple
+StatsTuple = namedtuple('StatsTuple', 'ethstat_tx_overrun, ethstat_tx_ok, ethstat_tx_error, ethstat_tx_fnord, ' +
+ 'ethstat_rx_overrun, ethstat_rx_ok, ethstat_rx_error, ethstat_lastrxerr, ' +
+ 'ethstat_again, ethstat_hasherr, ' +
+ 'mcp2515_errors, mcp2515_rx_ok, mcp2515_tx')
+
+class Frame(object):
+ def __init__(s, data):
+ (dst, src, proto, oui, subp, typ) = unpack('>6s6sH3sHB', data[:20])
+ (tsv, ) = unpack('<I', data[20:24])
+ s.data = data[24:]
+ s.dst = MacAddr(dst)
+ s.src = MacAddr(src)
+ s.proto = proto
+ s.ts = time()
+ s.tsr = tsv / 100.
+
+ if s.proto != 0x88b7 or oui != '\x00\x80\x41' or subp != 0xaaaa or len(data) < 5:
+ raise NotACANFrame('invalid protocol/OUI/subp or too short')
+ if typ == 0x01:
+ stats = []
+ data = data[24:]
+ while len(data) >= 4:
+ stats.append(unpack('<I', data[:4])[0])
+ data = data[4:]
+ spretty = StatsTuple._make(stats[:len(StatsTuple._fields)])
+ print '\033[33m%s stats: %s\033[m' % (ts(), repr(spretty))
+ raise NotACANFrame('stats frame')
+
+ if typ != 0x03:
+ raise NotACANFrame('invalid type %02x' % (typ))
+
+ (ts2, ) = unpack('<I', s.data[:4])
+ (addr, dlc) = unpack('>IB', s.data[4:9])
+ s.tsr2 = ts2 / 100.
+
+ s.dlc = dlc & 0x0f
+ s.is_eid = addr & 0x00080000
+ s.is_rtr = bool(dlc & 0x40 if s.is_eid else addr & 0x00100000)
+ s.addr = addr & 0xffe3ffff
+ s.sid = addr >> 20
+ s.eid = addr & 0x3ffff
+
+ if s.is_eid:
+ s.addrstr = '%03x-%05x' % (s.sid, s.eid)
+ else:
+ s.addrstr = '%03x-XXXXX' % (s.sid)
+
+ if len(data) < 9 + s.dlc:
+ raise InvalidCANFrame('truncated frame')
+ s.payload = s.data[9:9+s.dlc]
+
+ def __str__(s):
+ return '%s <- %s %8.2f d=%.2f %s %s: %s' % (
+ s.dst, s.src, s.tsr, s.tsr - s.tsr2,
+ s.addrstr, 'RTR:' if s.is_rtr else 'norm',
+ ' '.join(['%02x' % (ord(c), ) for c in s.payload]))
+
+@contextlib.contextmanager
+def USBHandle(dev):
+ uh = dev.open()
+ try:
+ yield uh
+ finally:
+ del uh
+
+def repower_r0ket():
+ hubs = hub_ctrl.find_hubs(False, False)
+ for h in hubs:
+ if h['dev'].idVendor == 0x050f and h['dev'].idProduct == 0x0003:
+ break
+ else:
+ print 'could not find USB hub!'
+ return
+
+ with USBHandle(h['dev']) as uh:
+ feat = hub_ctrl.USB_PORT_FEAT_POWER
+ index = 1 # port no
+
+ req = usb.REQ_CLEAR_FEATURE
+ uh.controlMsg(requestType = hub_ctrl.USB_RT_PORT, request = req, value = feat, index = index, buffer = None, timeout = 1000)
+ print '\033[31;1m%s: port powered off.\033[m' % (ts(), )
+ sleep(2)
+ req = usb.REQ_SET_FEATURE
+ uh.controlMsg(requestType = hub_ctrl.USB_RT_PORT, request = req, value = feat, index = index, buffer = None, timeout = 1000)
+ print '\033[31;1m%s: port powered on.\033[m' % (ts(), )
+
+ with open('/var/log/ethcan.log', 'a') as logf:
+ logf.write('%s: r0ket powercycled\n' % (ts(), ))
+
+def main():
+ poller = select.poll()
+ poller.register(s, select.POLLIN)
+ timeout = 10
+ while True:
+ ev = poller.poll(timeout * 1000)
+ if len(ev) == 0:
+ # no stats frame for 10s ... r0ket stuck.
+ try: repower_r0ket()
+ except: traceback.print_exc()
+ timeout = 20
+ continue
+ else:
+ timeout = 10
+
+ (data, addr) = s.recvfrom(65536)
+ if addr[0] != 'br0':
+ continue
+ try:
+ pkt = Frame(data)
+ except NotACANFrame, e:
+ if str(e) != 'stats frame':
+ print e
+ continue
+ except InvalidCANFrame, e:
+ print e
+ continue
+
+ scf = SubCANFrame.create(pkt)
+ if scf is not None:
+ scf.process()
+
+ print ts(), addr[0], pkt
+
+ rd = {}
+ for d in devices:
+ rd[d.name] = d.dict()
+ output_file = '/home/services/http/subcan.json'
+ with open(output_file + '.new', 'w') as output:
+ output.write(json.dumps(rd, sort_keys=True, indent=4))
+ os.rename(output_file + '.new', output_file)
+
+if __name__ == '__main__':
+ ifindex = 3
+
+ s = socket.socket(AF_PACKET, SOCK_RAW, htons(0x88b7))
+ mreq = pack('@iHH8s', ifindex, 0, 6, '\xff\x3a\xf6CAN\x00\x00')
+ s.setsockopt(263, 1, mreq)
+
+ main()