class openhab_mqtt_protocol: processcnt = 1 def __init__(self): self._log = core._log self._log.debug("Protocol: openhab mqtt contruction") self._lock = Event() # release lock, ready for next loop self._lock.clear() def init(self, protocol): self._log.debug("Protocol " + name + ": Init") self._client_id = protocol['client_id'] self._server = protocol['hostname'] self._port = protocol['port'] self._user = protocol['user'] self._password = protocol['password'] self._queue_out1 = {} self._queue_out2 = {} self._queue_out3 = {} self._queue_out = protocol[ 'publish'] #### was commented out AJ, now back in self._pubstr = protocol['publish'] #### added AJ self._queue_in = protocol['subscribe'] self._mq = MQTTClient(self._client_id, self._server, self._port, self._user, self._password) # Print diagnostic messages when retries/reconnects happens self._mq.DEBUG = True self._queue = queues.Queue(maxsize=100) return self._queue def connect(self): self._log.debug("Protocol: " + name + ": connect") return self._mq.reconnect() def disconnect(self): self._log.debug("Protocol: " + name + ": disconnect") self._mq.disconnect() def check(self): self._log.debug("Protocol: " + name + ": check") self._mq.check_msg() def status(self): self._log.debug("Protocol: " + name + ": status") self._mq.ping() def recieve(self): self._log.debug("Protocol: " + name + ": recieve") self._mq.subscribe(self.queue_in) def send(self, devicedata): self._log.debug("Protocol: " + name + ": send " + devicedata["stype"]) # connect or reconnect to mqtt server self.connect() mqttdata1 = None mqttdata2 = None mqttdata3 = None # case - all sensor types while True: mqttdata1 = None # looks like duplication of above! mqttdata1 = {} mqttdata2 = {} mqttdata3 = {} self._queue_out1 = '' self._queue_out2 = '' self._queue_out3 = '' message1 = '' message2 = '' message3 = '' # Get next plugin datavalues from utils.py, plugin_senddata(self, queuedata) try: devicedata['unitname'] = self._queue.get_nowait() devicedata['devicename'] = self._queue.get_nowait() except Exception as e: self._log.debug("Protocol: " + name + " SENSOR_TYPE_SINGLE exception: " + repr(e)) break # case SENSOR_TYPE_SINGLE if devicedata["stype"] == core.SENSOR_TYPE_SINGLE: # get plugin values devicedata['valueV1'] = self._queue.get_nowait() devicedata['valueN1'] = self._queue.get_nowait() # Assemble mqtt message mqttdata1 = {} mqttdata1['topic'] = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN1'] mqttdata1['msg'] = str(devicedata["valueV1"]) message1 = str(devicedata["valueV1"]) break # case SENSOR_TYPE_LONG if devicedata["stype"] == core.SENSOR_TYPE_LONG: self._log.debug("Protocol: " + name + ": SENSOR_TYPE_LONG") break # case SENSOR_TYPE_DUAL if devicedata["stype"] == core.SENSOR_TYPE_DUAL: self._log.debug("Protocol: " + name + ": SENSOR_TYPE_DUAL") break # case SENSOR_TYPE_TEMP_HUM if devicedata["stype"] == core.SENSOR_TYPE_TEMP_HUM: self._log.debug("Protocol: " + name + ": SENSOR_TYPE_TEMP_HUM") # Get plugin values try: devicedata['valueV1'] = self._queue.get_nowait() devicedata['valueN1'] = self._queue.get_nowait() devicedata['valueV2'] = self._queue.get_nowait() devicedata['valueN2'] = self._queue.get_nowait() except Exception as e: self._log.debug("Protocol: " + self._name + " SENSOR_TYPE_TEMP_HUM Exception: " + repr(e)) break # Assemble mqtt messages mqttdata1 = {} mqttdata1['topic'] = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN1'] mqttdata1['msg'] = str(devicedata["valueV1"]) message1 = str(devicedata["valueV1"]) mqttdata2 = {} mqttdata2['topic'] = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN2'] mqttdata2['msg'] = str(devicedata["valueV2"]) message1 = str(devicedata["valueV2"]) break # case SENSOR_TYPE_TEMP_BARO if devicedata["stype"] == core.SENSOR_TYPE_TEMP_BARO: self._log.debug("Protocol: " + name + ": SENSOR_TYPE_TEMP_BARO") break # case SENSOR_TYPE_TEMP_HUM_BARO if devicedata["stype"] == core.SENSOR_TYPE_TEMP_HUM_BARO: #self._log.debug("Protocol: "+name+": SENSOR_TYPE_TEMP_HUM_BARO") # Get plugin values try: devicedata['valueV1'] = self._queue.get_nowait() devicedata['valueN1'] = self._queue.get_nowait() devicedata['valueV2'] = self._queue.get_nowait() devicedata['valueN2'] = self._queue.get_nowait() devicedata['valueV3'] = self._queue.get_nowait() devicedata['valueN3'] = self._queue.get_nowait() except Exception as e: self._log.debug("Protocol: " + self._name + " SENSOR_TYPE_TEMP_HUM_BARO Exception: " + repr(e)) break # Assemble mqtt topics for valueV1, V2, V3 mqttdata1 = {} mqttdata1['topic'] = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN1'] mqttdata1['msg'] = str(devicedata["valueV1"]) message1 = str(devicedata["valueV1"]) mqttdata2 = {} mqttdata2['topic'] = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN2'] mqttdata2['msg'] = str(devicedata["valueV2"]) message2 = str(devicedata["valueV2"]) mqttdata3 = {} mqttdata3['topic'] = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN3'] mqttdata3['msg'] = str(devicedata["valueV3"]) message3 = str(devicedata["valueV3"]) break # case SENSOR_TYPE_SWITCH if devicedata["stype"] == core.SENSOR_TYPE_SWITCH: self._log.debug("Protocol: " + name + ": SENSOR_TYPE_SWITCH") # Get plugin values try: devicedata['valueV1'] = self._queue.get_nowait() devicedata['valueN1'] = self._queue.get_nowait() except Exception as e: self._log.debug("Protocol: " + self._name + " SENSOR_TYPE_SWITCH Exception: " + repr(e)) break # Switches can have many values, OpenHAB (usually) only two: 1 (=on) or 0 (=off) switch_on = ['closed', 'press', 'double', 'long', 'on'] switch_off = ['open', 'release', 'off'] if devicedata["valueV1"] in switch_on: devicedata["valueV1"] = 1 elif devicedata["valueV1"] in switch_off: devicedata["valueV1"] = 0 else: break # Assemble mqtt message mqttdata1 = {} mqttdata1['topic'] = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN1'] mqttdata1['msg'] = str(devicedata["valueV1"]) message1 = str(devicedata["valueV1"]) break # case SENSOR_TYPE_DIMMER if devicedata["stype"] == core.SENSOR_TYPE_DIMMER: self._log.debug("Protocol: " + name + ": SENSOR_TYPE_DIMMER") break # case SENSOR_TYPE_WIND if devicedata["stype"] == core.SENSOR_TYPE_WIND: self._log.debug("Protocol: " + name + ": SENSOR_TYPE_WIND") break # else UNKNOWN self._log.debug("Protocol " + name + ": Unknown sensor type!") break # Now publish the data to the MQTT broker/server # test for a user entry in webform protocol 'Publish' field; use it if it exists if self._pubstr != '': # entry exists in Publish field self._queue_out1 = self._pubstr self._queue_out2 = self._pubstr self._queue_out3 = self._pubstr else: # use "standard" format (unitname/devicename/valuename)... self._log.debug('Protocol: ' + name + ': "standard" topic format') self._queue_out1 = str(mqttdata1['topic']) if devicedata.get('valueN2') != None: self._queue_out2 = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN2'] if devicedata.get('valueN3') != None: self._queue_out3 = devicedata['unitname'] + "/" + devicedata[ 'devicename'] + "/" + devicedata['valueN3'] # Whichever the sensor type, check if we have mqtt data to send, and if so publish it # publish datavalue1... if message1 != None: self._log.debug("Protocol: " + name + " Publish: Topic: " + self._queue_out1 + ", Message: " + message1) self._mq.publish(self._queue_out1, message1) # publish datavalue2 (if it exists) if devicedata.get('valueN2') != None: if message2 != None: self._log.debug("Protocol: " + name + " Publish: Topic: " + self._queue_out2 + ", Message: " + message2) self._mq.publish(self._queue_out2, message2) # publish datavalue3 (if it exists) if devicedata.get('valueN3') != None: if mqttdata3['msg'] != None: self._log.debug("Protocol: " + name + " Publish: Topic: " + self._queue_out3 + ", Message: " + message3) self._mq.publish(self._queue_out3, message3) # we may eventually need more, for example for Dummy device (4 values)... # End of send # def process(self): # processing todo for protocol (main loop of protocol) self._log.debug("Protocol: " + name + " Processing...") devicedata = {} try: while True: message1 = self._queue.get_nowait( ) # keep reading from the protocol queue if message1 == core.QUEUE_MESSAGE_START: # found "start" message break # ready to read devicedata values devicedata['stype'] = self._queue.get_nowait() # get sensor type devicedata['serverid'] = self._queue.get_nowait() # get server id #print("OHmqtt l 266: devicedata = ", devicedata) self.send( devicedata ) # go and get other datavalues as needed, and publish them to MQTT ... except Exception as e: self._log.debug("Protocol: " + name + " process Exception: " + repr(e)) # release lock, ready for next processing self._lock.clear()
class IoTCClient(): def __init__(self, id_scope, device_id, credentials_type: IoTCConnectType, credentials, logger=None, storage=None): self._device_id = device_id self._id_scope = id_scope self._credentials_type = credentials_type self._content_type = 'application%2Fjson' self._content_encoding = 'utf-8' self._connected = False self._credentials = credentials self._storage = storage self._events = {} self._model_id = None if logger is not None: self._logger = logger else: self._logger = ConsoleLogger(IoTCLogLevel.API_ONLY) self._twin_request_id = time() def set_content_type(self, content_type): self._content_type = encode_uri_component(content_type) def set_content_encoding(self, content_encoding): self._content_encoding = content_encoding def set_log_level(self, log_level: IoTCLogLevel): self._logger.set_log_level(log_level) def _on_message(self, topic, message): topic = topic.decode('utf-8') self._logger.debug(topic) if topic == HubTopics.TWIN_RES.format(200, self._twin_request_id): self._logger.info('Received twin: {}'.format(message)) if topic.startswith(HubTopics.PROPERTIES): # desired properties self._logger.info( 'Received desired property message: {}'.format(message)) message = json.loads(message.decode('utf-8')) self.on_properties_update(message) elif topic.startswith(HubTopics.COMMANDS): # commands match = self._commands_regex.match(topic) if match is not None: if all(m is not None for m in [match.group(1), match.group(2)]): command_name = match.group(1) command_req = match.group(2) command = Command(command_name, message) try: command_name_with_components = command_name.split("*") if len(command_name_with_components) > 1: # In a component self._logger.debug("Command in a component") command = Command( command_name_with_components[1], message, component_name=command_name_with_components[0], ) def reply_fn(): self._logger.debug( 'Acknowledging command {}'.format( command.name)) self._mqtt_client.publish( '$iothub/methods/res/{}/?$rid={}'.format( 200, command_req).encode('utf-8'), '') if command.component_name is not None: self.send_property({ "{}".format(command.component_name): { "{}".format(command.name): { "value": command.value, "requestId": command_req } } }) else: self.send_property({ "{}".format(command.name): { "value": command.value, "requestId": command_req } }) command.reply = reply_fn self._on_commands(command) sleep(0.1) except: pass elif topic.startswith( HubTopics.ENQUEUED_COMMANDS.format(self._device_id)): params = topic.split( "devices/{}/messages/devicebound/".format(self._device_id), 1)[1].split('&') for param in params: p = param.split('=') if p[0] == "method-name": command_name = decode_uri_component(p[1]) command = Command(command_name, message) try: command_name_with_components = command_name.split("*") if len(command_name_with_components) > 1: # In a component self._logger.debug("Command in a component") command = Command( command_name_with_components[1], message, component_name=command_name_with_components[0], ) except: pass self._logger.debug( 'Received enqueued command {} with message: {}'.format( command.name, command.value)) self._on_enqueued_commands(command) def connect(self, force_dps=False): creds = None if force_dps: self._logger.info("Refreshing credentials...") if self._storage is not None and force_dps is False: creds = self._storage.retrieve() if creds is None: prov = ProvisioningClient(self._id_scope, self._device_id, self._credentials_type, self._credentials, self._logger, self._model_id) creds = prov.register() self._mqtt_client = MQTTClient(self._device_id, creds.host, 8883, creds.user, creds.password, ssl=True, keepalive=60) self._commands_regex = ure.compile( '\$iothub\/methods\/POST\/(.+)\/\?\$rid=(.+)') try: self._mqtt_client.connect(False) self._connected = True self._logger.info('Device connected!') if self._storage: self._storage.persist(creds) self._mqtt_client.set_callback(self._on_message) self._mqtt_client.subscribe(HubTopics.TWIN) self._mqtt_client.subscribe('{}/#'.format(HubTopics.PROPERTIES)) self._mqtt_client.subscribe('{}/#'.format(HubTopics.COMMANDS)) self._mqtt_client.subscribe('{}/#'.format( HubTopics.ENQUEUED_COMMANDS.format(self._device_id))) self._logger.debug(self._twin_request_id) self._mqtt_client.publish( HubTopics.TWIN_REQ.format( self._twin_request_id).encode('utf-8'), '{{}}') except: self._logger.info("ERROR: Failed to connect to Hub") if force_dps is True: exit(1) self.connect(True) def is_connected(self): if self._connected == True: return True return False def set_model_id(self, model): self._model_id = model def send_property(self, payload): self._logger.debug('Sending properties {}'.format(json.dumps(payload))) self._mqtt_client.publish( HubTopics.PROP_REPORT.format(time()).encode('utf-8'), json.dumps(payload)) def send_telemetry(self, payload, properties=None): topic = 'devices/{}/messages/events/?$.ct={}&$.ce={}'.format( self._device_id, self._content_type, self._content_encoding) if properties is not None: for prop in properties: topic += '&{}={}'.format(prop, properties[prop]) self._mqtt_client.publish(topic.encode('utf-8'), json.dumps(payload).encode('utf-8')) def on(self, event, callback): self._events[event] = callback def listen(self): if not self.is_connected(): return self._mqtt_client.ping() self._mqtt_client.wait_msg() sleep(0.5) def _handle_property_ack( self, callback, property_name, property_value, property_version, component_name=None, ): if callback is not None: ret = callback(property_name, property_value, component_name) else: ret = True if ret: if component_name is not None: self._logger.debug("Acknowledging {}".format(property_name)) self.send_property({ "{}".format(component_name): { "{}".format(property_name): { "ac": 200, "ad": "Property received", "av": property_version, "value": property_value, } } }) else: self._logger.debug("Acknowledging {}".format(property_name)) self.send_property({ "{}".format(property_name): { "ac": 200, "ad": "Property received", "av": property_version, "value": property_value, } }) else: self._logger.debug( 'Property "{}" unsuccessfully processed'.format(property_name)) def on_properties_update(self, patch): try: prop_cb = self._events[IoTCEvents.PROPERTIES] except: return # Set component at false by default is_component = False for prop in patch: is_component = False if prop == "$version": continue # check if component try: is_component = patch[prop]["__t"] except KeyError: pass if is_component: for component_prop in patch[prop]: if component_prop == "__t": continue self._logger.debug( 'In component "{}" for property "{}"'.format( prop, component_prop)) self._handle_property_ack( prop_cb, component_prop, patch[prop][component_prop]["value"], patch["$version"], prop, ) else: self._handle_property_ack(prop_cb, prop, patch[prop]["value"], patch["$version"]) def _cmd_resp(self, command: Command, value): self._logger.debug('Responding to command "{}" request'.format( command.name)) self.send_property({ '{}'.format(command.name): { 'value': value, 'requestId': command.request_id } }) def _on_commands(self, command: Command): try: cmd_cb = self._events[IoTCEvents.COMMANDS] except KeyError: return self._logger.debug('Received command {}'.format(command.name)) cmd_cb(command) def _on_enqueued_commands(self, command: Command): try: cmd_cb = self._events[IoTCEvents.ENQUEUED_COMMANDS] except KeyError: return self._logger.debug('Received enqueued command {}'.format(command.name)) cmd_cb(command)
class Service(BaseService): # Setup def __init__(self): super().__init__() self.mqtt = None self._log_stream = None self._asyncio_loop = asyncio.get_event_loop() self._services = {} self._init_mqtt() self._mqtt_connect() self._init_logging() self._get_updates() self._init_services() self.start_all_services() self._asyncio_loop.create_task(self._update_ntp()) def _init_mqtt(self): MQTT_USER = env['MQTT_USER'] if 'MQTT_USER' in env.keys() else None MQTT_PASSWORD = env['MQTT_PASSWORD'] if 'MQTT_PASSWORD' in env.keys( ) else None # Create an mqtt client self.mqtt = MQTTClient(self.hardware_id, env['MQTT_HOST'], user=MQTT_USER, password=MQTT_PASSWORD) self.mqtt.set_callback(self._mqtt_callback) def _init_logging(self): LOG_LOCALLY = env['LOG_LOCALLY'] if 'LOG_LOCALLY' in env.keys( ) else True self._log_stream = MQTTStream(self.mqtt, self.hardware_id, LOG_LOCALLY) self._asyncio_loop.create_task(self._log_stream._process_log_queue()) # Set the log level based on the global environment variable 'LOG_LEVEL' log_level_string = env['LOG_LEVEL'] if 'LOG_LEVEL' in env.keys( ) else 'DEBUG' # Convert the log level `string` to the right enum LOG_LEVEL = logging.DEBUG for x in ['DEBUG', 'INFO', 'WARNING', 'ERROR']: if x == log_level_string: LOG_LEVEL = eval('logging.%s' % x) break # Make this the default log stream logging.basicConfig(level=LOG_LEVEL, stream=self._log_stream) self._asyncio_loop.create_task(self._process_mqtt_messages()) @staticmethod def _mqtt_callback(topic, message): topic_path = topic.decode('utf-8').split('/')[1:] service, channel = topic_path # Command router if channel == 'commands': message = json.loads(message) _command_queue.append((service, message)) async def _process_mqtt_messages(self): while True: try: self.mqtt.check_msg() except: pass await asyncio.sleep(0.1) while len(_command_queue): service, message = _command_queue.pop(0) args = message['args'] if 'args' in message.keys() else '' command = 'service.%s(%s)' % (message['command'], args) # Include `command`, `token`, and `args`, in the response response = { 'command': message['command'], 'token': message['token'], 'args': args } if message['command'] in self._services[service]._methods: try: response['response'] = eval( command, globals(), {'service': self._services[service]}) except Exception as e: response['exception'] = repr(e) self._logger.error( 'Remote command ("%s") caused an exception.' % command) sys.print_exception(e, self._log_stream) else: response[ 'exception'] = 'The specified command is not available.' self._logger.error( 'Remote command ("%s") caused an exception.' % command) self.mqtt.publish( '%s/%s/responses' % (self.hardware_id, service), json.dumps(response)) gc.collect() async def _update_ntp(self): def update(): global _startup_time try: self._logger.info('Get NTP time') lock = _thread.allocate_lock() with lock: _startup_time = ntptime.time() - time.time() self._logger.info('_startup_time=%s' % _startup_time) except Exception as e: self._logger.warning(e) sys.print_exception(e, self._log_stream) # Try every 10 s until we get an update while not _startup_time: update() await asyncio.sleep(10) gc.collect() # Afterwords, sync once per day while True: await asyncio.sleep(60 * 60 * 24) update() gc.collect() @requires_network def _wifi_connect(self): pass @requires_network def _mqtt_connect(self): self.mqtt.connect() self.mqtt.subscribe('%s/#' % self.hardware_id) @requires_network def _get_updates(self): reboot_flag = False # Get a list of all services for service in os.listdir('services'): if service == '__init__.py' or service.startswith('.'): continue self._logger.info('Check for updates to %s' % service) service_env = get_env(service) if 'GITHUB_URL' in service_env.keys(): self._logger.info('GITHUB_URL=%s' % service_env['GITHUB_URL']) remote_module_path = service_env[ 'PYTHON_MODULE_PATH'] if 'PYTHON_MODULE_PATH' in service_env else '' o = OTAUpdater(service_env['GITHUB_URL'], module_path='services/%s' % service, remote_module_path=remote_module_path) try: gc.collect() if o.check_for_update_to_install_during_next_reboot(): gc.collect() o.download_and_install_update_if_available() reboot_flag = True gc.collect() except Exception as e: self._logger.error("Couldn't get update info. %s" % repr(e)) sys.print_exception(e, self._log_stream) else: self._logger.error('No env defined for %s' % self.name) sys.print_exception(e, self._log_stream) if reboot_flag: self._logger.info('Updates installed. Rebooting...') machine.reset() def _init_services(self): self._logger.info('root environment = %s' % (json.dumps(self.get_env()))) # Get a list of all services for service in os.listdir('services'): if service == '__init__.py' or service.startswith('.'): continue try: if service == 'supervisor': self._services[service] = self else: # Create new service exec('import %s' % service, locals()) self._services[service] = locals()[service].Service() self._logger.info('Initialized %s %s' % (self._services[service].name, self._services[service].version)) service_env = self.get_env(service) self._logger.info('%s environment = %s' % (service, json.dumps(service_env))) except Exception as e: self._logger.error('Failed to initialize %s: %s' % (service, repr(e))) sys.print_exception(e, self._log_stream) self._logger.info('Start asyncio background thread.') # Start the asyncio loop in a background thread _thread.start_new_thread(self._asyncio_loop.run_forever, tuple()) @property def status(self): return { name: (service.state, service.version) for name, service in self._services.items() } def reset(self): machine.reset() def stop_all_services(self): for service in self._services.values(): service.stop() def start_all_services(self): for service in self._services.values(): service.start() # This function runs continuously async def loop(self): self._logger.debug('state=%s' % self.state) # Keep wifi and mqtt connections alive try: self.mqtt.ping() except: # _mqtt_connect() requires wifi, so this will also reconnect wifi # if necessary self._mqtt_connect() gc.collect() self._logger.info('gc.mem_free()=%s' % gc.mem_free()) await asyncio.sleep(60)
class Service(BaseService): # Setup the fan to use a PWM frequency of 25kHz and a 50% duty cycle. fan = machine.PWM(machine.Pin(19, machine.Pin.OUT), freq=25000, duty=int(0.5*1023)) ds = ds18x20.DS18X20(onewire.OneWire(machine.Pin(32))) onewire_addresses = {'temp_in': None, 'temp_out': bytearray(b'(\xd6G\x8a\x01\x00\x00\xb9'), 'temp_panel': bytearray(b'(\xd6ay\x97\t\x03\x8d') } # Setup def __init__(self): super().__init__() self.wifi = network.WLAN(network.STA_IF) MQTT_USER = self.env['MQTT_USER'] if 'MQTT_USER' in self.env.keys() else None MQTT_PASSWORD = self.env['MQTT_PASSWORD'] if 'MQTT_PASSWORD' in self.env.keys() else None self.mqtt = MQTTClient(self.hardware_id, self.env['MQTT_HOST'], user=MQTT_USER, password=MQTT_PASSWORD) self._asyncio_loop.create_task(self._blynk_event_loop()) self._asyncio_loop.create_task(self._maintain_connections(30)) self._asyncio_loop.create_task(self._update_sensors(10)) self._logger.info("Scanning onewire bus...") self._logger.info(str(self.ds.scan())) def mqtt_connect(self): self.mqtt.connect() @classmethod def set_fan_duty_cycle(cls, value): duty_cycle = int(float(value) / 100 * 1023) cls.fan.duty(duty_cycle) @staticmethod def get_frequency(): global interrupt_counter, counter_start_time_ms """ # 1. Measure time for n pulses (blocking) interrupt_counter = 0 start_time = time.ticks_ms() while interrupt_counter < n: pass t_delta = time.ticks_diff(time.ticks_ms(), start_time) frequency = float(n) / t_delta * 1000 return frequency # 2. Measure number of pulses over a fixed period of time (blocking) interrupt_counter = 0 t_delta_ms = 500 time.sleep_ms(t_delta_ms) frequency = interrupt_counter / (t_delta_ms / 1000.0) return frequency # 3. Measure the number of pulses and time since the last time # this function was called (non-blocking). Values with this method seem # too low (gets better the longer the blynk_event_loop co-routine sleeps). current_time_ms = time.ticks_ms() t_delta = time.ticks_diff(current_time_ms, counter_start_time_ms) frequency = interrupt_counter / (t_delta / 1000.0) print('t_delta=%d, interrupt_counter=%d' % (t_delta, interrupt_counter)) interrupt_counter = 0 counter_start_time_ms = time.ticks_ms() return frequency """ interrupt_counter = 0 t_delta_ms = 500 time.sleep_ms(t_delta_ms) frequency = interrupt_counter / (t_delta_ms / 1000.0) return frequency @classmethod def get_temperatures(cls, convert=True): output = {} try: if convert: cls.ds.convert_temp() time.sleep_ms(750) for label, address in cls.onewire_addresses.items(): if address: output[label] = cls.ds.read_temp(address) else: output[label] = None except onewire.OneWireError: pass return output @classmethod def get_power(cls): global data # only turn on the fan if the panel temp is > 25C if data['temp_panel'] > 25: cls.set_fan_duty_cycle(75) else: cls.set_fan_duty_cycle(0) # Use the fan's rated flow rate from the datasheet scaled by the duty # cycle. We could probably calibrate this based on the tachometer signal, # which seems to change when the air flow is restricted. flow_rate = cls.fan.duty() / 100.0 * 3 # m^3/min """ https://builditsolar.com/References/Measurements/CollectorPerformance.htm # https://www.engineeringtoolbox.com/air-properties-d_156.html air_density = 1.208 kg/m^3 Air density increases as it is heated. Use the same correction factor as builditsolar.com page for now (1.208 * 0.065 / 0.075 = 1.047 kg/m^3). Qout = (flow_rate)*(air_density)*(temp_out - temp_in)*(Cp_air) Qout = (0.6 m^3/min)(1.047 kg/m^3)(30C - 20C)(1.006 kJ/kgK) Qout = (7.29 kJ/min)*(1000 J/kJ)*(1/60 min/s) Qout = 105 W """ air_density = 1.208 * (.065 / .075) # Specific heat capacity of air (should also correct for temperature: # https://www.ohio.edu/mechanical/thermo/property_tables/air/air_Cp_Cv.html). # Should we be using Cp or Cv (constant pressure or constant volume)? Cp_air = 1.006 # kJ/kgK Qout = flow_rate * air_density * (data['temp_out'] - data['temp_in']) * Cp_air * 1000.0 / 60.0 return Qout async def _update_sensors(self, sleep_s=10): global data convert_s = 0.75 while True: if self.state == 'running': try: data['fan_frequency'] = self.get_frequency() data['fan_duty_cycle'] = int(self.fan.duty() / 1023.0 * 100) data.update(self.get_temperatures()) # Hard code temp_in for now data['temp_in'] = 20 data['power'] = self.get_power() self._logger.debug(repr(data)) # If we're connected to the blynk server, push out updates if blynk.state == BlynkLib.CONNECTED: for label, pin in virtual_pins.items(): blynk.virtual_write(pin, data[label]) # Try to publish updates over mqtt try: self.mqtt.publish('open-solar-furnace/%s' % self.hardware_id, json.dumps(data)) except: pass except Exception as e: self._logger.error('Exception: %s' % repr(e)) if sleep_s > convert_s: await asyncio.sleep(sleep_s - convert_s) else: await asyncio.sleep(1) gc.collect() async def _blynk_event_loop(self, sleep_s=.1): while True: if self.state == 'running': await asyncio.sleep(sleep_s) try: blynk.run() except Exception as e: self._logger.error('Exception: %s' % repr(e)) else: await asyncio.sleep(sleep_s) gc.collect() async def _maintain_connections(self, sleep_s): while True: if self.state == 'running': await asyncio.sleep(sleep_s) try: # If wifi is down, disconnect blynk if not self.wifi.isconnected(): self._logger.debug('blynk.disconnnect()') blynk.disconnect() # Reconnect mqtt if ping() fails try: self.mqtt.ping() except: self._logger.debug('mqtt_connect()') self.mqtt_connect() # Try reconnecting blynk (if it's not disconnected and we have wifi) if self.wifi.isconnected() and blynk.state == BlynkLib.DISCONNECTED: self._logger.info("blynk_connect()") blynk.connect() await asyncio.sleep(5) except Exception as e: self._logger.error('Exception: %s' % repr(e)) else: await asyncio.sleep(1) gc.collect()
class IoTCClient(): def __init__(self, id_scope, device_id, credentials_type: IoTCConnectType, credentials, logger=None): self._device_id = device_id self._id_scope = id_scope self._credentials_type = credentials_type self._content_type = 'application%2Fjson' self._content_encoding = 'utf-8' self._connected = False self._credentials = credentials self._events = {} self._model_id = None if logger is not None: self._logger = logger else: self._logger = ConsoleLogger(IoTCLogLevel.API_ONLY) self._twin_request_id = time() def set_content_type(self, content_type): self._content_type = encode_uri_component(content_type) def set_content_encoding(self, content_encoding): self._content_encoding = content_encoding def set_log_level(self, log_level: IoTCLogLevel): self._logger.set_log_level(log_level) def _on_message(self, topic, message): topic = topic.decode('utf-8') if topic == HubTopics.TWIN_RES.format(200, self._twin_request_id): self._logger.info('Received twin: {}'.format(message)) if topic.startswith(HubTopics.PROPERTIES): # desired properties self._logger.info( 'Received desired property message: {}'.format(message)) message = json.loads(message.decode('utf-8')) self.on_properties_update(message) elif topic.startswith(HubTopics.COMMANDS): # commands self._logger.info('Received command {} with message: {}'.format( topic, message)) match = self._commands_regex.match(topic) if match is not None: if all(m is not None for m in [match.group(1), match.group(2)]): command_name = match.group(1) command_req = match.group(2) command = Command(command_name, command_req) if message is not None: command.payload = message self._on_commands(command) elif topic.startswith( HubTopics.ENQUEUED_COMMANDS.format(self._device_id)): params = topic.split( "devices/{}/messages/devicebound/".format(self._device_id), 1)[1].split('&') for param in params: p = param.split('=') if p[0] == "method-name": command_name = p[1].split("Commands%3A")[1] self._logger.info( 'Received enqueued command {} with message: {}'.format( command_name, message)) command = Command(command_name, None) if message is not None: command.payload = message self._on_enqueued_commands(command) def connect(self): prov = ProvisioningClient(self._id_scope, self._device_id, self._credentials_type, self._credentials, self._logger, self._model_id) creds = prov.register() self._mqtt_client = MQTTClient(self._device_id, creds.host, 8883, creds.user.encode('utf-8'), creds.password.encode('utf-8'), ssl=True, keepalive=60) self._commands_regex = ure.compile( '\$iothub\/methods\/POST\/(.+)\/\?\$rid=(.+)') self._mqtt_client.connect(False) self._connected = True self._logger.info('Device connected!') self._mqtt_client.set_callback(self._on_message) self._mqtt_client.subscribe(HubTopics.TWIN) self._mqtt_client.subscribe('{}/#'.format(HubTopics.PROPERTIES)) self._mqtt_client.subscribe('{}/#'.format(HubTopics.COMMANDS)) self._mqtt_client.subscribe('{}/#'.format( HubTopics.ENQUEUED_COMMANDS.format(self._device_id))) self._logger.debug(self._twin_request_id) self._mqtt_client.publish( HubTopics.TWIN_REQ.format(self._twin_request_id).encode('utf-8'), '{{}}') def is_connected(self): if self._connected == True: return True return False def set_model_id(self, model): self._model_id = model def send_property(self, payload): self._logger.debug('Sending properties {}'.format(json.dumps(payload))) self._mqtt_client.publish( HubTopics.PROP_REPORT.format(time()).encode('utf-8'), json.dumps(payload)) def send_telemetry(self, payload, properties=None): topic = 'devices/{}/messages/events/?$.ct={}&$.ce={}'.format( self._device_id, self._content_type, self._content_encoding) if properties is not None: for prop in properties: topic += '{}={}&'.format( encode_uri_component(prop), encode_uri_component(properties[prop])) topic = topic[:-1] self._mqtt_client.publish(topic.encode('utf-8'), json.dumps(payload).encode('utf-8')) def on(self, event, callback): self._events[event] = callback def listen(self): if not self.is_connected(): return self._mqtt_client.ping() self._mqtt_client.wait_msg() sleep(1) def on_properties_update(self, patch): try: prop_cb = self._events[IoTCEvents.PROPERTIES] except: return for prop in patch: if prop == '$version': continue ret = prop_cb(prop, patch[prop]['value']) if ret: self._logger.debug('Acknowledging {}'.format(prop)) self.send_property({ '{}'.format(prop): { "value": patch[prop]["value"], 'status': 'completed', 'desiredVersion': patch['$version'], 'message': 'Property received' } }) else: self._logger.debug( 'Property "{}" unsuccessfully processed'.format(prop)) def _cmd_resp(self, command: Command, value): self._logger.debug('Responding to command "{}" request'.format( command.name)) self.send_property({ '{}'.format(command.name): { 'value': value, 'requestId': command.request_id } }) def _cmd_ack(self, command: Command): self._logger.debug('Acknowledging command {}'.format(command.name)) self._mqtt_client.publish( '$iothub/methods/res/{}/?$rid={}'.format( 200, command.request_id).encode('utf-8'), '') def _on_commands(self, command: Command): try: cmd_cb = self._events[IoTCEvents.COMMANDS] except KeyError: return self._logger.debug('Received command {}'.format(command.name)) self._cmd_ack(command) cmd_cb(command, self._cmd_resp) def _on_enqueued_commands(self, command: Command): try: cmd_cb = self._events[IoTCEvents.ENQUEUED_COMMANDS] except KeyError: return self._logger.debug('Received enqueued command {}'.format(command.name)) self._cmd_ack(command) cmd_cb(command)
class UnmanagedDevice: """ An "unmanaged device" for the Watson IoT platform. Can be used with "Quickstart"; see https://quickstart.internetofthings.ibmcloud.com """ decoders = {} encoders = {} commands = {} def __init__(self, org=QUICKSTART_ORG, device_type=None, device_id=None, username='******', token='', port=8883, clean_session=True, domain=DOMAIN, ssl_params=None, log_level='info'): """ Builds proper params for connecting to IoT platform MQTT broker. Registers JSON encoder & decoder. Creates MQTT client object, but does not connect. `quickstart` implies an *insecure* connection! `device_type` and `token` not necessary if `org` is `quickstart`. :param log_level: Logging level :type log_level: str :param org: IoT platform organization :type org: str :param device_type: IoT platform device type :type device_type: str :param device_id: IoT platform client identifier :type device_id: str :param username: IoT platform username :type username: str :param token: IoT platform API token :type token: str :param port: MQTT broker port :type port: int :param clean_session: Whether to use a clean session when connecting :type clean_session: bool :param domain: IoT platform domain name :type domain: str :param ssl_params: Additional SSL parameters for a secure connection :type ssl_params: dict """ if not device_id: raise Exception('"device_id" parameter required') self.org = org if not self.is_quickstart: if not device_type: raise Exception('"device_type" parameter required') if not token: raise Exception('"token" parameter required') self.username = username self.token = token self.device_type = device_type self.address = '%s.messaging.%s' % (org, domain) self.client_id = 'd:%s:%s:%s' % (self.org, self.device_type, device_id) self.port = port self.keep_alive = 60 self.logger = logging.getLogger( '%s.%s' % (self.__module__, self.__class__.__name__)) self.logger.level = LOG_LEVELS[log_level] self.clean_session = clean_session self.ssl_params = ssl_params or {} self.client = MQTTClient(self.client_id, self.address, user=self.username, password=self.token, keepalive=60, ssl=self.is_secure, ssl_params=self.ssl_params) if self.logger.level == logging.DEBUG: self.client.DEBUG = True self.set_decoder('json', bytes_to_json) self.set_encoder('json', json.dumps) self.set_decoder('text', bytes_to_utf8) # noinspection PyTypeChecker self.set_encoder('text', str) @property def is_connected(self): """ Crudely checks connectivity by pinging :return: Whether or not socket is alive :rtype: bool """ try: self.client.ping() return True except OSError: return False def set_encoder(self, name, func): """ "Registers" an encoder :param name: Name of encoder :type name: str :param func: Encoding function :type func: function """ self.encoders[name] = func def unset_encoder(self, name): """ "Un-registers" a encoder :param name: Name of existing encoder :type name: str """ try: del self.encoders[name] except KeyError: pass def set_decoder(self, name, func): """ "Registers" a decoder :param name: Name of decoder :type name: str :param func: Decoding function :type func: function """ self.decoders[name] = func def unset_decoder(self, name): """ "Un-registers" a decoder :param name: Name of existing decoder :type name: str """ try: del self.decoders[name] except KeyError: pass def set_command(self, command_id, handler): """ "Registers" a command handler (if org is not "quickstart") :param command_id: Command ID :type command_id: str :param handler: Command handler :type handler: function """ if self.is_quickstart: raise Exception('"quickstart" org does not support commands') self.commands[command_id] = handler def unset_command(self, command_id): """ "Unregisters" a command :param command_id: Command ID :type command_id: str """ try: del self.commands[command_id] except KeyError: pass @property def is_secure(self): """ Secure connection? `False` if `org` is `quickstart` :return: Whether or not SSL is enabled. :rtype: bool """ return self.port == 8883 and not self.is_quickstart @property def is_quickstart(self): """ Is "quickstart" org? :return: Whether or not `org` is `quickstart` :rtype: bool """ return self.org == QUICKSTART_ORG def connect(self): """ Connects to the MQTT broker. If not a "quickstart" org, then subscribes to commands. """ self.client.connect(self.clean_session) self.logger.debug('client "%s" connected to %s:%s' % (self.client_id, self.address, self.port)) if not self.is_quickstart: def message_callback(topic, message): """ Callback executed when a msg for a subscribed topic is received :param topic: Raw MQTT topic :type topic: bytes :param message: Raw MQTT message :type message: bytes """ topic = bytes_to_utf8(topic) matches = TOPIC_REGEX.match(topic) command_id = matches.group(1) message_format = matches.group(2) if message_format in self.decoders: message = self.decoders[message_format](message) else: self.logger.debug( 'no suitable decoder for message format "%s"' % message_format) self.logger.debug('topic: %s\nmessage: %s' % (topic, message)) if command_id in self.commands: self.logger.info('received command "%s"' % command_id) self.commands[command_id](message) else: self.logger.warning('command "%s" received, \ but no handler registered' % command_id) self.client.set_callback(message_callback) self.client.subscribe(DEVICE_COMMAND_TOPIC) self.logger.debug('subscribed to device command topic: %s', DEVICE_COMMAND_TOPIC) def publishEvent(self, event_id, payload, message_format='json', qos=0): """ Publishes an event :param event_id: Event ID :type event_id: str :param payload: Event payload :type payload: Any :param message_format: Message format :type message_format: str :param qos: Quality of Service :type qos: int """ if not self.is_connected: raise Exception('client is not connected') if qos == 2: raise Exception('QoS level 2 not implemented') event_id = event_id.strip() if message_format in self.encoders: payload = self.encoders[message_format](payload) self.client.publish('iot-2/evt/%s/fmt/%s' % (event_id, message_format), payload, qos) def disconnect(self): """ Disconnects (if connected) """ try: self.client.disconnect() self.logger.warning('Closed connection to the IBM Watson \ IoT Platform') except OSError: self.logger.warning('Attempted to disconnect from a \ disconnected socket') def loop(self): """ Non-blocking check-for-messages. You need to do something else after this, such as `time.sleep(1)`, or other meaningful work, if you are going to do this in a busy-loop. This appears unsupported in some environments (incl. unix) """ self.client.check_msg() def sync_loop(self): """ Blocking check-for-messages. Run this in a busy-loop. """ self.client.wait_msg()
class domoticz_mqtt_protocol: processcnt = 1 def __init__(self): self._log = core._log self._log.debug("Protocol: domoticz mqtt contruction") self._lock = Event() # release lock, ready for next loop self._lock.clear() def init(self, protocol): self._log.debug("Protocol " + name + ": Init") self._client_id = protocol['client_id'] self._server = protocol['hostname'] self._port = protocol['port'] self._user = protocol['user'] self._password = protocol['password'] self._queue_out = protocol['publish'] self._queue_in = protocol['subscribe'] self._mq = MQTTClient(self._client_id, self._server, self._port, self._user, self._password) # Print diagnostic messages when retries/reconnects happens #self._mq.DEBUG = True self._queue = queues.Queue(maxsize=100) return self._queue def connect(self): self._log.debug("Protocol " + name + ": connect") return self._mq.reconnect() def disconnect(self): self._log.debug("Protocol " + name + ": disconnect") self._mq.disconnect() def check(self): self._log.debug("Protocol " + name + ": check") self._mq.check_msg() def status(self): self._log.debug("Protocol " + name + ": status") self._mq.ping() def recieve(self): self._log.debug("Protocol " + name + ": recieve") self._mq.subscribe(self.queue_in) def send(self, devicedata): self._log.debug("Protocol " + name + ": send " + devicedata["stype"]) # connect or reconnect to mqtt server self.connect() mqttdata = None # case while True: mqttdata = None # case SENSOR_TYPE_SINGLE if devicedata["stype"] == core.SENSOR_TYPE_SINGLE: self._log.debug("Protocol " + name + ": SENSOR_TYPE_SINGLE") # Get plugin values try: devicedata['value1'] = self._queue.get_nowait() except Exception as e: self._log.debug("Protocol " + name + " SENSOR_TYPE_SINGLE exception: " + repr(e)) break # Assemble mqtt message mqttdata = {} mqttdata["idx"] = devicedata["serverid"] mqttdata["nvalue"] = 0 mqttdata["svalue"] = str(devicedata["value1"]) message = ujson.dumps(mqttdata) break # case SENSOR_TYPE_LONG if devicedata["stype"] == core.SENSOR_TYPE_LONG: self._log.debug("Protocol " + name + ": SENSOR_TYPE_LONG") break # case SENSOR_TYPE_DUAL if devicedata["stype"] == core.SENSOR_TYPE_DUAL: self._log.debug("Protocol " + name + ": SENSOR_TYPE_DUAL") break # case SENSOR_TYPE_TEMP_HUM if devicedata["stype"] == core.SENSOR_TYPE_TEMP_HUM: self._log.debug("Protocol " + name + ": SENSOR_TYPE_TEMP_HUM") # Get plugin values try: devicedata['value1'] = self._queue.get_nowait() devicedata['value2'] = self._queue.get_nowait() except Exception as e: self._log.debug("Protocol " + self._name + " SENSOR_TYPE_TEMP_HUM Exception: " + repr(e)) break # Assemble mqtt message mqttdata = {} mqttdata["idx"] = devicedata["serverid"] mqttdata["nvalue"] = 0 mqttdata["svalue"] = str(devicedata["value1"]) + ";" + str( devicedata["value2"]) + ";0" message = ujson.dumps(mqttdata) break # case SENSOR_TYPE_TEMP_BARO if devicedata["stype"] == core.SENSOR_TYPE_TEMP_BARO: self._log.debug("Protocol " + name + ": SENSOR_TYPE_TEMP_BARO") break # case SENSOR_TYPE_TEMP_HUM_BARO if devicedata["stype"] == core.SENSOR_TYPE_TEMP_HUM_BARO: self._log.debug("Protocol " + name + ": SENSOR_TYPE_TEMP_HUM_BARO") # Get plugin values try: devicedata['value1'] = self._queue.get_nowait() devicedata['value2'] = self._queue.get_nowait() devicedata['value3'] = self._queue.get_nowait() except Exception as e: self._log.debug("Protocol " + self._name + " SENSOR_TYPE_TEMP_HUM_BARO Exception: " + repr(e)) break # Assemble mqtt message mqttdata = {} mqttdata["idx"] = devicedata["serverid"] mqttdata["nvalue"] = 0 mqttdata["svalue"] = str(devicedata["value1"]) + ";" + str( devicedata["value2"]) + ";0;" + str( devicedata["value3"]) + ";0" message = ujson.dumps(mqttdata) break # case SENSOR_TYPE_SWITCH if devicedata["stype"] == core.SENSOR_TYPE_SWITCH: self._log.debug("Protocol " + name + ": SENSOR_TYPE_SWITCH") # Get plugin values try: devicedata['value1'] = self._queue.get_nowait() except Exception as e: self._log.debug("Protocol " + self._name + " SENSOR_TYPE_SWITCH Exception: " + repr(e)) break # Switches can have many values, domoticz only two: on or off switch_on = ['closed', 'press', 'double', 'long'] switch_off = ['open', 'release'] if devicedata["value1"] in switch_on: devicedata["value1"] = 'On' elif devicedata["value1"] in switch_off: devicedata["value1"] = 'Off' else: break # Assemble mqtt message mqttdata = {} mqttdata["command"] = "switchlight" mqttdata["idx"] = devicedata["serverid"] mqttdata["switchcmd"] = devicedata["value1"] message = ujson.dumps(mqttdata) break # case SENSOR_TYPE_DIMMER if devicedata["stype"] == core.SENSOR_TYPE_DIMMER: self._log.debug("Protocol " + name + ": SENSOR_TYPE_DIMMER") break # case SENSOR_TYPE_WIND if devicedata["stype"] == core.SENSOR_TYPE_WIND: self._log.debug("Protocol " + name + ": SENSOR_TYPE_WIND") break # else UNKNOWN self._log.debug("Protocol " + name + ": Unknown sensor type!") break if mqttdata != None: self._log.debug("Protocol " + name + ": Message: " + message) self._mq.publish(self._queue_out, message) def process(self): # processing todo for protocol self._log.debug("Protocol " + name + " Processing...") devicedata = {} try: while True: message = self._queue.get_nowait() if message == core.QUEUE_MESSAGE_START: break devicedata['stype'] = self._queue.get_nowait() devicedata['serverid'] = self._queue.get_nowait() self.send(devicedata) except Exception as e: self._log.debug("Protocol " + name + " process Exception: " + repr(e)) # release lock, ready for next processing self._lock.clear()
class MQTTHandler: def __init__(self, name, server): self.mqtt = MQTTClient(hexlify(machine.unique_id()), server) self.name = name self.actions = {} self.publishers = {} self.connect() self.mqtt.set_callback(self.handle_mqtt_msgs) self.publish_all_after_msg = True def connect(self): print('.connect() Check if MQTT is already connected') if self.isconnected(): self.mqtt.disconnect() try: print('.connect() Not connected, so lets connect') self.mqtt.connect() except OSError: print(".connect() MQTT could not connect") return False time.sleep(3) if self.isconnected(): self.resubscribe_all() return True else: # Some delay to avoid system getting blocked in a endless loop in case of # connection problems, unstable wifi etc. time.sleep(5) return False def isconnected(self): try: self.mqtt.ping() except OSError: print(".isconnected() MQTT not connected - Ping not successfull") return False except AttributeError: print(".isconnected() MQTT not connected - Ping not available") return False return True def publish_generic(self, name, value): print(".publish_generic() Publish: {0} = {1}".format(name, value)) self.mqtt.publish(name, str(value)) def handle_mqtt_msgs(self, topic, msg): print(".handle_mqtt_msgs() Received MQTT message: {0}:{1}".format( topic, msg)) if topic in self.actions: print(".handle_mqtt_msgs() Found registered function {0}".format( self.actions[topic])) self.actions[topic](msg) if self.publish_all_after_msg: self.publish_all() def register_action(self, topicname, cbfunction): topic = self.name + b'/' + bytes(topicname, 'ascii') print(".register_action() Get topic {0} for {1}".format( topic, cbfunction)) if self.isconnected(): print('.register_action() MQTT connected, try to register') self.mqtt.subscribe(topic) self.actions[topic] = cbfunction def register_publisher(self, topicname, function): topic = self.name + b'/' + bytes(topicname, 'ascii') print(".register_publisher() Get topic {0} for {1}".format( topic, function)) self.publishers[topic] = function def publish_all(self): for topic in self.publishers: self.publish_generic(topic, self.publishers[topic]()) def resubscribe_all(self): for topic in self.actions: self.mqtt.subscribe(topic)