Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
from __future__ import division
import sys
import time
import yaml as yamllib
from enum import Enum, unique
import serial
import contextlib
from docopt import docopt
# Todo: Si on essaie de programmer un node et que le node connecté ne correspond pas
# au nodeid, on demande confirmation !
DELAY = 0.01
FILE = 'oa.yaml'
with open(FILE) as f:
yaml = yamllib.load(f)
class OAException(Exception):
pass
IO_TO_EXT_INT = [ 2, 3, 1, 0, -1, -1, -1, 6, 4, 5 ]
EXT_INT_TYPE = ( 'falling', 'change', 'rising' )
IOS = ( 0, 1, 2, 3, -1, -1, 6, 7, 8, 9 )
IO_STATE = ( 'input', 'output', 'low', 'high', 'pullup', 'nopullup' )
frame_mandatory_fields = ('counter', 'waketype', 'wakearg')
@unique
class FRAME_CONTENT(Enum):
# Preamble
TYPE = 1
COUNTER = 2
WAKETYPE = 3
WAKEARG = 4
# Digital input
INPUT0 = 8
INPUT1 = 9
INPUT2 = 10
INPUT3 = 11
INPUT4 = 12
INPUT5 = 13
INPUT6 = 14
INPUT7 = 15
INPUT8 = 16
INPUT9 = 17
# Analog input
ANALOG0 = 32
ANALOG1 = 33
ANALOG2 = 34
ANALOG3 = 35
ANALOG4 = 36
ANALOG5 = 37
# Analog value
VOLTAGE = 64
TEMPERATURE = 65
f = FRAME_CONTENT
frame_content_map = {
'type': (f.TYPE, 1),
'counter': (f.COUNTER, 2),
'waketype': (f.WAKETYPE, 1),
'wakearg': (f.WAKEARG, 1),
'input0': (f.INPUT0, 1),
'input1': (f.INPUT1, 1),
'input2': (f.INPUT2, 1),
'input3': (f.INPUT3, 1),
'input4': (f.INPUT4, 1),
'input5': (f.INPUT5, 1),
'input6': (f.INPUT6, 1),
'input7': (f.INPUT7, 1),
'input8': (f.INPUT8, 1),
'input9': (f.INPUT9, 1),
'analog0': (f.ANALOG0, 2),
'analog1': (f.ANALOG1, 2),
'analog2': (f.ANALOG2, 2),
'analog3': (f.ANALOG3, 2),
'analog4': (f.ANALOG4, 2),
'analog5': (f.ANALOG5, 2),
'voltage': (f.VOLTAGE, 2),
'temperature': (f.TEMPERATURE, 1)
}
class Profile:
name = ''
description = ''
ext_int = {}
ios = {}
frame = None
feedback = True
eintwait = 2
period = 5
def __init__(self):
self.name = self.description = ''
self.ext_int = {}
self.ios = {}
self.frame = {}
def __repr__(self):
return '%s' % (self.description)
class Config:
__name = ''
freq = ''
group = ''
ack = 0
power = 0
cmdtimeout = 0
usbtimeout = 0
autostart = ''
remote = {}
@staticmethod
def format(name, value):
if name == 'name':
return str(value)
elif name == 'group':
return int(value)
elif name == 'freq':
if value in (433, 868, 915):
return str(value)[0]
return None
elif name in ('ack', 'autostart'):
return 0 if not int(value) else 1
elif name == 'power':
value = int(value)
return value if 0 <= value <= 7 else None
elif name in ('cmdtimeout', 'usbtimeout'):
return int(value)
elif name == 'remote':
return "%i %i" % (int((value['active'] is True)), int(value['wait_error_cycle']))
class Frame:
id = ''
name = ''
content = []
def __init__(self):
self.content = []
def __repr__(self):
return '%i:%s (%s)' % (self.id, self.name, self.content)
def parse_devices():
devices = {}
for name, content in yaml['devices'].items():
devices[name] = content
return devices
def parse_nodes():
nodes = {}
for name, content in yaml['nodes'].items():
nodes[name] = content
return nodes
def parse_config(content, name=None):
config = Config()
for name, content in content.items():
if name:
config.__name = name
if name in vars(Config) and name[0:2] == '__':
continue
setattr(config, name, Config.format(name, content));
return config
def parse_frame(content):
# Frame
try:
def frame_add(s):
key, _ = frame_content_map[s]
frame.content.append(key)
frame = Frame()
frame.id = content['id']
# Add mandatory fields
for field in frame_mandatory_fields:
if field not in frame.content:
frame_add(field)
try:
frame.name = content['name']
except KeyError:
pass
for item in content['content']:
if item in frame_mandatory_fields:
continue
try:
if type(item) == list:
for c in item:
frame_add(c)
else:
frame_add(item)
except KeyError:
raise OAException("Unknow frame item: %s" % (item))
except OAException as e:
print(e)
return
return frame
def parse_frames():
frames = {}
for name, content in yaml['frames'].items():
frames[name] = parse_frame(content)
return frames
# Declare
if 'declare' in yaml:
for index, item in yaml['declare'].items():
if index in frame_content_map:
raise OAException("Frame %s already exists !" % (index))
frame_content_map[index] = tuple(item)
def parse_profile(content, name=None):
profile = Profile()
if name:
profile.name = name
for attr in ('description', 'period', 'feedback', 'eintwait'):
if attr[0:2] == '__':
continue
if attr in content:
setattr(profile, attr, content[attr]);
# Parse external_interrupts
if 'external_interrupts' in content:
try:
for index, info in content['external_interrupts'].items():
int_num, int_type = None, None
if index[0:2] == 'io':
value = int(index[2:])
if value != -1:
try:
int_num = str(IO_TO_EXT_INT[value])
except IndexError:
pass
#else:
# int_num = value
elif index[0:3] == 'int':
try:
int_num = int(index[3:])
except ValueError:
pass
#else:
# if value != -1 and value in IO_TO_EXT_INT:
# int_num = value
if int_num is None:
raise OAException("External interrupt %s not found !" % (index))
if info.lower() not in EXT_INT_TYPE:
raise OAException("External interrupt type invalid !")
return
profile.ext_int[int_num] = info.lower()
except OAException as e:
print(e)
return
ios = {}
# Parse ios
if 'ios' in content:
try:
for index, info in content['ios'].items():
io_num = None
if index[0:2] == 'io':
io_num = int(index[2:])
if io_num == -1 or (io_num not in IOS) or io_num is None:
raise OAException("Input / Output %s not found !" % (index));
profile.ios[io_num] = []
for item in info:
if item.lower() not in IO_STATE:
raise OAException("IO state invalid: %s (io %s) !" % (item, index))
profile.ios[io_num].append(item)
except OAException as e:
print(e)
return
frame = []
# Frame
profile.frame = parse_frame(content['frame'])
'''
try:
def frame_add(s):
key, _ = frame_content_map[s]
frame.content.append(key)
frame = Frame()
frame.id = content['frame']['id']
try:
frame.name = content['frame']['name']
except KeyError:
pass
for item in content['frame']['content']:
try:
if type(item) == list:
for c in item:
frame_add(c)
else:
frame_add(item)
except KeyError:
raise OAException("Unknow frame item: %s" % (item))
profile.frame = frame
except OAException as e:
print(e)
return
'''
return profile
arguments = docopt("""OpenAlarm Base
Usage:
{0} [options] nodeid <nodeid>
{0} [options] config write <config>
{0} [options] config set <key> <value>
{0} [options] profile write <profile_name> [<profile_id>]
{0} [options] profile set <profile_id>
{0} [options] node write <node_name>
{0} [options] node read
{0} [options] listen [--csv <csv_file>]
{0} [options] remote <node_name> --set <commands>...
{0} --version
Options:
-p <port> Serial port
-f <nodeid> Force node write even when different nodeid
-h --help Show this screen
-d --debug Debug mode
-v --verbose Verbose mode
""".format(sys.argv[0]), version='OpenAlarm Base 1.x')
if __name__ == '__main__':
class Serial:
device = None
serial = None
debug = False
verbose = False
def __init__(self, device):
self.serial = serial.Serial(device, baudrate=9600)
self.device = device
def send(self, string, read=False):
if self.debug or self.verbose:
print('%s' % (string))
if not self.debug:
self.serial.write(bytes('%s\r' % (string), 'ascii'))
if read:
# Eat response
self.read()
def read(self):
if not self.debug:
response = self.serial.readline().decode().strip()
else:
response = 'OK'
if self.verbose:
print("-> %s" % (response))
return response if not self.debug else 'OK'
@contextlib.contextmanager
def save_verbose(self):
self.send("verbose get")
verbose = self.read()
self.send("verbose set 0", True)
try:
yield
finally:
# Restore verbose
self.send("verbose set %s" % (verbose))
@contextlib.contextmanager
def save_profile(self):
self.send("profile get")
profile = self.read()
try:
yield
finally:
# Restore profile
self.send("profile set %s" % (profile))
def send(cmd, error="Error !"):
ser.send(cmd)
read = ser.read()
if read != 'OK':
print("Error while sending cmd : %s, returned : %s" % (cmd, read))
raise OAException(error)
time.sleep(DELAY)
def write_config(ser, config):
config = parse_config(config)
for item, value in vars(config).items():
if item[0:2] == '__':
continue
send("set %s %s" % (item, value), "Error while setting param %s to %s !" % (item, value))
def write_profile(ser, profile, profile_id=0):
if profile_id is not None:
ser.send("set profile set %i" % (profile_id), True)
profile = parse_profile(profile)
for item in ('period', 'feedback', 'eintwait'):
value = getattr(profile, item)
if type(value) == bool:
value = int(value)
send("set %s %s" % (item, value), "Error while setting param %s to %s !" % (item, value))
# Program frame
send("frame set %s %s" % (profile.frame.id, ' '.join([ str(item.value) if isinstance(item, FRAME_CONTENT) else str(item) for item in profile.frame.content if item not in (FRAME_CONTENT.COUNTER, FRAME_CONTENT.WAKETYPE, FRAME_CONTENT.WAKEARG) ])), "Error while setting int !")
# Program ios
for line in [ str(io) + ' ' + ' '.join(data) for io, data in profile.ios.items() ]:
send("io set %s" % (line), "Error while setting io !")
# Program int
send("int clear")
for line in [ str(io) + ' ' + data for io, data in profile.ext_int.items() ]:
send("int add %s\r" % (line), "Error while setting int !")
force_node_id = None
def test_nodeid(config_nodeid):
if not force_node_id:
ser.send("get nodeid")
nodeid = int(ser.read())
if nodeid != config_nodeid:
print("Config nodeid (%i) is not same than node found (%i) on usb !" % (yaml['nodes'][name]['id'], nodeid))
sys.exit()
try:
debug = ('-d' in arguments and arguments['-d'] == True) or ('--debug' in arguments and arguments['--debug'] == True)
verbose = ('-v' in arguments and arguments['-v'] == True) or ('--verbose' in arguments and arguments['--verbose'] == True)
if '-f' in arguments and arguments['-f'] is not None:
force_node_id = int(arguments['-f'])
if verbose:
print("Verbose mode !")
if not debug:
ser = None
def open_serial(device):
try:
return Serial(device)
except serial.serialutil.SerialException:
pass
if '-p' in arguments and arguments['-p'] is not None:
device = arguments['-p']
ser = open_serial(device)
if not ser:
raise OAException("Unable to open device %s !" % (device))
else:
for device in parse_devices():
ser = open_serial(device)
if ser:
break
if not ser:
raise OAException("Unable to open any device !")
print("Use device %s" % (device))
else:
ser = Serial(None)
ser.debug = debug
ser.verbose = verbose
# +--------+
# | Nodeid |
# +--------+
if arguments['nodeid'] == True:
nodeid = int(arguments['<nodeid>'])
print("Set nodeid to %i" % (nodeid))
with ser.save_verbose():
ser.send("set nodeid %i" % (nodeid))
# +--------+
# | Config |
# +--------+
elif arguments['config'] == True:
if arguments['write'] == True:
name = arguments['<config>']
if name not in yaml['configs']:
raise OAException("Config %s not found !" % (name))
print("Program node with config '%s'" % (name))
# Write config !
with ser.save_verbose():
write_config(ser, yaml['configs'][name])
elif arguments['set'] == True:
key, value = arguments['<key>'], arguments['<value>']
if key not in vars(Config):
raise OAException("Error: unknow key !")
try:
value = Config.format(key, value)
if value is None:
raise OAException()
except:
raise OAException("Error: %s value is invalid !" % (value))
with ser.save_verbose():
send("set %s %s" % (key, value), "Error while setting param %s to %s !" % (item, value))
# +---------+
# | Profile |
# +---------+
elif arguments['profile'] == True:
if arguments['write'] == True:
name = arguments['<profile_name>']
#profiles = parse_profiles();
if name not in yaml['profiles']:
raise OAException("Profile %s not found !" % (name))
profile_id = None
try:
profile_id = int(arguments['<profile_id>'])
except TypeError:
pass
print("Program node with profile '%s'" % (name))
# Write profile !
with ser.save_verbose():
with ser.save_profile():
write_profile(ser, yaml['profiles'][name], profile_id)
elif arguments['set'] == True:
profile_id = int(arguments['<profile_id>'])
with ser.save_verbose():
ser.send("set profile %i" % (profile_id))
if ser.read() != 'OK':
raise OAException("Error while setting profile to %i !" % (profile_id))
# +------+
# | Node |
# +------+
elif arguments['node'] == True:
if arguments['write'] == True:
name = arguments['<node_name>']
if name not in yaml['nodes']:
raise OAException("Node %s not found !" % (name))
test_nodeid(yaml['nodes'][name]['id'])
with ser.save_verbose():
for name, content in yaml['nodes'][name].items():
if name == 'id':
ser.send("set nodeid %i" % (content))
elif name == 'key':
ser.send("set key set %s" % (force_node_id if force_node_id else content))
elif name == 'config':
write_config(ser, content)
elif name == 'profile':
for profile_id, profile in content.items():
write_profile(ser, profile, profile_id)
if arguments['read'] == True:
with ser.save_verbose():
ser.send("get nodeid")
print("Nodeid: %i" % int(ser.read()))
# +--------+
# | Listen |
# +--------+
elif arguments['listen'] == True:
print("Start listen mode !")
frames = parse_frames()
frames_cache = {}
"""
Examples:
- 01020400010200800C6001
- 01020500010200760C6101
- 01020600010200760C6101
"""
swap = lambda x: '%s%s' % (x[2:4], x[0:2])
nodeid = lambda x: int(x, 16)
frametype = lambda x: int(x, 16)
counter = lambda x: int(swap(x), 16)
waketype = lambda x: int(x, 16)
wakearg = lambda x: int(x, 16)
#voltage = lambda x: "%0.2fV (0x%s)" % (int(swap(x), 16) / 1000, x)
voltage = lambda x: "%0.2f" % (int(swap(x), 16) / 1000)
#temperature = lambda x: "%0.2f˚C (0x%s)" % (int(swap(x), 16) if int(swap(x), 16) < 0x7F else (int(swap(x), 16) - 0x100), x)
temperature = lambda x: "%0.2f" % (int(swap(x), 16) if int(swap(x), 16) < 0x7F else (int(swap(x), 16) - 0x100))
#input = lambda x: int(x)
def input(bits, value):
size = len(bits)
values = []
for bit in bits:
values.append("bit%s:%i" % (bit, 1 if (int(value, 16) & (int(bit) + 1)) else 0))
#return ', '.join(values)
return values
frame_decoder = {
#'input?': input,
'counter': counter,
'waketype': waketype,
'wakearg': wakearg,
'voltage': voltage,
'temperature': temperature
}
isbit = lambda x: (8 <= x <= 17)
def decode(name, data):
#if name == 'temperature':
return frame_decoder[name](payload[fromm:pointer])
index = 0
def read_byte():
global index
index += 2
return int(content[index - 2:index], 16)
def read_word():
global index
index += 2
return int(content[index - 2:index], 16)
def read_all():
return content[index:]
def print_result(name, data):
maxsize = 1
for frame, _ in frame_decoder.items():
if len(frame) > maxsize:
maxsize = len(frame)
print(" {:<{maxsize}}: {}".format(name, data, maxsize=maxsize + 1))
import datetime
if '--csv' in arguments and arguments['--csv'] is not False:
import csv
csv_file = arguments['<csv_file>']
f = open(csv_file, 'a')
writer = csv.writer(f)
with ser.save_verbose():
ser.send("listen raw")
try:
nodeinfo = {}
while True:
index = 0
line = ser.read()
#line = "OKX 01020400010205800C6001"
if line[0:3] != 'OKX':
continue
info, content = line.split(' ')
nodeid = read_byte()
if nodeid not in nodeinfo:
nodeinfo[nodeid] = { 'last': time.time() }
# Todo: Test if preamble is present
frametype = read_word()
payload = read_all()
print("Nodeid: %i, frame type: %i" % (nodeid, frametype), end='')
if verbose:
print(", payload: %s (%i second(s))" % (payload, time.time() - nodeinfo[nodeid]['last']))
else:
print(" (%i second(s))" % (time.time() - nodeinfo[nodeid]['last']))
# Get frametype
if frametype not in frames_cache:
found = False
for _, frame in frames.items():
if frame.id == frametype:
frames_cache[frame.id] = frame.content
found = True
if not found:
#raise OAException("Frame type not found !")
print("Frame type not found !")
continue
pointer = 0
lastisbit = False
bitstart = 0
bits = []
data = []
data.append(datetime.datetime.now())
for item in frames_cache[frametype]:
try:
name = item.name.lower()
except AttributeError:
print("Pouf! %s" % (name))
sys.exit()
_, size = frame_content_map[name]
if isbit(_.value):
if not len(bits):
bitstart = pointer
bits.append(_.name[-1])
elif len(bits):
#print(bitstart, pointer, payload[bitstart:pointer], payload)
a = input(bits, payload[bitstart:pointer])
#print(a)
print_result('Input', ', '.join(a))
bits = []
if not isbit(_.value) or (isbit(_.value) and not lastisbit):
fromm = pointer
pointer += size * 2
lastisbit = isbit(_.value)
# Skip contiguous input
if lastisbit or isbit(_.value):
continue
if name.lower() not in ('counter', 'waketype', 'wakearg') or verbose:
try:
result = decode(name, payload[fromm:pointer])
data.append(result)
print_result(name.capitalize(), result)
except KeyError:
print("Unable to found frame decoder (%s)" % (name))
lastisbit = isbit(_.value)
#if csv and nodeid == 5:
# writer.writerow(data)
nodeinfo[nodeid]['last'] = time.time()
#time.sleep(1)
except KeyboardInterrupt:
pass
finally:
print("Exit listen mode !")
ser.send("exit")
# +--------+
# | Remote |
# +--------+
elif arguments['remote'] == True:
nodes = parse_nodes()
name = arguments['<node_name>']
if name not in nodes:
raise OAException("Node %s not found !" % (name))
node = nodes[name]
valid_commands = {
'period': 'period',
'power': 'power',
'led_toggle': 'led toggle',
'led_set': 'led set',
'feedback': 'feedback',
'timeout': 'timeout',
'profile_set': 'profile set'
}
# Test command
commands = []
for arg in arguments['<commands>']:
try:
key, val = arg.split('=')
except ValueError:
raise OAException("Invalid command format !")
if not key in valid_commands:
raise OAException("Unknow command (use: %s) !" % (', '.join(valid_commands.keys())))
if int(val) > 255:
raise OAException("Invalid format argument !");
commands.append((valid_commands[key], int(val)))
with ser.save_verbose():
print("Start remote connection with node %i !" % (node['id']))
ser.send("remote %i %s" % (node['id'], node['key']))
try:
while True:
response = ser.read()
if response[0] == '.':
response = response.strip('.')
if response == 'Connecting!':
print("Connecting...")
elif response == 'Connected!':
print("Connected !")
# Send cmd
for command, val in commands:
print("Send command %s with arg %i" % (command, val))
send("%s %i" % (command, val))
break
elif response == 'OK':
pass
elif response == '.':
print(".")
elif response == 'Error!':
print("Error !")
break
elif response == 'Disconnected!':
print('Disconnected!')
break
else:
print("Paf:", response)
except KeyboardInterrupt:
ser.send("exit")
pass
finally:
print("Exit remote !")
ser.send("exit")
except OAException as e:
print(e)
sys.exit()