def update_cache(): json = {'command_names': {}} db_model = Model() # command id - names all_commands = db_model.get_commands() for cmd in all_commands: json['command_names'][cmd['cmd_id']] = cmd['cmd_name'] # device names and vendors all_devices = db_model.get_all_device_names() json['devices'] = {} for dev in all_devices: json['devices'][dev['dev_name']] = { 'vendor_name': dev['vendor_name'], 'model_name': dev['model_name'] } # find all models, their vendor and their connection info for m in db_model.get_models_w_cnx_info(): vendor_name = m['vendor_name'] model_name = m['model_name'] if vendor_name not in json: json[vendor_name] = {} if model_name not in json[vendor_name]: json[vendor_name][model_name] = {} # aliases to the dictionaries m_dict = json[vendor_name][model_name] # connection info m_dict['cnx'] = { 'name': m['cnx_name'], 'type': m['cnxt_name'], 'server': m['cnx_server'], 'port': m['cnx_port'], 'username': m['cnx_username'], 'msg_template': m['cnx_msg_template'] } if m['cnxt_name'] == 'mqtt': m_dict['cnx']['tx_topic'] = m['cnx_mqtt_tx_topic'] m_dict['cnx']['rx_topic'] = m['cnx_mqtt_rx_topic'] # generate model commands m = import_module(f'drivers.{vendor_name}.{model_name}.funcs') m_dict['commands'] = m.generate_cmd_table(all_commands) db_model.cleanup() print(cache_json_path) with open(cache_json_path, 'w') as fout: json_dump(json, fout, indent=4)
def cronjob(sched_id, cmd_id): db_model = Model() devices = db_model.get_schedule_active_devices(sched_id, 'iled', 'iled') if not devices: return with open(cache_json_path, 'r') as fin: json = json_load(fin) cmd_name = json['command_names'][str(cmd_id)] errors = [] succeeded = [] dev_ids_to_update = [] for dev_id, dev_name, password in devices: # call worker.py res = handle_request( vendor='iled', model='iled', dev_name=dev_name, password=password, command=cmd_id ) if res['status'] != 'OK': errors.append({ 'dev_name': dev_name, 'status': res['status'], 'reason': res['message'] }) else: dev_ids_to_update.append(dev_id) succeeded.append(dev_name) if errors: json = json_dumps({'schedule_id' : sched_id, 'vendor' : 'iled', 'model' : 'iled', 'command': cmd_name, 'errors' : errors}) db_model.insert_log('schedule_error', json, 1) if dev_ids_to_update: db_model.update_device_status(dev_ids_to_update, cmd_id=cmd_id, dstat_name='active') db_model.cleanup()
def _cronjob(sched_id): db_model = Model() devices = db_model.get_schedule_active_devices(sched_id, 'nasys', 'ul2011') sched_cmd_data = generate_schedule(sched_id) cmd = 'send_custom_command' msg = json_dumps(sched_cmd_data) errors = [] succeeded = [] for _dev_id, dev_name, password in devices: res = handle_request(vendor='nasys', model='ul2011', dev_name=dev_name, password=password, command=cmd, parameter=msg) if res['status'] != 'OK': errors.append({ 'dev_name': dev_name, 'status': res['status'], 'reason': res['message'] }) else: succeeded.append(dev_name) if errors: json = json_dumps({ 'schedule_id': sched_id, 'vendor': 'nasys', 'model': 'ul2011', 'errors': errors }) db_model.insert_log('schedule_error', json, 1) db_model.cleanup()
def update_cron(): times = get_sunrise_sunset_times() db_model = Model() schedule_items = db_model.get_all_schedule_items(times) schedule_models = db_model.get_all_schedule_models() db_model.cleanup() # print(schedule_items) # print(schedule_models) with open(cron_txt_path, 'w') as fout: write_crons_header(fout) for sched_id in schedule_items: items = schedule_items[sched_id] models = schedule_models[sched_id] # FIXME: the lines probably belond in a separate function for each model in drivers/model/funcs.py # NASys controllers if ('nasys', 'ul2011') in models: fout.write( f'{crontimes["nasys_update_schedule"]} /usr/bin/python3 {basepath}/drivers/nasys/ul2011/cron.py {sched_id}\n' ) # iLED controllers if ('iled', 'iled') in models: for time, cmd_id, _cmd_name in items: fout.write( f'{time[3:5]} {time[0:2]} * * * /usr/bin/python3 {basepath}/drivers/iled/iled/cron.py {sched_id} {cmd_id}\n' ) fout.flush() username = getenv('USER') run([f'crontab -u {username} {cron_txt_path} <<EOL'], shell=True)
succeeded, errors = slave.execute() # If there were errors, go to the except clause # nothing is done for now, just thinking ahead if errors: raise CommandExecutionError() except UnknownCommandError as e: errors = [{'type': 'unknown_command'}] error = e except CommandExecutionError as e: error = e finally: json['errors'] = errors json['succeeded'] = succeeded try: json['error_type'] = error.__class__.__name__ json['message'] = error.get_description() except: json['message'] = slave.format_message(succeeded, errors) db_model.update_log(log['log_id'], 'done', json_dumps(json), bool(errors)) # Release db_model.cleanup() unlink(pidfile)
class Notifier: def __init__(self, protector): self.protector = protector self.db_model = Model() self.vendor_name = 'nasys' self.model_name = 'ul2011' self.initialize_notifier() def initialize_notifier(self): cnx = self.db_model.get_cnx_info(self.vendor_name, self.model_name) self.server = cnx['cnx_server'] port = cnx['cnx_port'] if port is not None and len(port) > 0: self.server += f':{port}' # this will be called when a new message is received def on_message(self, msg): print('Received: ', msg) json = json_loads(msg) data = str(json['payload']) fport = str(json['port']) dev_name = str(json['deveui']) # TODO: make sure the device is ours if fport == '24': status_info = decode_fp24_message(data) print(status_info) self.db_model.insert_log('feedback', json_dumps(status_info), 0) # TODO what if node error? # TODO profile_id == 255 ==> set individually # TODO current_level ===> update cmd_id? (hm...) elif fport == '25': usage_info = decode_fp25_message(data) print(usage_info) self.db_model.insert_sensor_data(dev_name, usage_info) elif fport == '60': reply_info = decode_fp60_message(data) print(reply_info) self.db_model.insert_log('feedback', json_dumps(reply_info), 0) # TODO also implement 01feXX message decoding elif fport == '99': sys_info = decode_fp99_message(data) print(sys_info) self.db_model.insert_log('feedback', json_dumps(sys_info), 0) # TODO # set active/inactive/error status depending on boot/shutdown/error # remember: change map icon as well def main_loop(self): while True: ws = websocket.WebSocket() ws.connect(self.server) if not ws.connected: raise KeyboardInterrupt() try: while True: msg = ws.recv() with self.protector: self.on_message(msg) except: # wait a bit, then reconnect # WARNING: we could possibly drop crucial messages # sleep(1) pass def clean_up(self): self.db_model.cleanup()
class Notifier: def __init__(self, protector): self.protector = protector self.client = mqtt.Client() self.db_model = Model() self.initialize_notifier() def initialize_notifier(self): cnx = self.db_model.get_cnx_info('iled', 'iled') # Called when the client receives a CONNACK response from the server def on_connect(client, userdata, flags, rc): # DEBUG print('Connected.') client.subscribe(cnx['cnx_mqtt_rx_topic']) # DEBUG print('Subscribed to {}'.format(cnx['cnx_mqtt_rx_topic'])) # Called when the client receives a message from the server def on_message(client, userdata, msg): with self.protector: try: topic = msg.topic msg = json_loads(str(msg.payload.decode())) dev_name = msg['devEUI'] command_hex = msg['dataHex'] except (ValueError, KeyError): # DEBUG print(datetime.now(), 'Ignoring bad message', topic, msg) return # DEBUG print(datetime.now(), topic, dev_name, command_hex) if command_hex == '312e303038': print(f'Received {command_hex} from {dev_name}') return info = self.db_model.get_dev_id_and_cmd_name(dev_name) if not info: print(f'Unknown device name {dev_name}') return try: dim_level_in_db = int( info['cmd_name'][len('set_dim_level_'):]) dim_level_reported = hex_to_dim_level(command_hex) has_problem = dim_level_in_db != dim_level_reported except: has_problem = True data = json_dumps({ 'received_message': json_dumps(msg), 'wanted_dim_level': dim_level_in_db, 'actual_dim_level': dim_level_reported, }) self.db_model.insert_log('feedback', data, has_problem) self.db_model.update_device_status( [info['dev_id']], dstat_name='error' if has_problem else 'active') self.client.on_connect = on_connect self.client.on_message = on_message password = aes_decrypt(cnx['cnx_password']) self.client.username_pw_set(cnx['cnx_username'], password) password = '' if cnx['cnx_port'] is not None: self.client.connect(cnx['cnx_server'], port=int(cnx['cnx_port']), keepalive=60) else: self.client.connect(cnx['cnx_server'], keepalive=60) def main_loop(self): self.client.loop_forever() def clean_up(self): try: self.client.disconnect() self.db_model.cleanup() except: pass
def _handle_request(**kwargs): # Load json with open(cache_json_path, 'r') as fin: cache_json = json_load(fin) try: cmd_id = str(kwargs['command']) except KeyError as e: return {'status': 'NOTOK', 'message': f'Missing parameters: {e}'} # If cmd_id is not a known id, assume the command name has been given try: cmd_name = cache_json['command_names'][cmd_id] except KeyError: cmd_name = cmd_id ################################################################# # Set dim level command. All current models support it. ################################################################# if cmd_name.startswith('set_dim_level'): try: vendor = kwargs['vendor'] model = kwargs['model'] dev_name = kwargs['dev_name'] password = kwargs['password'] except KeyError as e: return {'status': 'NOTOK', 'message': f'Missing parameters: {e}'} model_info = cache_json[vendor][model] cmd_data = model_info['commands'][cmd_name] cnx = model_info['cnx'] return send_commands(dev_name, cmd_data, cnx, password) ################################################################# # Update the cron file. ################################################################# elif cmd_name == 'update_cron': # run updater update_cron() return {'status': 'OK', 'message': 'Success'} ################################################################# # Update the schedule. ################################################################# elif cmd_name == 'update_cache': # run updater update_cache() return {'status': 'OK', 'message': 'Success'} ################################################################# # Start notification daemon for a certain model ################################################################# elif cmd_name == 'start_notifications': try: vendor = kwargs['vendor'] model = kwargs['model'] except KeyError as e: return {'status': 'NOTOK', 'message': f'Missing parameters: {e}'} pidfile = f'{basepath}/pids/__{vendor}__{model}__notifier__.pid' daemon = f'{basepath}/scripts/notification_daemon.py' if is_locked_pidfile(pidfile): return {'status': 'OK', 'message': 'Daemon is running'} Popen(['/usr/bin/python3', daemon, '-v', vendor, '-m', model]) return {'status': 'OK', 'message': 'Notification daemon started'} ################################################################# # Stop notification daemon for a certain model ################################################################# elif cmd_name == 'stop_notifications': try: vendor = kwargs['vendor'] model = kwargs['model'] except KeyError as e: return {'status': 'NOTOK', 'message': f'Missing parameters: {e}'} pidfile = f'{basepath}/pids/__{vendor}__{model}__notifier__.pid' try: with open(pidfile, 'r') as fin: pid = int(fin.readline()) # ask daemon to terminate kill(pid, SIGTERM) return {'status': 'OK', 'message': 'Notification daemon stopped'} except FileNotFoundError as e: return { 'status': 'OK', 'message': 'Notification daemon is not running' } except OSError as e: if e.errno == ESRCH: return { 'status': 'OK', 'message': 'Notification daemon is not running' } else: return { 'status': 'NOTOK', 'message': 'Stopping the daemon failed' } except Exception as e: return { 'status': 'OK', 'message': f'An unknown error occured: {e}' } ################################################################# # Custom command to update a device schedule. (only for devices # that support it, e.g. Nasys models) ################################################################# elif cmd_name.startswith('set_device_schedule'): try: vendor = kwargs['vendor'] model = kwargs['model'] dev_name = kwargs['dev_name'] password = kwargs['password'] sched_id = int(kwargs['parameter']) except KeyError as e: return {'status': 'NOTOK', 'message': f'Missing parameters: {e}'} except ValueError as e: return {'status': 'NOTOK', 'message': f'Invalid parameter: {e}'} model_info = cache_json[vendor][model] cnx = model_info['cnx'] # FIXME: pass the schedule as parameter, not the sched_id only, too slow db_model = Model() schedule = db_model.get_schedule_items(sched_id) db_model.cleanup() try: from importlib import import_module m = import_module(f'drivers.{vendor}.{model}.funcs') commands = m.generate_schedule_commands(schedule) except (ImportError, AttributeError): return { 'status': 'OK', 'message': f'{vendor}/{model} models do not store their schedule' } return send_commands(dev_name, commands, cnx, password) ################################################################# # Custom command to clear the schedule of a device (that supports it) ################################################################# elif cmd_name.startswith('clear_device_schedule'): try: vendor = kwargs['vendor'] model = kwargs['model'] dev_name = kwargs['dev_name'] password = kwargs['password'] except KeyError as e: return {'status': 'NOTOK', 'message': f'Missing parameters: {e}'} model_info = cache_json[vendor][model] cnx = model_info['cnx'] try: cmd_data = model_info['commands']['clear_device_schedule'] except KeyError: return { 'status': 'OK', 'message': f'{vendor}/{model} models do not store their schedule' } return send_commands(dev_name, cmd_data, cnx, password) ################################################################# # Send a custom message to a device ################################################################# elif cmd_name.startswith('send_custom_command'): try: vendor = kwargs['vendor'] model = kwargs['model'] dev_name = kwargs['dev_name'] password = kwargs['password'] parameter = kwargs['parameter'] except KeyError as e: return {'status': 'NOTOK', 'message': f'Missing parameters: {e}'} model_info = cache_json[vendor][model] cnx = model_info['cnx'] try: custom_cmd_data = json_loads(parameter) except: return {'status': 'NOTOK', 'message': f'Invalid message: {e}'} return send_commands(dev_name, custom_cmd_data, cnx, password) ################################################################# # Unknown command, error out. ################################################################# else: # Unknown command return { 'status': 'NOTOK', 'message': f'Command {cmd_name} is not implemented' }