def update_device(jdevice): dprint('updating: ', jdevice) device_id = jdevice.get('device_id') or None if (not device_id or len(device_id) == 0): return ({'error': 'Invalid device_id'}) dev = Device.query.filter_by(device_id=device_id).first() if not dev: return ({'error': 'device_id does not exist'}) fallback_id = jdevice.get('fallback_id') or None # fallback_id must be a unique non-empty string if (fallback_id and len(fallback_id) > 0): fb = Device.query.filter_by(fallback_id=fallback_id).first() if fb: return ({'error': 'fallback_id already exists'}) dev.fallback_id = fallback_id mac = jdevice.get('mac') or None if (mac): dev.mac = mac ip = jdevice.get('ip') or None if (ip): dev.ip = ip hardware_type = jdevice.get('hardware_type') or None if (hardware_type): dev.hardware_type = hardware_type num_relays = jdevice.get('num_relays') or -1 if (num_relays >= 0): dev.num_relays = num_relays num_sensors = jdevice.get('num_sensors') or -1 if (num_sensors >= 0): dev.num_sensors = num_sensors print(jdevice.get('enabled')) ####enab = jdevice.get('enabled') or None # TODO: understand this! if 'enabled' in jdevice: dev.enabled = jdevice.get('enabled') db.session.commit() return ({'result': 'successfully updated device'})
def update_device(jdevice): dprint('updating: ', jdevice) device_id = jdevice.get('device_id') or None if (not device_id or len(device_id) == 0): return ({'result': False, 'error': 'Invalid device_id'}) dev = Device.query.filter_by(device_id=device_id).first() if not dev: return ({'result': False, 'error': 'device_id does not exist'}) # TODO: keep the default fallback id of Tasmota as it is; do not change it! fallback_id = jdevice.get('fallback_id') or None # fallback_id must be a unique non-empty string if (fallback_id and len(fallback_id) > 0): fb = Device.query.filter_by(fallback_id=fallback_id).first() if fb: return ({'result': False, 'error': 'fallback_id already exists'}) dev.fallback_id = fallback_id # TODO: keep the discovered MAC and IP of the device without changing mac = jdevice.get('mac') or None if (mac): dev.mac = mac ip = jdevice.get('ip') or None if (ip): dev.ip = ip hardware_type = jdevice.get('hardware_type') or None if (hardware_type): dev.hardware_type = hardware_type # TODO: auto discover num_relays num_relays = jdevice.get('num_relays') or -1 if (num_relays >= 0): dev.num_relays = num_relays # TODO: there is only one sensor object containing a JSON of all sensors num_sensors = jdevice.get('num_sensors') or -1 if (num_sensors >= 0): dev.num_sensors = num_sensors print(jdevice.get('enabled')) ####enab = jdevice.get('enabled') or None # TODO: understand the if(None) behaviour of a boolean variable in python! if 'enabled' in jdevice: dev.enabled = jdevice.get('enabled') db.session.commit() return ({'result': True, 'msg': 'successfully updated device'})
def ping_relsens(device_id): if SIMULATION_MODE: dprint('In simulation mode: not pinging the relays') return dprint('\nPinging relsens in the device: ', device_id) topic = '{}/{}/{}'.format(PUB_PREFIX, device_id, BROADCAST_RELSEN) mqtt.publish(topic, EMPTY_PAYLOAD)
def create_test_db(): dprint('Deleting the old database...') db.drop_all() # to avoid violating the unique value constraints dprint('Creating a test database...') db.create_all() #return (add_test_data()) return ({'result': 'Hub DB created'})
def insert_device(device_id, fallback_id=None, mac=None, ip=None, hardware_type="Generic", num_relays=1, num_sensors=0, enabled=True): if (not device_id or len(device_id) == 0): dprint('Invalid device_id') return False # TODO: return the error reason also dprint('inserting device id: {}'.format(device_id)) # check for existing device (device_id must be unique) dev = Device.query.filter_by(device_id=device_id).first() if dev: dprint('Device ID already exists: {}'.format(device_id)) return False # check for existing device (fallback_id must be unique) if (fallback_id): fb = Device.query.filter_by(fallback_id=fallback_id).first() if fb: dprint('Falback ID already exists: {}'.format(fallback_id)) return False dev = Device(device_id=device_id, fallback_id=fallback_id, mac=mac, ip=ip, hardware_type=hardware_type, num_relays=num_relays, num_sensors=num_sensors, enabled=enabled) db.session.add(dev) db.session.commit() dprint('Added device: {}'.format(dev)) return True
def update_timer(device_id, relsen_id, timer_list, repeat=True): dprint('updating timer for {}/{}..'.format(device_id, relsen_id)) relay = 1 if (relsen_id == 'POWER'): # the device has a single relay relay = 1 # redundant, but for clarity else: relay = relsen_id[ -1] # ASSUMPTION: relsen id is in the form POWER1, POWER2 etc. *** if relay < '0' or relay > '4': print('relsen_id has to be from POWER1 to POWER4 only') return Flase relay_num = int(relay) # relay number 1 to 4 within the device if len(timer_list) > 2: print( '\n**** CAUTION: Only 2 sechedules allowed per relay; others are ignored ****\n' ) if SIMULATION_MODE: dprint('In Simulation Mode: not setting timer') return True starting_timer_id = (relay_num - 1) * 4 + 1 send_timer_command(device_id, relay_num, starting_timer_id, timer_list[0], repeat) # this sets up 2 timers: one ON, and one OFf if len(timer_list) > 1: # process the second pair of schedule times send_timer_command(device_id, relay_num, starting_timer_id + 2, timer_list[1], repeat) # setup the next 2 timers return True
def send_timer_command(device_id, relay_num, timer_id, time_pair, repeat): suffix = 'Timer' + str(timer_id) # timer_id can be from 1 to 16 topic = '{}/{}/{}'.format(PUB_PREFIX, device_id, suffix) pl = { "Enable": 1, "Mode": 0, "Time": time_pair[0], "Window": 0, "Days": "1111111", "Repeat": repeat, "Output": relay_num, "Action": 1 } # ON payload = json.dumps(pl) dprint(topic, payload) mqtt.publish(topic, payload) # OFF timer for the same relay: suffix = 'Timer' + str(timer_id + 1) topic = '{}/{}/{}'.format(PUB_PREFIX, device_id, suffix) pl = { "Enable": 1, "Mode": 0, "Time": time_pair[1], "Window": 0, "Days": "1111111", "Repeat": repeat, "Output": relay_num, "Action": 0 } # OFF payload = json.dumps(pl) dprint(topic, payload) mqtt.publish(topic, payload)
def update_sensor_reading (device_id, str_msg): # TODO: save it in in-memory status (and later, in the database) #try: dprint ('sensor reading for: ', device_id) dprint (str_msg) jsensor = json.loads(str_msg) in_mem_status[devid][SENSOR_RELSEN] = jsensor['StatusSNS'] # this is stored as an inner json jstatus = {'device_id' : device_id, 'relsen_id' : SENSOR_RELSEN, 'status' : jsensor['StatusSNS']} socketio.emit (SERVER_EVENT, jstatus)
def ping_mqtt(): if SIMULATION_MODE: dprint ('In simulation mode: not pinging MQTT devices') return dprint ('\nPinging all devices...') topic = '{}/{}/{}'.format (PUB_PREFIX, BROADCAST_DEVICE, PUB_SUFFIX) # POWER #dprint (topic, ' (blank)') mqtt.publish (topic, EMPTY_PAYLOAD)
def send_tracer_broadcast(): if SIMULATION_MODE: dprint('In simulation mode: not sending tracer') return topic = '{}/{}/{}'.format(PUB_PREFIX, BROADCAST_DEVICE, BROADCAST_RELSEN) # POWER0 dprint('Sending probe to: ', topic) mqtt.publish(topic, EMPTY_PAYLOAD) # empty payload gets the relay status
def remove_all_relsens(current_user): dprint('{} is removing all relays/sensors...'.format(current_user)) rel = Relsen.query.all() dprint('{} records found.'.format(len(rel))) for rs in rel: db.session.delete(rs) db.session.commit() return ({'result': 'All relays & sensors removed.'})
def remove_all_status(current_user): dprint('{} is removing all status data...'.format(current_user)) sta = Status.query.all() dprint('{} records found.'.format(len(sta))) for s in sta: db.session.delete(s) db.session.commit() return ({'result': 'All status data removed.'})
def onboard_device(jnew_device): dprint('\nOnboarding: ', jnew_device) if not insert_device(jnew_device['device_id']): return False for rsid in jnew_device['relsen_list']: if not insert_relsen(jnew_device['device_id'], rsid): return False build_device_inventory() # update in-mem structures return True
def start_daemon(): if SIMULATION_MODE: dprint('\n* In Simulation Mode: not starting daemon thread *\n') return global bgthread print('\nChecking daemon...') with thread_lock: if bgthread is None: # as this should run only once print('\nStarting background thread...\n') bgthread = socketio.start_background_task(bgtask)
def onboard_device(jnew_device): dprint('\nOnboarding: ', jnew_device) if not insert_device(jnew_device['device_id']): return False for rsid in jnew_device['relsen_list']: if not insert_relsen(jnew_device['device_id'], rsid): return False ###build_device_inventory() # update in-mem structures; TODO: revisit this. needs testing. will this call succeed, since they are disabled? build_active_device_inventory_route() # this initializes their status also return True
def send_tracer_broadcast(): global sensor_count if SIMULATION_MODE: dprint ('In simulation mode: not sending tracer') return topic = '{}/{}/{}'.format (PUB_PREFIX, BROADCAST_DEVICE, BROADCAST_RELSEN) # POWER0 dprint ('Sending probe to: ',topic) mqtt.publish (topic, EMPTY_PAYLOAD) # empty payload gets the relay status sensor_count = (sensor_count+1) % SENSOR_INTERVAL if (sensor_count==0): request_sensor_reading(BROADCAST_DEVICE)
def update_network_params (device_id, str_msg): # TODO: save it in in-memory cache and database global in_mem_network print ('Network settings for: ', device_id) netparams = json.loads(str_msg) if (device_id not in in_mem_network): in_mem_network[device_id] = {} in_mem_network[device_id]['host_name'] = netparams['StatusNET']['Hostname'] in_mem_network[device_id]['ip_address'] = netparams['StatusNET']['IPAddress'] in_mem_network[device_id]['mac_id'] = netparams['StatusNET']['Mac'] #dprint (netparams) dprint(in_mem_network[device_id])
def subscribe_mqtt(): global subscribed if SIMULATION_MODE: # TODO: implement such a wrapper method for mqtt.publish() also dprint('\n* In Simulation Mode: not subscribing to MQTT *\n') return # TODO: additional subscriptions like TELE # do not check the 'subscribed' flag here: this may be a reconnect event! print('Subscribing to MQTT: %s' % (SUB_TOPIC)) mqtt.subscribe(SUB_TOPIC) # duplicate subscriptions are OK print('Subscribing to MQTT: %s' % (LWT_TOPIC)) mqtt.subscribe(LWT_TOPIC) # duplicate subscriptions are OK subscribed = True # tell socketIO.on_connect() not to subscribe again
def start_daemon(): global bgthread, TERMINATE if SIMULATION_MODE: dprint('\n* In Simulation Mode: not starting daemon thread *\n') return print('\nChecking daemon...') with thread_lock: if bgthread is None: # as this should run only once print('\nStarting background thread...\n') TERMINATE = False # reset the flag -it it was earlier stopped manually bgthread = socketio.start_background_task(bgtask) return {'result': True, 'msg': 'Worker thread started.'}
def get_devices_in_room(): room = request.args.get('room_name') if (not room): return ({'error': 'room_name is required'}) relsens = Relsen.query.filter_by(room_name=room).all() devices = set([]) # to avoid duplicates for rs in relsens: devices.add(rs.controller) dprint(devices) dprint(type(devices)) retval = [] for d in devices: retval.append(d.toJSON()) return ({'devices': retval})
def insert_relsen(device_id, relsen_id, relsen_name=None, relsen_type=None, room_name=None, room_type=None, group_name=None, schedule=None, repeat=False): dprint('inserting relsen: {}:{}'.format(device_id, relsen_id)) # check for existance of device (device_id must preexist) dev = Device.query.filter_by(device_id=device_id).first() if not dev: dprint('Unknown device ID: {}'.format(device_id)) return False for rel in dev.relsens: # combination of (device_id+relsen_id) must be unique if (rel.relsen_id == relsen_id): dprint('{}.{} already exists'.format(device_id, relsen_id)) return False rs = Relsen( controller=dev, relsen_id=relsen_id, relsen_name=relsen_name, relsen_type=relsen_type, room_name=room_name, room_type=room_type, group_name=group_name, schedule= schedule, # LHS is the DB column name; RHS is a stringified json object (which has 'schedule' as the key) repeat=repeat) db.session.add(rs) db.session.commit() dprint('Added relay: {}'.format(rs)) return True
def remove_device(current_user): devid = request.args.get('device_id') if (not devid): return ({'error': 'device_id is required'}) dev = Device.query.filter_by(device_id=devid).first() if (not dev): return ({'error': 'invalid device_id'}) dprint('{} is removing a device record..'.format(current_user)) for rs in dev.relsens: db.session.delete(rs) for sta in dev.stat: db.session.delete(sta) db.session.delete(dev) db.session.commit() return ({'result': 'Device record removed.'})
def remove_all_devices(current_user): dprint(remove_all_status()) # this is needed for dependency constraint dprint(remove_all_relsens()) dprint('{} is removing all device records...'.format(current_user)) devs = Device.query.all() dprint('{} records found.'.format(len(devs))) for d in devs: db.session.delete(d) db.session.commit() return ({'result': 'All device records removed.'})
def bgtask(): dprint ('Entering background thread...') global TERMINATE while not TERMINATE: socketio.sleep (PING_INTERVAL) # stop/daemon will be mostly called during this sleep if TERMINATE : break #dprint ('\nWaking !...') for devid in is_online: if (not is_online[devid]['online']): # TODO: Make 3 attempts before declaring it offline is_online[devid]['count'] = (is_online[devid]['count']+1) % MAX_RETRIES if (is_online[devid]['count']==0): mark_offline (devid) send_offline_notification (devid) for devid in is_online: is_online[devid]['online'] = False # reset for next round of checking send_tracer_broadcast() # get status of all relays: Necessary, when a device comes out of the offline mode print ('\n *** Background thread terminates. ***\n')
def insert_status (device_id, relay_status=None, sensor_values=None, # time_stamp=None, event_type=None, online=True): return {'result' : 'this is a placeholder'} # check for existance of device (device_id must already exist) dev = Device.query.filter_by (device_id=device_id).first() if not dev: dprint ('Unknown device ID: {}'.format(device_id)) return False st = Status ( controller = dev, relay_status = relay_status, sensor_values = sensor_values, event_type = event_type, online = online) db.session.add (st) db.session.commit() dprint ('Added status: {}'.format(st)) return True
def clear_timers (device_id, relsen_id): dprint ('Clearing all timers for: {}/{}'.format (device_id, relsen_id)) relay = 1 if (relsen_id == 'POWER'): # the device has a single relay relay = 1 # redundant, but for clarity else: relay = relsen_id[-1] # ASSUMPTION: relsen id is in the form POWER1, POWER2 etc. *** if relay < '0' or relay > '4': print ('relsen_id has to be from POWER1 to POWER4 only') return False relay_num = int(relay) # relay number 1 to 4 within the device starting_timer_id = (relay_num-1)*4 + 1 for i in range (0, MAX_TIMERS): relsen = 'Timer'+ str(starting_timer_id+i) topic = '{}/{}/{}'.format (PUB_PREFIX, device_id, relsen) payload = json.dumps({"Enable":0}) dprint (topic, payload) mqtt.publish (topic, payload) return True
def ping_device(device_id): if SIMULATION_MODE: dprint('In simulation mode: not pinging the device') return dprint('\nPinging the device: ', device_id) topic = '{}/{}/{}'.format(PUB_PREFIX, device_id, PUB_SUFFIX) # POWER dprint(topic, ' (blank)') mqtt.publish(topic, EMPTY_PAYLOAD)
def update_relsen(jrelsen): device_id = jrelsen.get('device_id') or None if (not device_id or len(device_id) == 0): return ({'error': 'Invalid device_id'}) relsen_id = jrelsen.get('relsen_id') or None if (not relsen_id or len(relsen_id) == 0): return ({'error': 'Invalid relsen_id'}) dev = Device.query.filter_by(device_id=device_id).first() if not dev: return ({'error': 'device_id does not exist'}) rs = Relsen.query.filter_by(device_id=device_id, relsen_id=relsen_id).first() if (not rs): return ({'error': 'relsen_id does not exist'}) relsen_name = jrelsen.get('relsen_name') or None if (relsen_name): rs.relsen_name = relsen_name relsen_type = jrelsen.get('relsen_type') or None if (relsen_type): rs.relsen_type = relsen_type room_name = jrelsen.get('room_name') or None if (room_name): rs.room_name = room_name room_type = jrelsen.get('room_type') or None if (room_type): rs.room_type = room_type group_name = jrelsen.get('group_name') or None if (group_name): rs.group_name = group_name dprint('incoming schedule: ', jrelsen.get('schedule')) schedule = jrelsen.get('schedule') if (schedule is None): # an empty array evaluates to False *** schedule = [] dprint('new schedule: ', schedule) dprint('updating schedule..') rs.schedule = json.dumps( {'schedule': schedule} ) # store it as a stringified json (so, the 'schedule' key is again needed!) rs.repeat = False if 'repeat' in jrelsen: rs.repeat = jrelsen.get('repeat') db.session.commit() enable_device( device_id ) # every time you configure a device, mark it enabled; this is useful for new devices that are onboarded in a disabled state *** if (schedule is not None and len(schedule) > 0): dprint('sending schedule update to Tasmota..') update_timer(device_id, relsen_id, jrelsen.get('schedule'), rs.repeat) return ({'result': 'successfully updated relsen'})
def get_latest_db_status(): dprint('Status class not implemented') return ({ 'place_holder': 'not implemented' }) # TODO: this is to get the last known status from the
def delete_test_db(current_user): dprint('{} is deleting the Hub database...'.format(current_user)) db.drop_all() return ({'result': 'Test DB removed'})