See More

# -*- coding: utf-8 -*- """ .. module:: openzwave.command This file is part of **python-openzwave** project https://github.com/OpenZWave/python-openzwave. :platform: Unix, Windows, MacOS X :sinopsis: openzwave wrapper .. moduleauthor:: bibi21000 aka Sébastien GALLET License : GPL(v3) **python-openzwave** is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. **python-openzwave** is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with python-openzwave. If not, see http://www.gnu.org/licenses. """ from openzwave.object import ZWaveNodeInterface from threading import Timer # Set default logging handler to avoid "No handler found" warnings. import logging try: # Python 2.7+ from logging import NullHandler except ImportError: class NullHandler(logging.Handler): """NullHandler logger for python 2.6""" def emit(self, record): pass logger = logging.getLogger('openzwave') logger.addHandler(NullHandler()) class ZWaveNodeBasic(ZWaveNodeInterface): """ Represents an interface to BasicCommands I known it's not necessary as they can be included in the node directly. But it's a good starting point. What I want to do is provide an automatic mapping system hidding the mapping classes. First example, the battery level, it's not a basic command but don't care. Its command class is 0x80. A user should write .. code-block:: python if self.handle_command_class(class_id): ret=command_Class(...) The classic way to do it is a classic method of registering. But Another way : using heritage multiple ZWaveNode(ZWaveObject, ZWaveNodeBasic, ....) The interface will implement methods command_class_0x80(param1,param2,...) That's the first thing to do We also can define a property with a friendly name handle_command_class will do the rest Another way to do it : A node can manage actuators (switch, dimmer, ...) and sensors (temperature, consummation, temperature) So we need a kind of mechanism to retrieve commands in a user friendly way Same for sensors. A good use case is the AN158 Plug-in Meter Appliance Module We will study the following command classes : 'COMMAND_CLASS_SWITCH_ALL', 'COMMAND_CLASS_SWITCH_BINARY', 'COMMAND_CLASS_METER', The associated values are : .. code-block:: python COMMAND_CLASS_SWITCH_ALL : { 72057594101481476L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': '', 'data': 'On and Off Enabled', 'min': 0L, 'writeonly': False, 'label': 'Switch All', 'readonly': False, 'data_str': 'On and Off Enabled', 'type': 'List'} } COMMAND_CLASS_SWITCH_BINARY : { 72057594093060096L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': '', 'data': False, 'min': 0L, 'writeonly': False, 'label': 'Switch', 'readonly': False, 'data_str': False, 'type': 'Bool'} } COMMAND_CLASS_METER : { 72057594093273600L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': '', 'data': False, 'min': 0L, 'writeonly': False, 'label': 'Exporting', 'readonly': True, 'data_str': False, 'type': 'Bool'}, 72057594101662232L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': '', 'data': 'False', 'min': 0L, 'writeonly': True, 'label': 'Reset', 'readonly': False, 'data_str': 'False', 'type': 'Button'}, 72057594093273090L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': 'kWh', 'data': 0.0, 'min': 0L, 'writeonly': False, 'label': 'Energy', 'readonly': True, 'data_str': 0.0, 'type': 'Decimal'}, 72057594093273218L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': 'W', 'data': 0.0, 'min': 0L, 'writeonly': False, 'label': 'Power', 'readonly': True, 'data_str': 0.0, 'type': 'Decimal'} } Another example from an homePro dimmer (not configured in openzwave): .. code-block:: python COMMAND_CLASS_SWITCH_MULTILEVEL : { 72057594109853736L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': '', 'data': 'False', 'min': 0L, 'writeonly': True, 'label': 'Dim', 'readonly': False, 'data_str': 'False', 'type': 'Button'}, 72057594109853697L: { 'help': '', 'max': 255L, 'is_polled': False, 'units': '', 'data': 69, 'min': 0L, 'writeonly': False, 'label': 'Level', 'readonly': False, 'data_str': 69, 'type': 'Byte'}, 72057594118242369L: { 'help': '', 'max': 255L, 'is_polled': False, 'units': '', 'data': 0, 'min': 0L, 'writeonly': False, 'label': 'Start Level', 'readonly': False, 'data_str': 0, 'type': 'Byte'}, 72057594109853720L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': '', 'data': 'False', 'min': 0L, 'writeonly': True, 'label': 'Bright', 'readonly': False, 'data_str': 'False', 'type': 'Button'}, 72057594118242352L: { 'help': '', 'max': 0L, 'is_polled': False, 'units': '', 'data': False, 'min': 0L, 'writeonly': False, 'label': 'Ignore Start Level', 'readonly': False, 'data_str': False, 'type': 'Bool'} } What about the conclusion : The COMMAND_CLASS_SWITCH_ALL is defined with the same label and use a list as parameter. This should be a configuration parameter. Don't know what to do for this command class The COMMAND_CLASS_SWITCH_BINARY use a bool as parameter while COMMAND_CLASS_SWITCH_MULTILEVEL use 2 buttons : Dim and Bright. Dim and Bright must be done in 2 steps : set the level and activate the button. So we must add one or more lines in the actuators : Switch : {setter:self.set_command_class_0xYZ(valueId, new), getter:} We must find a way to access the value directly Bright Dim So for the COMMAND_CLASS_SWITCH_BINARY we must define a function called Switch (=the label of the value). What happen if we have 2 switches on the node : 2 values I suppose. COMMAND_CLASS_SWITCH_MULTILEVEL uses 2 commands : 4 when 2 dimmers on the done ? Don't know but it can. COMMAND_CLASS_METER export many values : 2 of them sends a decimal and are readonly. They also have a Unit defined ans values are readonly COMMAND_CLASS_METER are used for sensors only. So we would map every values entries as defined before Programming : get_switches : retrieve the list of switches on the node is_switch (label) : says if the value with label=label is a switch get_switch (label) : retrieve the value where label=label """ def get_battery_level(self, value_id=None): """ The battery level of this node. The command 0x80 (COMMAND_CLASS_BATTERY) of this node. :param value_id: The value to retrieve state. If None, retrieve the first value :type value_id: int :return: The level of this battery :rtype: int """ if value_id is None: for val in self.get_battery_levels(): return self.values[val].data elif value_id in self.get_battery_levels(): return self.values[value_id].data return None def get_battery_levels(self): """ The command 0x80 (COMMAND_CLASS_BATTERY) of this node. Retrieve the list of values to consider as batteries. Filter rules are : command_class = 0x80 genre = "User" type = "Byte" readonly = True writeonly = False :return: The list of switches on this node :rtype: dict() """ return self.get_values(class_id=0x80, genre='User', \ type='Byte', readonly=True, writeonly=False) def get_power_level(self, value_id=None): """ The power level of this node. The command 0x73 (COMMAND_CLASS_POWERLEVEL) of this node. :param value_id: The value to retrieve state. If None, retrieve the first value :type value_id: int :return: The level of this battery :rtype: int """ if value_id is None: for val in self.get_power_levels(): return self.values[val].data elif value_id in self.get_power_levels(): return self.values[value_id].data return None def get_power_levels(self): """ The command 0x73 (COMMAND_CLASS_POWERLEVEL) of this node. Retrieve the list of values to consider as power_levels. Filter rules are : command_class = 0x73 genre = "User" type = "Byte" readonly = True writeonly = False :return: The list of switches on this node :rtype: dict() """ return self.get_values(class_id=0x73, genre='User', \ type='Byte', readonly=True, writeonly=False) def can_wake_up(self): """ Check if node contain the command class 0x84 (COMMAND_CLASS_WAKE_UP). Filter rules are : command_class = 0x84 :return: True if the node can wake up :rtype: bool """ res = self.get_values(class_id=0x84) if res is not None and len(res) > 0: return True else: return False class ZWaveNodeSwitch(ZWaveNodeInterface): """ Represents an interface to switches and dimmers Commands """ def get_switches_all(self): """ The command 0x27 (COMMAND_CLASS_SWITCH_ALL) of this node. Retrieve the list of values to consider as switches_all. Filter rules are : command_class = 0x27 genre = "System" type = "List" readonly = False writeonly = False :return: The list of switches on this node :rtype: dict() """ return self.get_values(class_id=0x27, genre='System', type='List', readonly=False, writeonly=False) def set_switch_all(self, value_id, value): """ The command 0x27 (COMMAND_CLASS_SWITCH_ALL) of this node. Set switches_all to value (using value value_id). :param value_id: The value to retrieve state :type value_id: int :param value: A predefined string :type value: str """ if value_id in self.get_switches_all(): self.values[value_id].data = value return True return False def get_switch_all_state(self, value_id): """ The command 0x27 (COMMAND_CLASS_SWITCH_ALL) of this node. Return the state (using value value_id) of a switch or a dimmer. :param value_id: The value to retrieve state :type value_id: int :return: The state of the value :rtype: bool """ if value_id in self.get_switches_all(): instance = self.values[value_id].instance for switch in self.get_switches(): if self.values[switch].instance == instance: return self.values[switch].data for dimmer in self.get_dimmers(): if self.values[dimmer].instance == instance: if self.values[dimmer].data == 0: return False else: return True return None def get_switch_all_item(self, value_id): """ The command 0x27 (COMMAND_CLASS_SWITCH_ALL) of this node. Return the current value (using value value_id) of a switch_all. :param value_id: The value to retrieve switch_all value :type value_id: int :return: The value of the value :rtype: str """ if value_id in self.get_switches_all(): return self.values[value_id].data return None def get_switch_all_items(self, value_id): """ The command 0x27 (COMMAND_CLASS_SWITCH_ALL) of this node. Return the all the possible values (using value value_id) of a switch_all. :param value_id: The value to retrieve items list :type value_id: int :return: The value of the value :rtype: set() """ if value_id in self.get_switches_all(): return self.values[value_id].data_items return None def get_switches(self): """ The command 0x25 (COMMAND_CLASS_SWITCH_BINARY) of this node. Retrieve the list of values to consider as switches. Filter rules are : command_class = 0x25 genre = "User" type = "Bool" readonly = False writeonly = False :return: The list of switches on this node :rtype: dict() """ return self.get_values(class_id=0x25, genre='User', \ type='Bool', readonly=False, writeonly=False) def set_switch(self, value_id, value): """ The command 0x25 (COMMAND_CLASS_SWITCH_BINARY) of this node. Set switch to value (using value value_id). :param value_id: The value to retrieve state :type value_id: int :param value: True or False :type value: bool """ if value_id in self.get_switches(): self.values[value_id].data = value return True return False def get_switch_state(self, value_id): """ The command 0x25 (COMMAND_CLASS_SWITCH_BINARY) of this node. Return the state (using value value_id) of a switch. :param value_id: The value to retrieve state :type value_id: int :return: The state of the value :rtype: bool """ if value_id in self.get_switches(): return self.values[value_id].data return None def get_dimmers(self): """ The command 0x26 (COMMAND_CLASS_SWITCH_MULTILEVEL) of this node. Retrieve the list of values to consider as dimmers. Filter rules are : command_class = 0x26 genre = "User" type = "Bool" readonly = False writeonly = False :return: The list of dimmers on this node :rtype: dict() """ return self.get_values(class_id=0x26, genre='User', \ type='Byte', readonly=False, writeonly=False) def set_dimmer(self, value_id, value): """ The command 0x26 (COMMAND_CLASS_SWITCH_MULTILEVEL) of this node. Set switch to value (using value value_id). :param value_id: The value to retrieve state :type value_id: int :param value: The level : a value between 0-99 or 255. 255 set the level to the last value. \ 0 turn the dimmer off :type value: int """ logger.debug("set_dimmer Level:%s", value) if value_id in self.get_dimmers(): if 99 < value < 255: value = 99 elif value < 0: value = 0 self.values[value_id].data = value #Dimmers doesn't return the good level. #Add a Timer to refresh the value if value == 0: timer1 = Timer(1, self.values[value_id].refresh) timer1.start() timer2 = Timer(2, self.values[value_id].refresh) timer2.start() return True return False def get_dimmer_level(self, value_id): """ The command 0x26 (COMMAND_CLASS_SWITCH_MULTILEVEL) of this node. Get the dimmer level (using value value_id). :param value_id: The value to retrieve level :type value_id: int :return: The level : a value between 0-99 :rtype: int """ if value_id in self.get_dimmers(): return self.values[value_id].data return None class ZWaveNodeSensor(ZWaveNodeInterface): """ Represents an interface to Sensor Commands """ def get_sensors(self, type='All'): """ The command 0x30 (COMMAND_CLASS_SENSOR_BINARY) of this node. The command 0x31 (COMMAND_CLASS_SENSOR_MULTILEVEL) of this node. The command 0x32 (COMMAND_CLASS_METER) of this node. Retrieve the list of values to consider as sensors. Filter rules are : command_class = 0x30-32 genre = "User" readonly = True writeonly = False :param type: the type of value :type type: 'All' or PyValueTypes :return: The list of switches on this node :rtype: dict() """ values = {} values.update(self.get_values(type=type, class_id=0x30, genre='User', \ readonly=True, writeonly=False)) values.update(self.get_values(type=type, class_id=0x31, genre='User', \ readonly=True, writeonly=False)) values.update(self.get_values(type=type, class_id=0x32, genre='User', \ readonly=True, writeonly=False)) return values def get_sensor_value(self, value_id): """ The command 0x30 (COMMAND_CLASS_SENSOR_BINARY) of this node. The command 0x31 (COMMAND_CLASS_SENSOR_MULTILEVEL) of this node. The command 0x32 (COMMAND_CLASS_METER) of this node. :param value_id: The value to retrieve value :type value_id: int :return: The state of the sensors :rtype: variable """ if value_id in self.get_sensors(): return self.values[value_id].data return None class ZWaveNodeSecurity(ZWaveNodeInterface): """ Represents an interface to Security Commands """ def get_protections(self): """ The command 0x75 (COMMAND_CLASS_PROTECTION) of this node. Retrieve the list of values to consider as protection. Filter rules are : command_class = 0x75 genre = "User" readonly = True writeonly = False :return: The list of switches on this node :rtype: dict() """ return self.get_values(class_id=0x75, genre='System', \ type='List', readonly=False, writeonly=False) def set_protection(self, value_id, value): """ The command 0x75 (COMMAND_CLASS_PROTECTION) of this node. Set protection to value (using value value_id). :param value_id: The value to set protection :type value_id: int :param value: A predefined string :type value: str """ if value_id in self.get_protections(): self.values[value_id].data = value return True return False def get_protection_item(self, value_id): """ The command 0x75 (COMMAND_CLASS_PROTECTION) of this node. Return the current value (using value value_id) of a protection. :param value_id: The value to retrieve protection value :type value_id: int :return: The value of the value :rtype: str """ if value_id in self.get_protections(): return self.values[value_id].data return None def get_protection_items(self, value_id): """ The command 0x75 (COMMAND_CLASS_PROTECTION) of this node. Return the all the possible values (using value value_id) of a protection. :param value_id: The value to retrieve items list :type value_id: int :return: The value of the value :rtype: set() """ if value_id in self.get_protections(): return self.values[value_id].data_items return None