def __init__(self, device, isac_node): self.isac_node = isac_node self.signals = {} self._ozw_notif_queue = Queue() self._running = True green.spawn(self._notif_reader) self.options = ZWaveOption( device, config_path='/usr/share/openzwave/config', user_path='./user-dir', cmd_line='' ) self.options.set_log_file("./user-dir/OZW_Log.log") self.options.set_append_log_file(False) self.options.set_console_output(False) self.options.set_save_log_level('Info') self.options.set_logging(False) self.options.lock() self.network = ZWaveNetwork(self.options, log=None) notif_to_func = [ (ZWaveNetwork.SIGNAL_NETWORK_STARTED, self.notif_network_started), (ZWaveNetwork.SIGNAL_NETWORK_RESETTED, self.notif_network_resetted), (ZWaveNetwork.SIGNAL_NETWORK_READY, self.notif_network_ready), (ZWaveNetwork.SIGNAL_NODE_ADDED, self.notif_node_added), (ZWaveNetwork.SIGNAL_NODE_NAMING, self.notif_node_named), (ZWaveNetwork.SIGNAL_NODE_REMOVED, self.notif_node_removed), (ZWaveNetwork.SIGNAL_VALUE_ADDED, self.notif_value_added), (ZWaveNetwork.SIGNAL_VALUE_CHANGED, self.notif_value_update), (ZWaveNetwork.SIGNAL_VALUE_REMOVED, self.notif_value_removed), (ZWaveNetwork.SIGNAL_CONTROLLER_COMMAND, self.notif_ctrl_message), (ZWaveNetwork.SIGNAL_CONTROLLER_WAITING, self.notif_ctrl_message), ] for notif, func in notif_to_func: dispatcher.connect(self._notif_wrapper(func), notif, weak=False) # dispatcher.connect(self._notif_wrapper_all, All) self.isac_node.add_rpc(self.network_heal) self.isac_node.add_rpc(self.controller_add_node, name='add_node') self.isac_node.add_rpc(self.controller_remove_node, name='remove_node') self.isac_node.add_rpc(self.controller_cancel_command, name='cancel_command')
class AlidronOZW(object): def __init__(self, device, isac_node): self.isac_node = isac_node self.signals = {} self._ozw_notif_queue = Queue() self._running = True green.spawn(self._notif_reader) self.options = ZWaveOption( device, config_path='/usr/share/openzwave/config', user_path='./user-dir', cmd_line='' ) self.options.set_log_file("./user-dir/OZW_Log.log") self.options.set_append_log_file(False) self.options.set_console_output(False) self.options.set_save_log_level('Info') self.options.set_logging(False) self.options.lock() self.network = ZWaveNetwork(self.options, log=None) notif_to_func = [ (ZWaveNetwork.SIGNAL_NETWORK_STARTED, self.notif_network_started), (ZWaveNetwork.SIGNAL_NETWORK_RESETTED, self.notif_network_resetted), (ZWaveNetwork.SIGNAL_NETWORK_READY, self.notif_network_ready), (ZWaveNetwork.SIGNAL_NODE_ADDED, self.notif_node_added), (ZWaveNetwork.SIGNAL_NODE_NAMING, self.notif_node_named), (ZWaveNetwork.SIGNAL_NODE_REMOVED, self.notif_node_removed), (ZWaveNetwork.SIGNAL_VALUE_ADDED, self.notif_value_added), (ZWaveNetwork.SIGNAL_VALUE_CHANGED, self.notif_value_update), (ZWaveNetwork.SIGNAL_VALUE_REMOVED, self.notif_value_removed), (ZWaveNetwork.SIGNAL_CONTROLLER_COMMAND, self.notif_ctrl_message), (ZWaveNetwork.SIGNAL_CONTROLLER_WAITING, self.notif_ctrl_message), ] for notif, func in notif_to_func: dispatcher.connect(self._notif_wrapper(func), notif, weak=False) # dispatcher.connect(self._notif_wrapper_all, All) self.isac_node.add_rpc(self.network_heal) self.isac_node.add_rpc(self.controller_add_node, name='add_node') self.isac_node.add_rpc(self.controller_remove_node, name='remove_node') self.isac_node.add_rpc(self.controller_cancel_command, name='cancel_command') # Plumbing def _notif_wrapper(self, f): def _notif(*args, **kwargs): logger.debug('Received notification for %s with args %s and kwargs %s', f.__name__, args, kwargs) del kwargs['signal'] del kwargs['sender'] self._ozw_notif_queue.put((f, args, kwargs)) return _notif def _notif_wrapper_all(self, *args, **kwargs): del kwargs['sender'] self._ozw_notif_queue.put((self.all_notif, args, kwargs)) def _notif_reader(self): while self._running: notif = self._ozw_notif_queue.get() if notif is None: continue f, args, kwargs = notif logger.debug('Reading notification for %s with args %s and kwargs %s', f.__name__, args, kwargs) f(*args, **kwargs) def all_notif(self, *args, **kwargs): import time from pprint import pformat as pf extra = [] if 'value' in kwargs and kwargs['value']: extra.append('value: %s' % self._make_uri(kwargs['node'], kwargs['value'])) elif 'node' in kwargs and kwargs['node']: extra.append('node: %s' % (kwargs['node'].name if kwargs['node'].name else kwargs['node'].node_id)) extra = ' ; '.join(extra) if args: logger.warning('>~>~># %f: %s ; %s ; %s', time.time(), pf(args), pf(kwargs), extra) else: logger.warning('>~>~># %f: %s ; %s', time.time(), pf(kwargs), extra) # Notifications from PYOZW def notif_network_started(self, network): logger.info('//////////// ZWave network is started ////////////') logger.debug( 'OpenZWave network is started: \ homeid %0.8x - %d nodes were found.', network.home_id, network.nodes_count ) def notif_network_resetted(self, network): logger.warning('OpenZWave network is resetted.') def notif_network_ready(self, network): logger.info('//////////// ZWave network is ready ////////////') logger.debug( 'ZWave network is ready: %d nodes were found.', network.nodes_count ) def notif_node_added(self, network, node): node_name = self._node_name(node) logger.info('Node added: %s.', node_name) self.isac_node.add_rpc(partial(self.node_heal, node), '%s/heal' % node_name) self.isac_node.add_rpc(partial(self.node_is_failed, node), '%s/is_failed' % node_name) def notif_node_named(self, network, node): logger.info('Node named: %s.', self._node_name(node)) # TODO: renaming of RPC enpoints as well as all IsacValue attached to the node, if the name really changed def notif_node_removed(self, network, node): logger.info('Node removed: %s.', self._node_name(node)) # TODO: Remove RPC endpoint (values should be removed by receiving VALUE_REMOVED notif) def notif_value_added(self, network, node, value): uri = self._make_uri(node, value) if uri in self.signals: logger.info('%s already registered', uri) return else: logger.info('Registering signal %s', uri) self.signals[uri] = { 'metadata': { 'uri': uri, 'label': value.label, 'help': value.help, 'max': value.max, 'min': value.min, 'units': value.units, 'genre': value.genre, 'type': value.type, 'is_read_only': value.is_read_only, 'is_write_only': value.is_write_only, 'instance': value.instance, 'index': value.index, 'value_id': value.value_id, 'node_id': node.node_id, 'node_name': node.name, 'location': node.location, 'home_id': node._network.home_id, 'command_class': node.get_command_class_as_string(value.command_class), 'data_items': list(value.data_items) if type(value.data_items) is set else value.data_items, }, 'static_tags': { 'home_id': node._network.home_id, 'location': node.location, 'node_id': node.node_id, 'command_class': node.get_command_class_as_string(value.command_class), 'index': value.index, 'instance': value.instance, }, 'node_value': (node, value), } data = self._value_data(value) #print '>>>> Creating IV', uri, data self.signals[uri]['isac_value'] = IsacValue( self.isac_node, uri, data, static_tags=self.signals[uri]['static_tags'], metadata=self.signals[uri]['metadata'], survey_last_value=False, survey_static_tags=False ) self.signals[uri]['isac_value'].observers += self._update_data_from_isac uri_poll = uri + '/poll' self.signals[uri_poll] = { 'isac_value': IsacValue( self.isac_node, uri_poll, value.is_polled, survey_last_value=False, survey_static_tags=False ), 'node_value': self.signals[uri]['node_value'], } self.signals[uri_poll]['isac_value'].observers += self._set_poll_from_isac def notif_value_update(self, network, node, value): uri = self._make_uri(node, value) logger.info('Value update for %s : %s.', uri, value.data) if uri not in self.signals: logger.info('%s not yet registered, skipping', uri) return signal = self.signals[uri] signal['isac_value'].value = self._value_data(value) def notif_value_removed(self, network, node, value): pass # TODO: Remove IsacValue def notif_ctrl_message(self, network, controller, **kwargs): # from pprint import pformat as pf # logger.warning('Controller message : %s', pf(kwargs)) pass # Update from ISAC def _update_data_from_isac(self, isac_value, value, ts, tags): uri = isac_value.uri signal = self.signals.get(uri, None) if signal is None: logger.error( 'Received an update from isac \ for a signal we don\'t know?! %s', uri ) return if signal['node_value'][1].is_read_only: logger.error( 'Signal %s is read only but we received an update \ from isac to write a value, %s, to it', uri, value ) return logger.info('Updating value %s with %s', uri, value) signal['node_value'][1].data = value def _set_poll_from_isac(self, isac_value, value, ts, tags): uri = isac_value.uri signal = self.signals.get(uri, None) if signal is None: logger.error('Signal %s is unknown?!', uri) return if uri.endswith('/poll'): if bool(value): try: intensity = int(value) except ValueError: intensity = 1 signal['node_value'][1].enable_poll(intensity) else: signal['node_value'][1].disable_poll() # RPC methods # controller: (hard_reset) has_node_failed(nodeid) name remove_failed_node(nodeid) replace_failed_node(nodeid) (soft_reset) # node: location name # def has_node_failed(self, node_name): # from pprint import pprint as pp # nodes_by_name = {node.name: node for node in self.network.nodes.values()} # pp(nodes_by_name) # node_id = nodes_by_name[node_name].node_id # # print nodes_by_name[node_name].is_failed # return self.network.controller.has_node_failed(node_id) def network_heal(self, upNodeRoute=False): logger.info('Healing network') self.network.heal(upNodeRoute) def controller_add_node(self, doSecurity=False): logger.info('Set controller into inclusion mode') return self.network.controller.add_node(doSecurity) def controller_remove_node(self): logger.info('Set controller into exclusion mode') return self.network.controller.remove_node() def controller_cancel_command(self): logger.info('Cancelling controller command') return self.network.controller.cancel_command() def node_heal(self, node, upNodeRoute=False): logger.info('Healing node %s', self._node_name(node)) return node.heal(upNodeRoute) def node_is_failed(self, node): logger.info('Asking if node %s is failed (%s)', self._node_name(node), node.is_failed) return node.is_failed # Tooling @staticmethod def _replace_all(s, olds, new): return reduce(lambda s, old: s.replace(old, new), list(olds), s) def _node_name(self, node): return node.name if node.name else str(node.node_id) def _value_data(self, value): data = value.data logger.debug('data type is %s', type(data)) if type(data) is str: try: data.decode() except UnicodeDecodeError: data = binascii.b2a_base64(data) return data def _make_uri(self, node, value): def _values_by_index(values): per_idx = {} for value in values: idx = value.index if idx not in per_idx: per_idx[idx] = [] per_idx[idx].append(value) return per_idx ok = False while not ok: try: values_by_idx = _values_by_index(node.get_values(class_id=value.command_class).values()) ok = True except RuntimeError as ex: if ex.message == 'dictionary changed size during iteration': continue else: raise is_multi_instance = len(values_by_idx[value.index]) > 1 cmd_class = node.get_command_class_as_string(value.command_class) cmd_class = cmd_class.replace('COMMAND_CLASS_', '').lower() node_name = self._node_name(node) label = self._replace_all(value.label.lower(), ' /()%:', '_').strip('_') if is_multi_instance: uri = 'zwave://%s.%s/%s/%d/%s' % ( node._network.home_id_str, node_name, cmd_class, value.instance, label) else: uri = 'zwave://%s.%s/%s/%s' % ( node._network.home_id_str, node_name, cmd_class, label) return str(uri) # Lifecycle def shutdown(self): # Stopping internal notification reader greenlet self._running = False self._ozw_notif_queue.put(None) self.network.stop() logger.info('Stopped network') self.network.destroy() logger.info('Destroyed network') self.isac_node.shutdown() logger.info('Stopped ISAC node')