Beispiel #1
0
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)
Beispiel #2
0
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()
Beispiel #3
0
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()
Beispiel #4
0
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)
Beispiel #6
0
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()
Beispiel #7
0
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
Beispiel #8
0
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'
        }