def handle_local_event_db_post(model, row, last_commit_field_changed_list=None): processed = False L.l.debug('Local DB change sent by model {} row {}'.format(model, row)) if str(models.Parameter) in str(model): # fixme: update app if params are changing to avoid need for restart processed = True # no need to propagate changes to other nodes elif str(models.Module) in str(model): if row.host_name == Constant.HOST_NAME: main.init_module(row.name, row.active) processed = True # propagate changes to all nodes as each must execute db sync or other commands locally # add here tables you are sure are safe to be propagated to all nodes elif str(models.Node) in str(model) or str(models.GpioPin) in str(model) \ or str(models.ZoneCustomRelay) in str(model) \ or str(models.Rule) in str(model) \ or str(models.ZoneThermostat) in str(model)\ or str(models.Pwm) in str(model): # or str(models.Area) in str(model) : txt = model_helper.model_row_to_json(row, operation='update') # execute all events directly first, then broadcast, as local events are not handled by remote mqtt queue obj_json = utils.json2obj(txt) obj_json[Constant. JSON_PUBLISH_FIELDS_CHANGED] = last_commit_field_changed_list # handle_event_mqtt_received(None, None, 'direct-event', obj_json) #_handle_internal_event(obj_json) _process_obj(obj_json) # mqtt_thread_run() transport.send_message_json(json=txt) processed = True
def on_models_committed(sender, changes): try: for obj, change in changes: # avoid recursion if isinstance(obj, models.Pwm): L.l.info("Commit change PWM={}".format(obj)) if hasattr(obj, Constant.JSON_PUBLISH_NOTIFY_TRANSPORT): # only send mqtt message once for db saves intended to be distributed if obj.notify_transport_enabled: if hasattr(obj, Constant.JSON_PUBLISH_NOTIFY_DB_COMMIT): if not obj.notified_on_db_commit: obj.notified_on_db_commit = True txt = model_helper.model_row_to_json( obj, operation=change) if txt is None: txt = model_helper.model_row_to_json( obj, operation=change) pass # execute all events directly first, # then broadcast, local events not handled by remote mqtt queue _handle_internal_event(utils.json2obj(txt)) # _process_obj(utils.json2obj(txt)) transport.send_message_json(json=txt) else: pass # send object to rule parser, if connected dispatcher.send(Constant.SIGNAL_DB_CHANGE_FOR_RULES, obj=obj, change=change) except Exception as ex: L.l.exception('Error in DB commit hook, {}'.format(ex))
def handle_local_event_db_post(model, row): processed = False L.l.debug('Local DB change sent by model {} row {}'.format(model, row)) if str(models.Parameter) in str(model): # fixme: update app if params are changing to avoid need for restart processed = True # no need to propagate changes to other nodes elif str(models.Module) in str(model): if row.host_name == Constant.HOST_NAME: main.init_module(row.name, row.active) processed = True # propagate changes to all nodes as each must execute db sync or other commands locally # add here tables you are sure are safe to be propagated to all nodes elif str(models.Node) in str(model) or str(models.GpioPin) in str(model) \ or str(models.ZoneCustomRelay) in str(model) \ or str(models.Rule) in str(model): # or str(models.Area) in str(model): txt = model_helper.model_row_to_json(row, operation='update') # execute all events directly first, then broadcast, as local events are not handled by remote mqtt queue handle_event_mqtt_received(None, None, 'direct-event', utils.json2obj(txt)) mqtt_thread_run() if transport.mqtt_io.client_connected: transport.send_message_json(json=txt) processed = True if processed: L.l.debug('Detected {} record change, row={}, trigger executed'.format( model, row)) else: L.l.debug( 'Detected {} record change, row={}, but change processing ignored'. format(model, row))
def init(): # use auto setup as described here: https://plot.ly/python/getting-started/ to avoid passwords in code # or less secure sign_in code below # py.sign_in(model_helper.get_param(constant.P_PLOTLY_USERNAME),model_helper.get_param(constant.P_PLOTLY_APIKEY)) username = "" if py.get_credentials()['username'] == '' or py.get_credentials( )['api_key'] == '': env_var = 'PLOTLY_CREDENTIALS_PATH' alt_path = os.environ.get(env_var) if not alt_path: L.l.info( 'Plotly config not in environment var: {}'.format(env_var)) env_var = 'OPENSHIFT_REPO_DIR' alt_path = os.environ.get(env_var) if alt_path is None: L.l.info( 'Plotly config not in environment var: {}'.format(env_var)) credential_file = model_helper.get_param( Constant.P_PLOTLY_ALTERNATE_CONFIG) alt_path = os.getcwd() + '/' + credential_file else: L.l.info('Plotly config found in environment var: {}, path={}'. format(env_var, alt_path)) alt_path = str(alt_path) + '/../data/.plotly.credentials' L.l.info("Plotly standard config empty, trying alt_path={}".format( alt_path)) try: with open(alt_path, 'r') as cred_file: data = cred_file.read().replace('\n', '') if len(data) > 0: cred_obj = utils.json2obj(data) username = cred_obj['username'] api_key = cred_obj['api_key'] if username and api_key: py.sign_in(username, api_key) global initialised initialised = True # else: # Log.logger.info("Plotly init from db folder config {}{} not ok, trying with db data".format(os.getcwd(), # credential_file)) # #alternate way if reading data from DB # py.sign_in(model_helper.get_param(constant.P_PLOTLY_USERNAME), # model_helper.get_param(constant.P_PLOTLY_APIKEY)) except Exception, ex: L.l.warning("error reading plotly credentials {}".format(ex))
def on_message(client, userdata, msg): json = msg try: if utils.get_base_location_now_date().minute != P.last_minute: P.last_minute = utils.get_base_location_now_date().minute transport.mqtt_io.mqtt_msg_count_per_minute = 0 transport.mqtt_io.mqtt_msg_count_per_minute += 1 P.last_rec = utils.get_base_location_now_date() #L.l.debug('Received from client [{}] userdata [{}] msg [{}] at {} '.format(client._client_id, # userdata, msg.topic, # utils.get_base_location_now_date())) # locate json string start = msg.payload.find('{') end = msg.payload.find('}') json = msg.payload[start:end + 1] if '"source_host_": "{}"'.format(Constant.HOST_NAME) not in json: # ignore messages send by this host x = json2obj(json) #if x[Constant.JSON_PUBLISH_SOURCE_HOST] != str(Constant.HOST_NAME): start = utils.get_base_location_now_date() dispatcher.send(signal=Constant.SIGNAL_MQTT_RECEIVED, client=client, userdata=userdata, topic=msg.topic, obj=x) elapsed = (utils.get_base_location_now_date() - start).total_seconds() if elapsed > 5: L.l.warning('Command received took {} seconds'.format(elapsed)) if False: if hasattr(x, 'command') and hasattr( x, 'command_id') and hasattr(x, 'host_target'): if x.host_target == Constant.HOST_NAME: L.l.info('Executing command {}'.format(x.command)) else: L.l.info( "Received command {} for other host {}".format( x, x.host_target)) except AttributeError as ex: L.l.warning('Unknown attribute error in msg {} err {}'.format( json, ex)) except ValueError as e: L.l.warning('Invalid JSON {} {}'.format(json, e))
def on_message(client, userdata, msg): if threading.current_thread().name != 'mqtt_loop': prctl.set_name("mqtt_loop") threading.current_thread().name = "mqtt_loop" start = utils.get_base_location_now_date() json = msg try: if utils.get_base_location_now_date().minute != P.last_minute: P.last_minute = utils.get_base_location_now_date().minute P.mqtt_msg_count_per_minute = 0 P.mqtt_msg_count_per_minute += 1 P.last_rec = utils.get_base_location_now_date() json = payload2json(msg.payload) # ignore messages sent by this host if '"source_host_": "{}"'.format(Constant.HOST_NAME) not in json \ and '"source_host": "{}"'.format(Constant.HOST_NAME) not in json \ and len(json) > 0: # or Constant.HOST_NAME == 'netbook': #debug x = utils.json2obj(json) if '_sent_on' in x: delta = (start - utils.parse_to_date(x['_sent_on'])).total_seconds() # L.l.info('Mqtt age={}'.format(delta)) if delta > 5: if 'source_host_' in x: host = x['source_host_'] else: host = 'N/A' L.l.info('Mqtt delta {} source_host='.format(delta, host)) x['is_event_external'] = True P.received_mqtt_list.append(x) else: pass except Exception as ex: L.l.warning('Unknown attribute error in msg {} err {}'.format( json, ex)) prctl.set_name("idle_mqtt_loop") threading.current_thread().name = "idle_mqtt_loop"
def mqtt_on_message(client, userdata, msg): # L.l.info("Topic={} payload={}".format(msg.topic, msg.payload)) if '/SENSOR' in msg.topic or '/RESULT' in msg.topic: topic_clean = P.sonoff_topic.replace('#', '') if topic_clean in msg.topic: sensor_name = msg.topic.split(topic_clean)[1].split('/')[1] obj = utils.json2obj(msg.payload) if 'ENERGY' in obj: energy = obj['ENERGY'] power = float(energy['Power']) if 'Voltage' in energy: voltage = int(energy['Voltage']) else: voltage = None if 'Factor' in energy: factor = energy['Factor'] else: factor = None if 'Current' in energy: current = float(energy['Current']) else: current = None # unit should match Utility unit name in models definition dispatcher.send(Constant.SIGNAL_UTILITY_EX, sensor_name=sensor_name, value=power, unit='watt') # todo: save total energy utility if voltage or factor or current: zone_sensor = models.ZoneSensor.query.filter_by(sensor_address=sensor_name).first() if zone_sensor is not None: current_record = models.Sensor.query.filter_by(address=sensor_name).first() if current_record is None: pass else: current_record.vad = None current_record.iad = None current_record.vdd = None record = models.Sensor(address=sensor_name, sensor_name=zone_sensor.sensor_name) record.is_event_external = True if voltage is not None: record.vad = round(voltage, 0) record.save_changed_fields(current_record=current_record, new_record=record, notify_transport_enabled=True, save_to_graph=True, debug=False) if current is not None: record.iad = round(current, 1) record.save_changed_fields(current_record=current_record, new_record=record, notify_transport_enabled=True, save_to_graph=True, debug=False) if factor is not None: record.vdd = round(factor, 1) record.save_changed_fields(current_record=current_record, new_record=record, notify_transport_enabled=True, save_to_graph=True, debug=False) # dispatcher.send(Constant.SIGNAL_UTILITY_EX, sensor_name=sensor_name, value=current, unit='kWh') elif 'POWER' in obj: power_is_on = obj['POWER'] == 'ON' current_relay = models.ZoneCustomRelay.query.filter_by(gpio_pin_code=sensor_name, gpio_host_name=Constant.HOST_NAME).first() if current_relay is not None: L.l.info("Got relay {} state={}".format(sensor_name, power_is_on)) new_relay = models.ZoneCustomRelay(gpio_pin_code=sensor_name, gpio_host_name=Constant.HOST_NAME) new_relay.relay_is_on = power_is_on current_relay.is_event_external = True models.ZoneCustomRelay().save_changed_fields(current_record=current_relay, new_record=new_relay, notify_transport_enabled=True, save_to_graph=True) else: L.l.error("ZoneCustomRelay with code {} does not exist in database".format(sensor_name)) elif 'COUNTER' in obj: # TelePeriod 60 counter = obj['COUNTER'] for i in [1, 2, 3, 4]: c = 'C{}'.format(i) if c in counter: cval = int(counter[c]) dispatcher.send(Constant.SIGNAL_UTILITY_EX, sensor_name=sensor_name, value=cval, index=i) elif 'BMP280' in obj: # iot/sonoff/tele/sonoff-basic-3/SENSOR = # {"Time":"2018-10-28T08:12:26","BMP280":{"Temperature":24.6,"Pressure":971.0},"TempUnit":"C"} bmp = obj['BMP280'] temp = bmp['Temperature'] press = bmp['Pressure'] sensor_address = '{}_{}'.format(sensor_name, 'bmp280') current_zone_sensor = models.ZoneSensor.query.filter_by(sensor_address=sensor_address).first() if current_zone_sensor is not None: current_sensor = models.Sensor.query.filter_by(address=sensor_address).first() sensor = models.Sensor(address=sensor_address, sensor_name=current_zone_sensor.sensor_name) sensor.temperature = temp sensor.pressure = press sensor.save_changed_fields( current_record=current_sensor, notify_transport_enabled=True, save_to_graph=True) else: L.l.info('Undefined sensor found in {}, value={}'.format(sensor_address, bmp)) else: L.l.warning("Usefull payload missing from topic {} payload={}".format(msg.topic, msg.payload)) else: L.l.warning("Invalid sensor topic {}".format(msg.topic))
def __update_ddns_rackspace(): try: ConfigFile = get_json_param(Constant.P_DDNS_RACKSPACE_CONFIG_FILE) with open(ConfigFile, 'r') as f: config_list = json.load(f) global cache if cache == {} or cache is None: cache = {} cache['auth'] = {} cache['auth']['expires'] = str(utils.get_base_location_now_date()) config = {} ip_json_test = '' try: #ip_json_test = requests.get('http://ip-api.com/json').text ip_json_test = requests.get('http://ipinfo.io').text ip_json_obj = utils.json2obj(ip_json_test) #public_ip = ip_json_obj['query'] #public_isp = ip_json_obj['isp'] public_ip = ip_json_obj['ip'] public_isp = ip_json_obj['org'] except Exception as ex: L.l.warning('Unable to get my ip, err={} text={}'.format( ex, ip_json_test)) return for config in config_list.values(): # check if public address is for this config dns entry isp = config['isp'] if isp != public_isp: continue # get IP address try: cache['ip:' + isp] = socket.gethostbyname( config['record_name']) except Exception as ex: cache['ip:' + isp] = None L.l.warning('Unable to get ip for host {}, err={}'.format( config['record_name'], ex)) if public_ip == '' or public_ip is None or public_ip == cache[ 'ip:' + isp]: L.l.debug('IP address for ' + isp + ' is still ' + public_ip + '; nothing to update.') return else: L.l.info('IP address was changed for {}, old was {} new is {}'. format(isp, cache['ip:' + isp], public_ip)) cache['ip:' + isp] = public_ip now = utils.get_base_location_now_date() expires = parser.parse(cache['auth']['expires']) now = pytz.utc.localize(now) expires = pytz.utc.localize(expires) if expires <= now: L.l.info( 'Expired rackspace authentication token; reauthenticating...' ) # authenticate with Rackspace authUrl = 'https://identity.api.rackspacecloud.com/v2.0/tokens' authData = { 'auth': { 'RAX-KSKEY:apiKeyCredentials': { 'username': config['username'], 'apiKey': config['api_key'] } } } authHeaders = { 'Accept': 'application/json', 'Content-type': 'application/json' } auth = requests.post(authUrl, data=json.dumps(authData), headers=authHeaders) auth = utils.json2obj(auth.text) cache['auth']['expires'] = auth['access']['token']['expires'] cache['auth']['token'] = auth['access']['token']['id'] # update DNS record url = 'https://dns.api.rackspacecloud.com/v1.0/' + config['account_id'] + \ '/domains/' + config['domain_id'] + '/records/' + config['record_id'] data = { 'ttl': config['record_ttl'], 'name': config['record_name'], 'data': public_ip } headers = { 'Accept': 'application/json', 'Content-type': 'application/json', 'X-Auth-Token': cache['auth']['token'] } result = requests.put(url, data=json.dumps(data), headers=headers) if result.ok: L.l.info('Updated IP address for {} to {}'.format( config['record_name'], public_ip)) else: L.l.warning('Unable to update IP, response={}'.format(result)) except Exception as ex: L.l.warning('Unable to check and update dns, err={}'.format(ex))
def _process_message(msg): # L.l.info("Topic={} payload={}".format(msg.topic, msg.payload)) if '/SENSOR' in msg.topic or '/RESULT' in msg.topic: topic_clean = P.sonoff_topic.replace('#', '') if topic_clean in msg.topic: try: sensor_name = msg.topic.split(topic_clean)[1].split('/')[1] obj = utils.json2obj(transport.mqtt_io.payload2json(msg.payload)) except Exception as ex: L.l.error("Error decoding sensor name, topic={} payload={}".format(msg.topic, msg.payload)) return False if obj is None: L.l.error("Unable to decode tasmota {}".format(msg.payload)) return False ################################################################# if 'ENERGY' in obj: energy = obj['ENERGY'] power = float(energy['Power']) if 'Voltage' in energy: voltage = int(energy['Voltage']) else: voltage = None if 'Factor' in energy: factor = energy['Factor'] else: factor = None if 'Current' in energy: current = float(energy['Current']) else: current = None if 'Today' in energy: today_energy = energy['Today'] else: today_energy = None # unit should match Utility unit name in models definition dispatcher.send(Constant.SIGNAL_UTILITY_EX, sensor_name=sensor_name, value=power, unit='watt') # todo: save total energy utility if voltage or factor or current: zone_sensor = m.ZoneSensor.find_one({m.ZoneSensor.sensor_address: sensor_name}) if zone_sensor is not None: record = m.Sensor.find_one({m.Sensor.address: sensor_name}) if record is None: record = m.Sensor() record.address = sensor_name record.sensor_name = zone_sensor.sensor_name record.vad = None record.iad = None record.vdd = None if voltage is not None: record.vad = round(voltage, 0) if current is not None: record.iad = round(current, 1) if factor is not None: record.vdd = round(factor, 1) if voltage is not None or current is not None or factor is not None: record.save_changed_fields(broadcast=False, persist=True) # dispatcher.send(Constant.SIGNAL_UTILITY_EX, sensor_name=sensor_name, value=current, unit='kWh') # check for single relay if 'POWER' in obj: power_is_on = obj['POWER'] == 'ON' # relay = m.ZoneCustomRelay.find_one({m.ZoneCustomRelay.gpio_pin_code: sensor_name, # m.ZoneCustomRelay.gpio_host_name: Constant.HOST_NAME}) relay = m.ZoneCustomRelay.find_one({m.ZoneCustomRelay.gpio_pin_code: sensor_name}) if relay is not None: L.l.info("Got single relay {} state={}".format(sensor_name, power_is_on)) relay.relay_is_on = power_is_on # set listeners to false as otherwise on reboot sonoff relays that were on will power- cycle relay.save_changed_fields(broadcast=False, persist=True, listeners=False) else: L.l.warning("ZoneCustomRelay single {} not defined in db".format(sensor_name)) # check for multiple relays multiple_relays_list = [] if 'POWER1' in obj: multiple_relays_list.append(1) if 'POWER2' in obj: multiple_relays_list.append(2) if len(multiple_relays_list) > 0: for index in multiple_relays_list: power_is_on = obj['POWER' + str(index)] == 'ON' relay = m.ZoneCustomRelay.find_one({m.ZoneCustomRelay.gpio_pin_code: sensor_name, m.ZoneCustomRelay.relay_index: index}) if relay is not None: L.l.info("Got multiple relay {} state={}".format(sensor_name, power_is_on)) relay.relay_is_on = power_is_on # set listeners to false as otherwise on reboot sonoff relays that were on will power- cycle relay.save_changed_fields(broadcast=False, persist=True, listeners=False) else: L.l.warning("ZoneCustomRelay multiple {} not defined in db".format(sensor_name)) if 'COUNTER' in obj: # TelePeriod 60 counter = obj['COUNTER'] for i in [1, 2, 3, 4, 5, 6, 7, 8]: c = 'C{}'.format(i) if c in counter: cval = int(counter[c]) dispatcher.send(Constant.SIGNAL_UTILITY_EX, sensor_name=sensor_name, value=cval, index=i) # iot/sonoff/tele/sonoff-basic-3/SENSOR = # {"Time":"2018-10-28T08:12:26","BMP280":{"Temperature":24.6,"Pressure":971.0},"TempUnit":"C"} # "BME280":{"Temperature":24.1,"Humidity":39.2,"Pressure":980.0},"PressureUnit":"hPa","TempUnit":"C"} # {"BME680":{"Temperature":29.0,"Humidity":63.3,"Pressure":981.6,"Gas":24.46},"PressureUnit":"hPa","TempUnit":"C"} # "MHZ19B":{"Model":"B","CarbonDioxide":473,"Temperature":26.0},"TempUnit":"C" for k, v in obj.items(): if k.startswith('BME') or k.startswith('BMP') or k.startswith('MHZ19') or k.startswith('DS18B20'): sensor_address = '{}_{}'.format(sensor_name, k.lower()) zone_sensor, sensor = _get_air_sensor(sensor_address=sensor_address, sensor_type=k) if 'Temperature' in v: sensor.temperature = v['Temperature'] if 'Pressure' in v: sensor.pressure = v['Pressure'] if 'Humidity' in v: if 0 < v['Humidity'] < 100: sensor.humidity = v['Humidity'] if 'Gas' in v: sensor.gas = v['Gas'] if 'CarbonDioxide' in v: val = v['CarbonDioxide'] if val > 0: sensor.co2 = val sensor.save_changed_fields(broadcast=False, persist=True) if k.startswith('INA219'): ina = v # obj['INA219'] # multiple ina sensors if '-' in k: index = k.split('-')[1] sensor = m.PowerMonitor.find_one( {m.PowerMonitor.host_name: sensor_name, m.PowerMonitor.type: "ina{}".format(index)}) else: sensor = m.PowerMonitor.find_one({m.PowerMonitor.host_name: sensor_name}) if sensor is None: L.l.warning('Sensor INA on {} not defined in db'.format(sensor_name)) else: if 'Voltage' in ina: voltage = ina['Voltage'] sensor.voltage = voltage if 'Current' in ina: current = ina['Current'] sensor.current = current if 'Power' in ina: power = ina['Power'] sensor.power = power sensor.save_changed_fields(broadcast=False, persist=True) if 'ANALOG' in obj: # "ANALOG":{"A0":7} an = obj['ANALOG'] a0 = an['A0'] sensor_address = '{}_{}'.format(sensor_name, 'a0') zone_sensor, sensor = _get_sensor(sensor_address=sensor_address, sensor_type='ANALOG') sensor.vad = a0 sensor.save_changed_fields(broadcast=False, persist=True) if 'PMS5003' in obj: # "PMS5003":{"CF1":0,"CF2.5":1,"CF10":3,"PM1":0,"PM2.5":1,"PM10":3,"PB0.3":444,"PB0.5":120,"PB1":12, # "PB2.5":6,"PB5":2,"PB10":2} pms = obj['PMS5003'] sensor_address = '{}_{}'.format(sensor_name, 'pms5003') zone_sensor, sensor = _get_dust_sensor(sensor_address=sensor_address, sensor_type='PMS5003') sensor.pm_1 = pms['PM1'] sensor.pm_2_5 = pms['PM2.5'] sensor.pm_10 = pms['PM10'] sensor.p_0_3 = pms['PB0.3'] sensor.p_0_5 = pms['PB0.5'] sensor.p_1 = pms['PB1'] sensor.p_2_5 = pms['PB2.5'] sensor.p_5 = pms['PB5'] sensor.p_10 = pms['PB10'] # sometimes first read after power on returns invalid 0 values if sensor.pm_1 + sensor.pm_2_5 + sensor.pm_10 + sensor.p_0_3 + sensor.p_0_5 + sensor.p_1 \ + sensor.p_2_5 + sensor.p_5 + sensor.p_10 != 0: sensor.save_changed_fields(broadcast=False, persist=True) if 'RfReceived' in obj: rf = obj['RfReceived'] sensor_id = rf['Data'] alarm = m.ZoneAlarm.find_one({m.ZoneAlarm.gpio_pin_code: sensor_id}) if alarm is not None: dispatcher.send(Constant.SIGNAL_GPIO, gpio_pin_code=sensor_id, pin_connected=False) dispatcher.send(Constant.SIGNAL_GPIO, gpio_pin_code=sensor_id, pin_connected=True) else: L.l.warning('Unknown Sonoff RF packet received {}'.format(sensor_id)) else: L.l.warning("Invalid sensor topic {}".format(msg.topic)) return True