Exemple #1
0
class Health(Model):
    HEALTH_CURRENT_STATUS = Opcode(0x04, None, "Health Current Status")

    def __init__(self):
        self.opcodes = [(self.HEALTH_CURRENT_STATUS,
                         self.__health_current_status_handler)]
        self.__tid = 0
        super(Health, self).__init__(self.opcodes)

    def get_status(self):
        self.send(self.HEALTH_CURRENT_STATUS)

    @property
    def _tid(self):
        tid = self.__tid
        self.__tid += 1
        if self.__tid >= 255:
            self.__tid = 0
        return tid

    def __health_current_status_handler(self, opcode, message):
        self.logger.info("Health status got this: %s", message)

    def __str__(self):
        return json.dumps({"model": "Health", "tid": self.__tid})

    def to_json(self):
        return {"model": "Health", "tid": self.__tid}
Exemple #2
0
class GenericOnOffClient(Model):
    GENERIC_ON_OFF_SET = Opcode(0x8202, None, "Generic OnOff Set")
    GENERIC_ON_OFF_SET_UNACKNOWLEDGED = Opcode(
        0x8203, None, "Generic OnOff Set Unacknowledged")
    GENERIC_ON_OFF_GET = Opcode(0x8201, None, "Generic OnOff Get")
    GENERIC_ON_OFF_STATUS = Opcode(0x8204, None, "Generic OnOff Status")

    def __init__(self):
        self.opcodes = [(self.GENERIC_ON_OFF_STATUS,
                         self.__generic_on_off_status_handler)]
        self.__tid = 0
        super(GenericOnOffClient, self).__init__(self.opcodes)

    def set(self, value, transition_time_ms=0, delay_ms=0, ack=True):
        message = bytearray()
        message += struct.pack("<BB", int(value > 0), self._tid)

        if transition_time_ms > 0:
            message += TransitionTime.pack(transition_time_ms, delay_ms)

        if ack:
            self.send(self.GENERIC_ON_OFF_SET, message)
        else:
            self.send(self.GENERIC_ON_OFF_SET_UNACKNOWLEDGED, message)

    def get(self):
        self.send(self.GENERIC_ON_OFF_GET)

    @property
    def _tid(self):
        tid = self.__tid
        self.__tid += 1
        if self.__tid >= 255:
            self.__tid = 0
        return tid

    def __generic_on_off_status_handler(self, opcode, message):
        logstr = "Present OnOff: " + "on" if message.data[0] > 0 else "off"
        if len(message.data) > 1:
            logstr += " Target OnOff: " + "on" if message.data[1] > 0 else "off"

        if len(message.data) == 3:
            logstr += " Remaining time: %d ms" % (TransitionTime.decode(
                message.data[2]))

        self.logger.info(logstr)
Exemple #3
0
class SimpleOnOffClient(Model):
    SIMPLE_ON_OFF_STATUS = Opcode(0xc4, 0x59, "Simple OnOff Status")
    SIMPLE_ON_OFF_SET = Opcode(0xc1, 0x59, "Simple OnOff Set")
    SIMPLE_ON_OFF_GET = Opcode(0xc2, 0x59, "Simple OnOff Get")
    SIMPLE_ON_OFF_SET_UNACKNOWLEDGED = Opcode(0xc3, 0x59, "Simple OnOff Set Unacknowledged")

    def __init__(self):
        self.opcodes = [
            (self.SIMPLE_ON_OFF_STATUS, self.__simple_on_off_status_handler)]
        self.__tid = 0
        super(SimpleOnOffClient, self).__init__(self.opcodes)

    def set(self, state):
        message = bytearray()
        message += struct.pack("<BB", int(state), self._tid)
        self.send(self.SIMPLE_ON_OFF_SET, message)

    def get(self):
        self.send(self.SIMPLE_ON_OFF_GET)

    def unacknowledged_set(self, state):
        message = bytearray()
        message += struct.pack("<BB", int(state), self._tid)
        self.send(self.SIMPLE_ON_OFF_SET_UNACKNOWLEDGED, message)

    @property
    def _tid(self):
        tid = self.__tid
        self.__tid += 1
        if self.__tid >= 255:
            self.__tid = 0
        return tid

    def __simple_on_off_status_handler(self, opcode, message):
        on_off = "on" if message.data[0] > 0 else "off"
        self.logger.info("Present value is %s", on_off)

    def __str__(self):
        return json.dumps({"model": "SimpleOnOffClient", "tid": self.__tid})

    def to_json(self):
        return {"model": "SimpleOnOffClient", "tid": self.__tid}
Exemple #4
0
class ConfigurationClient(Model):
    # We ignore some flake8 formatting errors to make the following contructs a bit more readable.
    _APPKEY_ADD = Opcode(0x00)  # noqa: E501,E221
    _APPKEY_DELETE = Opcode(0x8000)  # noqa: E501,E221
    _APPKEY_GET = Opcode(0x8001)  # noqa: E501,E221
    _APPKEY_UPDATE = Opcode(0x01)  # noqa: E501,E221
    _BEACON_GET = Opcode(0x8009)  # noqa: E501,E221
    _BEACON_SET = Opcode(0x800A)  # noqa: E501,E221
    _COMPOSITION_DATA_GET = Opcode(0x8008)  # noqa: E501,E221
    _MODEL_PUBLICATION_SET = Opcode(0x03)  # noqa: E501,E221
    _DEFAULT_TTL_GET = Opcode(0x800C)  # noqa: E501,E221
    _DEFAULT_TTL_SET = Opcode(0x800D)  # noqa: E501,E221
    _FRIEND_GET = Opcode(0x800F)  # noqa: E501,E221
    _FRIEND_SET = Opcode(0x8010)  # noqa: E501,E221
    _GATT_PROXY_GET = Opcode(0x8012)  # noqa: E501,E221
    _GATT_PROXY_SET = Opcode(0x8013)  # noqa: E501,E221
    _HEARTBEAT_PUBLICATION_GET = Opcode(0x8038)  # noqa: E501,E221
    _HEARTBEAT_PUBLICATION_SET = Opcode(0x8039)  # noqa: E501,E221
    _HEARTBEAT_SUBSCRIPTION_GET = Opcode(0x803A)  # noqa: E501,E221
    _HEARTBEAT_SUBSCRIPTION_SET = Opcode(0x803B)  # noqa: E501,E221
    _KEY_REFRESH_PHASE_GET = Opcode(0x8015)  # noqa: E501,E221
    _KEY_REFRESH_PHASE_SET = Opcode(0x8016)  # noqa: E501,E221
    _LOW_POWER_NODE_POLLTIMEOUT_GET = Opcode(0x802D)  # noqa: E501,E221
    _MODEL_APP_BIND = Opcode(0x803D)  # noqa: E501,E221
    _MODEL_APP_UNBIND = Opcode(0x803F)  # noqa: E501,E221
    _MODEL_PUBLICATION_GET = Opcode(0x8018)  # noqa: E501,E221
    _MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET = Opcode(0x801A)  # noqa: E501,E221
    _MODEL_SUBSCRIPTION_ADD = Opcode(0x801B)  # noqa: E501,E221
    _MODEL_SUBSCRIPTION_DELETE = Opcode(0x801C)  # noqa: E501,E221
    _MODEL_SUBSCRIPTION_DELETE_ALL = Opcode(0x801D)  # noqa: E501,E221
    _MODEL_SUBSCRIPTION_OVERWRITE = Opcode(0x801E)  # noqa: E501,E221
    _MODEL_SUBSCRIPTION_VIRTUAL_ADDRESS_ADD = Opcode(0x8020)  # noqa: E501,E221
    _MODEL_SUBSCRIPTION_VIRTUAL_ADDRESS_DELETE = Opcode(
        0x8021)  # noqa: E501,E221
    _MODEL_SUBSCRIPTION_VIRTUAL_ADDRESS_OVERWRITE = Opcode(
        0x8022)  # noqa: E501,E221
    _NETKEY_ADD = Opcode(0x8040)  # noqa: E501,E221
    _NETKEY_DELETE = Opcode(0x8041)  # noqa: E501,E221
    _NETKEY_GET = Opcode(0x8042)  # noqa: E501,E221
    _NETKEY_UPDATE = Opcode(0x8045)  # noqa: E501,E221
    _NETWORK_TRANSMIT_GET = Opcode(0x8023)  # noqa: E501,E221
    _NETWORK_TRANSMIT_SET = Opcode(0x8024)  # noqa: E501,E221
    _NODE_IDENTITY_GET = Opcode(0x8046)  # noqa: E501,E221
    _NODE_IDENTITY_SET = Opcode(0x8047)  # noqa: E501,E221
    _NODE_RESET = Opcode(0x8049)  # noqa: E501,E221
    _RELAY_GET = Opcode(0x8026)  # noqa: E501,E221
    _RELAY_SET = Opcode(0x8027)  # noqa: E501,E221
    _SIG_MODEL_APP_GET = Opcode(0x804B)  # noqa: E501,E221
    _SIG_MODEL_SUBSCRIPTION_GET = Opcode(0x8029)  # noqa: E501,E221
    _VENDOR_MODEL_APP_GET = Opcode(0x804D)  # noqa: E501,E221
    _VENDOR_MODEL_SUBSCRIPTION_GET = Opcode(0x802B)  # noqa: E501,E221

    _APPKEY_LIST = Opcode(0x8002)  # noqa: E501,E221
    _APPKEY_STATUS = Opcode(0x8003)  # noqa: E501,E221
    _BEACON_STATUS = Opcode(0x800B)  # noqa: E501,E221
    _COMPOSITION_DATA_STATUS = Opcode(0x02)  # noqa: E501,E221
    _DEFAULT_TTL_STATUS = Opcode(0x800E)  # noqa: E501,E221
    _FRIEND_STATUS = Opcode(0x8011)  # noqa: E501,E221
    _GATT_PROXY_STATUS = Opcode(0x8014)  # noqa: E501,E221
    _HEARTBEAT_PUBLICATION_STATUS = Opcode(0x06)  # noqa: E501,E221
    _HEARTBEAT_SUBSCRIPTION_STATUS = Opcode(0x803C)  # noqa: E501,E221
    _KEY_REFRESH_PHASE_STATUS = Opcode(0x8017)  # noqa: E501,E221
    _LOW_POWER_NODE_POLLTIMEOUT_STATUS = Opcode(0x802E)  # noqa: E501,E221
    _MODEL_APP_STATUS = Opcode(0x803E)  # noqa: E501,E221
    _MODEL_PUBLICATION_STATUS = Opcode(0x8019)  # noqa: E501,E221
    _MODEL_SUBSCRIPTION_STATUS = Opcode(0x801F)  # noqa: E501,E221
    _NETKEY_LIST = Opcode(0x8043)  # noqa: E501,E221
    _NETKEY_STATUS = Opcode(0x8044)  # noqa: E501,E221
    _NETWORK_TRANSMIT_STATUS = Opcode(0x8025)  # noqa: E501,E221
    _NODE_IDENTITY_STATUS = Opcode(0x8048)  # noqa: E501,E221
    _NODE_RESET_STATUS = Opcode(0x804A)  # noqa: E501,E221
    _RELAY_STATUS = Opcode(0x8028)  # noqa: E501,E221
    _SIG_MODEL_APP_LIST = Opcode(0x804C)  # noqa: E501,E221
    _SIG_MODEL_SUBSCRIPTION_LIST = Opcode(0x802A)  # noqa: E501,E221
    _VENDOR_MODEL_APP_LIST = Opcode(0x804E)  # noqa: E501,E221
    _VENDOR_MODEL_SUBSCRIPTION_LIST = Opcode(0x802C)  # noqa: E501,E221

    def __init__(self, prov_db):
        self.opcodes = [
            (self._APPKEY_LIST, self.__appkey_list_handler),  # noqa: E501,E203
            (self._APPKEY_STATUS,
             self.__appkey_status_handler),  # noqa: E501,E203
            (self._BEACON_STATUS,
             self.__beacon_status_handler),  # noqa: E501,E203
            (self._COMPOSITION_DATA_STATUS,
             self.__composition_data_status_handler),  # noqa: E501,E203
            (self._DEFAULT_TTL_STATUS,
             self.__default_ttl_status_handler),  # noqa: E501,E203
            (self._FRIEND_STATUS,
             self.__friend_status_handler),  # noqa: E501,E203
            (self._GATT_PROXY_STATUS,
             self.__gatt_proxy_status_handler),  # noqa: E501,E203
            (self._HEARTBEAT_PUBLICATION_STATUS,
             self.__heartbeat_publication_status_handler),  # noqa: E501,E203
            (self._HEARTBEAT_SUBSCRIPTION_STATUS,
             self.__heartbeat_subscription_status_handler),  # noqa: E501,E203
            (self._KEY_REFRESH_PHASE_STATUS,
             self.__key_refresh_phase_status_handler),  # noqa: E501,E203
            (self._LOW_POWER_NODE_POLLTIMEOUT_STATUS,
             self.__low_power_node_polltimeout_status_handler
             ),  # noqa: E501,E203
            (self._MODEL_APP_STATUS,
             self.__model_app_status_handler),  # noqa: E501,E203
            (self._MODEL_PUBLICATION_STATUS,
             self.__model_publication_status_handler),  # noqa: E501,E203
            (self._MODEL_SUBSCRIPTION_STATUS,
             self.__model_subscription_status_handler),  # noqa: E501,E203
            (self._NETKEY_LIST, self.__netkey_list_handler),  # noqa: E501,E203
            (self._NETKEY_STATUS,
             self.__netkey_status_handler),  # noqa: E501,E203
            (self._NETWORK_TRANSMIT_STATUS,
             self.__network_transmit_status_handler),  # noqa: E501,E203
            (self._NODE_IDENTITY_STATUS,
             self.__node_identity_status_handler),  # noqa: E501,E203
            (self._NODE_RESET_STATUS,
             self.__node_reset_status_handler),  # noqa: E501,E203
            (self._RELAY_STATUS,
             self.__relay_status_handler),  # noqa: E501,E203
            (self._SIG_MODEL_SUBSCRIPTION_LIST,
             self.__model_sig_subscription_list_handler),  # noqa: E501,E203
            (self._SIG_MODEL_APP_LIST,
             self.__model_sig_app_list_handler),  # noqa: E501,E203
            (self._VENDOR_MODEL_APP_LIST,
             self.__vendor_model_app_list_handler),  # noqa: E501,E203
            (self._VENDOR_MODEL_SUBSCRIPTION_LIST,
             self.__vendor_model_subscription_list_handler)
        ]  # noqa: E501,E203

        self.prov_db = prov_db
        self.previous_command = None
        # FIXME: Hack to retain the virtual label UUID until we get an ack.
        self._tmp_address = None
        super(ConfigurationClient, self).__init__(self.opcodes)

    @staticmethod
    def _unpack_key_ind(packed_keys):
        keys = []
        if packed_keys:
            pairs_cnt, single_cnt = (len(packed_keys) // 3,
                                     len(packed_keys) % 3 // 2)
            keys = [
                k for i in range(pairs_cnt)
                for k in mt.KeyIndex.unpack(packed_keys[i * 3:(i + 1) * 3])
            ]
            if single_cnt > 0:
                keys.append(mt.KeyIndex.unpack(packed_keys[3 * pairs_cnt:])[0])
        return keys

    def composition_data_get(self, page_number=0x00):
        self.send(self._COMPOSITION_DATA_GET, bytearray([page_number]))

    def appkey_add(self, appkey_index=0):
        key = self.prov_db.find_appkey(appkey_index)
        if not key:
            raise ValueError("Could not find appkey with index %d" %
                             (appkey_index))
        netkey_index = key.bound_net_key
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index, appkey_index)
        message += key.key
        self.previous_command = "add"
        self.send(self._APPKEY_ADD, message)

    def appkey_update(self, appkey_index=0):
        key = self.prov_db.find_appkey(appkey_index)
        if not key:
            raise ValueError("Could not find appkey with index %d" %
                             (appkey_index))
        netkey_index = key.bound_net_key
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index, appkey_index)
        message += key.key
        self.previous_command = "update"
        self.send(self._APPKEY_UPDATE, message)

    def appkey_delete(self, appkey_index=0):
        key = self.prov_db.find_appkey(appkey_index)
        if not key:
            raise ValueError("Could not find appkey with index %d" %
                             (appkey_index))
        netkey_index = key.bound_net_key
        key24 = mt.KeyIndex.pack(netkey_index, appkey_index)
        self.previous_command = "delete"
        self.send(self._APPKEY_DELETE, key24)

    def appkey_get(self, netkey_index=0):
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index)
        self.send(self._APPKEY_GET, message)

    def netkey_add(self, netkey_index=0):
        key = self.prov_db.find_netkey(netkey_index)
        if not key:
            raise ValueError("Could not find netkey with index %d" %
                             (netkey_index))
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index)
        message += key.key
        self.previous_command = "add"
        self.send(self._NETKEY_ADD, message)

    def netkey_update(self, netkey_index=0):
        key = self.prov_db.find_netkey(netkey_index)
        if not key:
            raise ValueError("Could not find netkey with index %d" %
                             (netkey_index))
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index)
        message += key.key
        self.previous_command = "update"
        self.send(self._NETKEY_UPDATE, message)

    def netkey_delete(self, netkey_index=0):
        message = mt.KeyIndex.pack(netkey_index)
        self.previous_command = "delete"
        self.send(self._NETKEY_DELETE, message)

    def netkey_get(self):
        self.send(self._NETKEY_GET)

    def model_app_bind(self, element_address, appkey_index, model_id):
        message = bytearray()
        message += struct.pack("<H", element_address)
        message += mt.KeyIndex.pack(appkey_index)
        message += model_id.pack()
        self.previous_command = "bind"
        self.send(self._MODEL_APP_BIND, message)

    def model_app_unbind(self, element_address, appkey_index, model_id):
        message = bytearray()
        message += struct.pack("<H", element_address)
        message += mt.KeyIndex.pack(appkey_index)
        message += model_id.pack()
        self.previous_command = "unbind"
        self.send(self._MODEL_APP_UNBIND, message)

    def model_app_get(self, element_address, model_id):
        message = bytearray()
        message += struct.pack("<H", element_address)
        message += model_id.pack()
        if model_id.company_id:
            self.send(self._VENDOR_MODEL_APP_GET, message)
        else:
            self.send(self._SIG_MODEL_APP_GET, message)

    def model_publication_set(self, element_address, model_id, publish):

        # FIXME: Hack to retain address
        self._tmp_address = publish.address.to_json()

        message = bytearray()
        message += struct.pack("<H", element_address)
        message += publish.pack()
        message += model_id.pack()

        # If it's a bytearray, it's a virtual address.
        if isinstance(publish.address, mt.VirtualAddress):
            self.send(self._MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET, message)
        else:
            self.send(self._MODEL_PUBLICATION_SET, message)

    def model_publication_get(self, element_address, model_id):
        message = bytearray()
        message += struct.pack("<H", element_address)
        message += model_id.pack()
        self.send(self._MODEL_PUBLICATION_GET, message)

    def subscription_message_get(self, element_address, address, model_id):
        message = bytearray()
        message += struct.pack("<H", element_address)

        # FIXME: Hack to retain address
        if isinstance(address, int):
            self._tmp_address = "%04x" % (address)
            address = struct.pack("<H", address)
        else:
            self._tmp_address = address.hex()
        message += address
        message += model_id.pack()
        return message

    def model_subscription_add(self, element_address, address, model_id):
        message = self.subscription_message_get(element_address, address,
                                                model_id)
        self.previous_command = "add"
        # If it's a bytearray, it's a virtual address
        if isinstance(address, bytearray):
            self.send(self._MODEL_SUBSCRIPTION_VIRTUAL_ADDRESS_ADD, message)
        else:
            self.send(self._MODEL_SUBSCRIPTION_ADD, message)

    def model_subscription_delete(self, element_address, address, model_id):
        message = self.subscription_message_get(element_address, address,
                                                model_id)
        self.previous_command = "delete"
        # If it's a bytearray, it's a virtual address
        if isinstance(address, bytearray):
            self.send(self._MODEL_SUBSCRIPTION_VIRTUAL_ADDRESS_DELETE, message)
        else:
            self.send(self._MODEL_SUBSCRIPTION_DELETE, message)

    def model_subscription_overwrite(self, element_address, address, model_id):
        message = self.subscription_message_get(element_address, address,
                                                model_id)
        self.previous_command = "overwrite"
        # If it's a bytearray, it's a virtual address
        if isinstance(address, bytearray):
            self.send(self._MODEL_SUBSCRIPTION_VIRTUAL_ADDRESS_OVERWRITE,
                      message)
        else:
            self.send(self._MODEL_SUBSCRIPTION_OVERWRITE, message)

    def model_subscription_delete_all(self, element_address, model_id):
        message = bytearray()
        message += struct.pack("<H", element_address)
        message += model_id.pack()
        self.previous_command = "delete_all"
        self.send(self._MODEL_SUBSCRIPTION_DELETE_ALL, message)

    def model_subscription_get(self, element_address, model_id):
        message = bytearray()
        message += struct.pack("<H", element_address)
        message += model_id.pack()
        if model_id.company_id:
            self.send(self._VENDOR_MODEL_SUBSCRIPTION_GET, message)
        else:
            self.send(self._SIG_MODEL_SUBSCRIPTION_GET, message)

    def key_refresh_phase_get(self, netkey_index):
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index)
        self.send(self._KEY_REFRESH_PHASE_GET, message)

    def key_refresh_phase_set(self, netkey_index):
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index)
        self.send(self._KEY_REFRESH_PHASE_GET, message)

    def node_reset(self):
        self.send(self._NODE_RESET)

    def beacon_get(self):
        self.send(self._BEACON_GET)

    def beacon_set(self, state):
        message = bytearray(struct.pack("<B", int(state)))
        self.send(self._BEACON_SET, message)

    def default_ttl_get(self):
        self.send(self._DEFAULT_TTL_GET)

    def default_ttl_set(self, ttl):
        message = bytearray(struct.pack("<B", ttl))
        self.send(self._DEFAULT_TTL_SET, message)

    def gatt_proxy_get(self):
        self.send(self._GATT_PROXY_GET)

    def gatt_proxy_set(self, state):
        message = bytearray(struct.pack("<B", int(state)))
        self.send(self._GATT_PROXY_SET, message)

    def relay_get(self):
        self.send(self._RELAY_GET)

    def relay_set(self,
                  state,
                  retransmit_count=0,
                  retransmit_interval_steps=0):
        retransmit = (retransmit_count & 0x07 |
                      (retransmit_interval_steps & 0x1f) << 3)
        message = bytearray(struct.pack("<BB", int(state), retransmit))
        self.send(self._RELAY_SET, message)

    def friend_get(self):
        self.send(self._FRIEND_GET)

    def friend_set(self, state):
        message = bytearray()
        message += struct.pack("<B", int(state))
        self.send(self._FRIEND_SET, message)

    def heartbeat_publication_get(self):
        self.send(self._HEARTBEAT_PUBLICATION_GET)

    def heartbeat_publication_set(self,
                                  dst,
                                  count,
                                  period,
                                  ttl=64,
                                  feature_bitfield=0,
                                  netkey_index=0):
        message = bytearray()
        message += struct.pack("<HBBBHH", dst, log2b(count), log2b(period),
                               ttl, feature_bitfield, netkey_index)
        self.send(self._HEARTBEAT_PUBLICATION_SET, message)

    def heartbeat_subscription_get(self):
        self.send(self._HEARTBEAT_SUBSCRIPTION_GET)

    def heartbeat_subscription_set(self, src, dst, period):
        message = bytearray()
        message += struct.pack("<HHB", src, dst, log2b(period))
        self.send(self._HEARTBEAT_SUBSCRIPTION_SET, message)

    def low_power_node_polltimeout_get(self, lpn_address):
        message = bytearray()
        message += struct.pack("<H", lpn_address)
        self.send(self._LOW_POWER_NODE_POLLTIMEOUT_GET, message)

    def network_transmit_get(self):
        self.send(self._NETWORK_TRANSMIT_GET)

    def network_transmit_set(self, count=0, interval_steps=1):
        message = bytearray()
        message += struct.pack(
            "<B",
            mt.NetworkTransmit(count, interval_steps).pack())
        self.send(self._NETWORK_TRANSMIT_SET, message)

    def node_identity_get(self, netkey_index):
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index)
        self.send(self._NODE_IDENTITY_GET, message)

    def node_identity_set(self, netkey_index, state):
        message = bytearray()
        message += mt.KeyIndex.pack(netkey_index)
        message += struct.pack("<B", int(state))
        self.send(self._NODE_IDENTITY_SET, message)

    def node_get(self, src):
        for node in self.prov_db.nodes:
            if node.unicast_address == src:
                return node

        raise RuntimeError("Could not find node %04x" % (src))

    def model_get(self, element_address, model_id):
        for node in self.prov_db.nodes:
            beg = node.unicast_address
            end = beg + len(node.elements)
            if beg <= element_address < end:
                index = int(element_address) - int(node.unicast_address)
                element = node.elements[index]
                for model in element.models:
                    if model.model_id == model_id:
                        return model

        raise RuntimeError(
            "Could not find model %r with element address %04x" %
            (model_id, element_address))

    def db_save(self):
        self.prov_db.store()

    def __composition_data_status_handler(self, opcode, message):
        page = message.data[0]
        data = message.data[1:]
        data = mt.CompositionData().unpack(data)
        node = self.node_get(message.meta["src"])
        node.cid = data["cid"]
        node.pid = data["pid"]
        node.vid = data["vid"]
        node.crpl = data["crpl"]
        node.features = mt.NodeFeatures(**data["features"])
        node.elements = data["elements"]
        self.db_save()
        self.logger.info("Received composition data (page 0x%02x): %s", page,
                         json.dumps(data, indent=2))

    def __heartbeat_publication_status_handler(self, opcode, message):
        status, dst, count_log, period_log, ttl, features, netkey_index = struct.unpack(
            "<BHBBBHH", message.data)
        status = AccessStatus(status)
        self.logger.info("Heartbeat publication status: %s", status)
        if status == AccessStatus.SUCCESS:
            count = 0 if count_log == 0 else 2**(count_log - 1)
            period = 0 if period_log == 0 else 2**(period_log - 1)
            features = {
                "relay": (features & (1 << 0)) > 0,
                "proxy": (features & (1 << 1)) > 0,
                "friend": (features & (1 << 2)) > 0,
                "lowPower": (features & (1 << 3)) > 0
            }
            self.logger.info(
                "Heartbeat publication state: " +
                "dst: %04x, count: %d, period: %ds, features: %r, subnet: %d",
                dst, count, period, features, netkey_index)

    def __appkey_status_handler(self, opcode, message):
        status = AccessStatus(message.data[0])
        netkey_index, appkey_index = mt.KeyIndex.unpack(message.data[1:4])
        self.logger.info("Appkey status: %s", status)
        if status in [
                AccessStatus.SUCCESS, AccessStatus.KEY_INDEX_ALREADY_STORED
        ]:
            node = self.node_get(message.meta["src"])
            if self.previous_command == "add" and appkey_index not in node.app_keys:
                node.app_keys.append(appkey_index)
            elif self.previous_command == "update":
                pass
            elif self.previous_command == "delete" and appkey_index in node.app_keys:
                node.app_keys.remove(appkey_index)
            self.db_save()
            self.logger.info(
                "Appkey %s %d succeded for subnet %d at node %04x",
                self.previous_command, appkey_index, netkey_index,
                message.meta["src"])

    def __appkey_list_handler(self, opcode, message):
        status, netkey_index = struct.unpack("<BH", message.data[0:3])
        status = AccessStatus(status)
        self.logger.info("Appkey list status: %s", status)
        if status == AccessStatus.SUCCESS:
            appkeys = ConfigurationClient._unpack_key_ind(message.data[3:])
            node = self.node_get(message.meta["src"])

            # Add newly discovered keys
            for index in appkeys:
                if ((self.prov_db.app_keys[index].bound_net_key == netkey_index
                     and index not in node.app_keys)):
                    node.app_keys.append(index)

            # Remove old dead keys
            for index in node.app_keys:
                if ((self.prov_db.app_keys[index].bound_net_key == netkey_index
                     and index not in appkeys)):
                    node.app_keys.remove(index)

            self.db_save()
            self.logger.info("Node %04x has appkeys: %r", message.meta["src"],
                             appkeys)

    def __beacon_status_handler(self, opcode, message):
        state = struct.unpack("<B", message.data)[0]
        state = bool(state)
        node = self.node_get(message.meta["src"])
        node.secure_network_beacon = mt.FeatureState(state)
        self.db_save()
        self.logger.info("Secure network beacon state: %s",
                         "on" if state else "off")

    def __default_ttl_status_handler(self, opcode, message):
        ttl = struct.unpack("<B", message.data)[0]
        node = self.node_get(message.meta["src"])
        node.default_TTL = mt.TTL(ttl)
        self.db_save()
        self.logger.info("Default TTL: %d", ttl)

    def __friend_status_handler(self, opcode, message):
        state = struct.unpack("<B", message.data)[0]
        node = self.node_get(message.meta["src"])
        node.features.friend = mt.FeatureState(state)
        self.db_save()
        self.logger.info("Friend state: %r", state)

    def __gatt_proxy_status_handler(self, opcode, message):
        state = struct.unpack("<B", message.data)[0]
        node = self.node_get(message.meta["src"])
        node.features.proxy = mt.FeatureState(state)
        self.db_save()
        self.logger.info("Proxy state: %r", state)

    def __key_refresh_phase_status_handler(self, opcode, message):
        status, netkey_index, phase = struct.unpack("<BHB", message.data)
        status = AccessStatus(status)
        self.logger.info("Key refresh status: %s", status)
        if status == AccessStatus.SUCCESS:
            node = self.node_get(message.meta["src"])
            for key in node.net_keys:
                if key.index == netkey_index:
                    key.phase = mt.KeyRefreshPhase(phase)
                    self.db_save()
                    self.logger.info(
                        "Netkey phase %r for subnet %r at node %04x", phase,
                        netkey_index, node.unicastAddress)

    def __model_publication_status_handler(self, opcode, message):
        status, element_address = struct.unpack("<BH", message.data[0:3])
        status = AccessStatus(status)
        self.logger.info("Model publication status: %s", status)
        if status == AccessStatus.SUCCESS:
            publish = mt.Publish.unpack(message.data[3:10])
            if self._tmp_address:
                publish.address = mt.any_address(self._tmp_address)
                self._tmp_address = None
            model = self.model_get(element_address,
                                   mt.ModelId.unpack(message.data[10:]))
            model.publish = publish
            self.db_save()
            self.logger.info(
                "Publication status for model %r at element %r to %r",
                model.model_id, element_address, publish)

    def __model_subscription_status_handler(self, opcode, message):
        status, element_address = struct.unpack("<BH", message.data[0:3])
        status = AccessStatus(status)
        self.logger.info("Model subscription status: %s", status)
        if status == AccessStatus.SUCCESS:
            # address = struct.unpack("<H", message[3:5])
            address = self._tmp_address
            self._tmp_address = None
            model_id = mt.ModelId.unpack(message.data[5:])
            model = self.model_get(element_address, model_id)
            if address not in model.subscribe:
                model.subscribe.append(mt.group_address(address))

            self.db_save()
            self.logger.info(
                "Added subscription %r to model %r at element %04x", address,
                model_id, element_address)

    def __network_transmit_status_handler(self, opcode, message):
        node = self.node_get(message.meta["src"])
        node.network_transmit = mt.NetworkTransmit.unpack(message.data[0])
        self.db_save()
        self.logger.info("Network transmit state: %r", node.network_transmit)

    def __relay_status_handler(self, opcode, message):
        state, retransmit = struct.unpack("<BB", message.data)
        node = self.node_get(message.meta["src"])
        node.relay_retransmit = mt.RelayRetransmit.unpack(retransmit)
        node.features.relay = mt.FeatureState(state)
        self.db_save()
        self.logger.info("Relay state: %r, count: %d, interval: %d", state,
                         node.relay_retransmit.count,
                         node.relay_retransmit.interval)

    def __heartbeat_subscription_status_handler(self, opcode, message):
        status, src, dst, period_log, count_log, min_hops, max_hops = struct.unpack(
            "<BHHBBBB", message.data)
        status = AccessStatus(status)
        self.logger.info("Heartbeat subscription status: %s", status)
        if status == AccessStatus.SUCCESS:
            if period_log == 0:
                period = 0
            else:
                period = 2**(period_log - 1)
            self.logger.info(
                "Heartbeat subscription state: " +
                "src: %04x, dst: %04x, period: %ds, count: %d, min/max: %d/%d",
                src, dst, period, count_log, min_hops, max_hops)

    def __model_app_status_handler(self, opcode, message):
        status, element_address, appkey_index = struct.unpack(
            "<BHH", message.data[:5])
        status = AccessStatus(status)
        model_id = mt.ModelId.unpack(message.data[5:])
        element_address = mt.UnicastAddress(element_address)
        appkey_index = mt.KeyIndex(appkey_index)

        self.logger.info("Model app bind status: %s", status)
        if status == AccessStatus.SUCCESS:
            model = self.model_get(element_address, model_id)

            # Was last command a bind or unbind?
            if self.previous_command == "bind" and appkey_index not in model.bind:
                model.bind.append(appkey_index)
            elif appkey_index in model.bind:
                model.bind.remove(appkey_index)

            self.db_save()
            self.logger.info("Appkey %s %d to model %r at %04x",
                             self.previous_command, appkey_index, model_id,
                             element_address)

    def __netkey_status_handler(self, opcode, message):
        status, netkey_index = struct.unpack("<BH", message.data)
        netkey_index = mt.KeyIndex(netkey_index)
        status = AccessStatus(status)
        self.logger.info("Netkey status: %s", status)
        if status == AccessStatus.SUCCESS:
            node = self.node_get(message.meta["src"])
            if netkey_index not in node.net_keys:
                node.net_keys.append(netkey_index)
            self.db_save()
            self.logger.info("Added subnet %d to node %04x", netkey_index,
                             message.meta["src"])

    def __netkey_list_handler(self, opcode, message):
        node = self.node_get(message.meta["src"])
        if len(message.data) > 0:
            node.net_keys = ConfigurationClient._unpack_key_ind(message.data)
            self.db_save()
        self.logger.info("Node %04x has subnets %r", message.meta["src"],
                         node.net_keys)

    def __node_identity_status_handler(self, opcode, message):
        status, netkey_index, identity = struct.unpack("<BHB", message.data)
        status = AccessStatus(status)
        self.logger.info("Node identity status: %s", status)
        if status == AccessStatus.SUCCESS:
            if identity == 0:
                state = "stopped"
            elif identity == 1:
                state = "running"
            else:
                state = "not supported"

            self.logger.info("Current node identity for subnet %d is %s",
                             netkey_index, state)

    def __node_reset_status_handler(self, opcode, message):
        self.logger.info("Node %04x was reset", message.meta["src"])

    def __model_sig_app_list_handler(self, opcode, message):
        status, element_address, model_id = struct.unpack(
            "<BHH", message.data[0:5])
        status = AccessStatus(status)
        self.logger.info("SIG Model App List status: %s", status)
        if status == AccessStatus.SUCCESS:
            appkeys = ConfigurationClient._unpack_key_ind(message.data[5:])
            model = self.model_get(element_address, mt.ModelId(model_id))
            model.bind = appkeys
            self.db_save()
            self.logger.info("SIG model %04x has appkey(s) %r bound", model_id,
                             appkeys)

    def __model_sig_subscription_list_handler(self, opcode, message):
        status, element_address, model_id = struct.unpack(
            "<BHH", message.data[0:5])
        status = AccessStatus(status)
        self.logger.info("SIG Model Subscription List status: %s", status)
        if status == AccessStatus.SUCCESS:
            if len(message.data) > 5:
                addresses = struct.unpack(
                    "<" + "H" * (len(message.data[5:]) // 2), message.data[5:])
                addresses = [mt.group_address(a) for a in addresses]
            else:
                addresses = []

            model = self.model_get(element_address, mt.ModelId(model_id))
            model.subscribe = addresses
            self.db_save()
            self.logger.info("SIG model %04x has addresse(s) %r bound",
                             model_id, addresses)

    def __vendor_model_app_list_handler(self, opcode, message):
        status, element_address, company_id, model_id = struct.unpack(
            "<BHHH", message.data[0:7])
        status = AccessStatus(status)
        self.logger.info("SIG Model App List status: %s", status)
        if status == AccessStatus.SUCCESS:
            appkeys = ConfigurationClient._unpack_key_ind(message.data[7:])
            model = self.model_get(
                element_address,
                mt.ModelId(model_id=model_id, company_id=company_id))
            model.bind = appkeys
            self.db_save()
            self.logger.info(
                "Vendor model %04x, company ID %04x has appkey(s) %r bound",
                model_id, company_id, appkeys)

    def __vendor_model_subscription_list_handler(self, opcode, message):
        status, element_address, company_id, model_id = struct.unpack(
            "<BHHH", message.data[0:7])
        status = AccessStatus(status)
        self.logger.info("SIG Model Subscription List status: %s", status)
        if status == AccessStatus.SUCCESS:
            if len(message.data) > 7:
                addresses = struct.unpack(
                    "<" + "H" * (len(message.data[7:]) / 2), message.data[7:])
                addresses = [mt.group_address(a) for a in addresses]
            else:
                addresses = []

            model = self.model_get(
                element_address,
                mt.ModelId(model_id=model_id, company_id=company_id))
            model.subscribe = addresses
            self.db_save()
            self.logger.info(
                "Vendor model %04x, company ID %04x has addresse(s) %r bound",
                model_id, company_id, addresses)

    def __low_power_node_polltimeout_status_handler(self, opcode, message):
        # We append a 0x00 to the bytearray to unpack the PollTimeout as a uint32_t
        lpn_address, poll_timeout = struct.unpack("<HI",
                                                  message.data + bytearray(1))

        if poll_timeout > 0:
            # Multiply to get in units of milliseconds (ref. 3.6.5.3, PollTimeout is in 100ms units)
            poll_timeout *= 100
            self.logger.info("Low power node %04x poll timeout %d ms.",
                             lpn_address, poll_timeout)
        else:
            self.logger.info(
                "Node is not a Friend not or LPN address %04x not a known LPN address",
                lpn_address)
Exemple #5
0
class GenericOnOffClient(Model):
    GENERIC_ON_OFF_SET = Opcode(0x8202, None, "Generic OnOff Set")
    GENERIC_ON_OFF_SET_UNACKNOWLEDGED = Opcode(
        0x8203, None, "Generic OnOff Set Unacknowledged")
    GENERIC_ON_OFF_GET = Opcode(0x8201, None, "Generic OnOff Get")
    GENERIC_ON_OFF_STATUS = Opcode(0x8204, None, "Generic OnOff Status")

    ACK_TIMER_TIMEOUT = 0.3
    RETRANSMISSIONS_LIMIT = 10

    retry_log = []

    def __init__(self,
                 db=None,
                 generic_on_off_client_status_cb=None,
                 set_ack_failed_cb=None):
        self.db = db
        self.opcodes = [(self.GENERIC_ON_OFF_STATUS,
                         self.__generic_on_off_status_handler)]
        self.__tid = 0

        self.__generic_on_off_client_status_cb = generic_on_off_client_status_cb
        self.__client_set_ack_failed_cb = set_ack_failed_cb

        self.timers = {}

        super(GenericOnOffClient, self).__init__(self.opcodes)

    def set(self, value, transition_time_ms=0, delay_ms=0, ack=True):
        message = bytearray()
        message += struct.pack("<BB", int(value > 0), self._tid)

        if transition_time_ms > 0:
            message += TransitionTime.pack(transition_time_ms, delay_ms)

        if ack:
            self.send(self.GENERIC_ON_OFF_SET, message)
            self.start_timer(self.address_handle, value)

        else:
            self.send(self.GENERIC_ON_OFF_SET_UNACKNOWLEDGED, message)

    def start_timer(self, address_handle, value):

        if address_handle not in self.timers:
            self.timers[address_handle] = {"attempt": 0, "value": value}
        else:
            self.cancel_timer(address_handle)

        if value is not self.timers[address_handle]["value"]:
            self.timers[address_handle]["attempt"] = 0

        self.timers[address_handle]["value"] = value

        timeout = (self.timers[address_handle]["attempt"] +
                   1) * self.ACK_TIMER_TIMEOUT
        self.timers[address_handle]["timer"] = threading.Timer(
            timeout, self.set_ack_timedout, args=(
                address_handle,
                value,
            ))
        self.timers[address_handle]["timer"].start()

    def cancel_timer(self, address_handle):
        if hasattr(self.timers[address_handle]["timer"], "cancel"):
            self.timers[address_handle]["timer"].cancel()

    def set_ack_timedout(self, address_handle, value):
        # SET ACK FAILED - RESET AND DO CB
        self.logger.info("Set ACK {} Timedout".format(address_handle))

        if self.timers[address_handle]["attempt"] < self.RETRANSMISSIONS_LIMIT:
            self.publish_set(0, address_handle)
            self.timers[address_handle]["attempt"] += 1
            self.set(value)
            self.logger.info("Resending SET {} attempts: {}".format(
                address_handle, self.timers[address_handle]["attempt"]))
            self.retry_log.append({
                "attempt":
                self.timers[address_handle]["attempt"],
                "address_handle":
                address_handle
            })
            return

        self.retry_log.append({
            "attempt": "FAILED",
            "address_handle": address_handle
        })

        self.timers[address_handle]["attempt"] = 0
        try:
            self.__client_set_ack_failed_cb(address_handle)
        except Exception as e:
            self.logger.error("Set ACK callback {} failed: {}".format(
                self.__client_set_ack_failed_cb, e))

    def get(self):
        self.send(self.GENERIC_ON_OFF_GET)

    @property
    def _tid(self):
        tid = self.__tid
        self.__tid += 1
        if self.__tid >= 255:
            self.__tid = 0
        return tid

    def __generic_on_off_status_handler(self, opcode, message):
        value = int(message.data.hex()[1])
        src = message.meta["src"]

        self.logger.info("Status Present OnOff: " +
                         ("on" if value > 0 else "off"))

        # stop timer..
        address_handle = self.db.find_address_handle(src)
        if address_handle in self.timers:
            self.logger.info(
                "Got status from src: {} - canceling timer".format(src))
            self.cancel_timer(address_handle)
            self.timers[address_handle]["attempt"] = 0

        try:
            self.__generic_on_off_client_status_cb(value, src)
        except:
            self.logger.error(
                "Failed trying generic onoff client status callback: ",
                self.__generic_on_off_client_status_cb)

    def __str__(self):
        return json.dumps({"model": "GenericOnOffClient", "tid": self.__tid})

    def to_json(self):
        return {"model": "GenericOnOffClient", "tid": self.__tid}
Exemple #6
0
class GenericOnOffServer(Model):
    GENERIC_ON_OFF_SET = Opcode(0x8202, None, "Generic OnOff Set")
    GENERIC_ON_OFF_SET_UNACKNOWLEDGED = Opcode(
        0x8203, None, "Generic OnOff Set Unacknowledged")
    GENERIC_ON_OFF_GET = Opcode(0x8201, None, "Generic OnOff Get")
    GENERIC_ON_OFF_STATUS = Opcode(0x8204, None, "Generic OnOff Status")

    def __init__(self, generic_on_off_server_set_cb=None, db=None):
        self.opcodes = [(self.GENERIC_ON_OFF_SET,
                         self.__generic_on_off_server_set_event_handler),
                        (self.GENERIC_ON_OFF_SET_UNACKNOWLEDGED,
                         self.__generic_on_off_server_set_unack_event_handler),
                        (self.GENERIC_ON_OFF_GET,
                         self.__generic_on_off_server_get_event_handler)]
        self.__tid = 0
        self.__generic_on_off_server_set_cb = generic_on_off_server_set_cb
        self.db = db

        super(GenericOnOffServer, self).__init__(self.opcodes)

    @property
    def _tid(self):
        tid = self.__tid
        self.__tid += 1
        if self.__tid >= 255:
            self.__tid = 0
        return tid

    def __generic_on_off_server_set_event_handler(self, opcode, message):

        # UNPACK MESSAGE
        value = int(message.data.hex()[1])
        src = message.meta["src"]
        appkey_handle = message.meta["appkey_handle"]

        logstr = " Set ack OnOff: " + ("on" if value > 0 else "off")
        self.logger.info(logstr)

        # SEND STATUS RESPONSE
        address_handle = self.db.find_address_handle(src)
        self.publish_set(appkey_handle, address_handle)
        self.status(value)

        # DO ACTION
        try:
            self.__generic_on_off_server_set_cb(value, src)
        except:
            self.logger.error(
                "Failed trying generic on off server set callback: ",
                self.__generic_on_off_server_set_unack_cb)

    def __generic_on_off_server_set_unack_event_handler(self, opcode, message):
        # UNPACK MESSAGE
        value = int(message.data.hex()[1])
        src = message.meta["src"]

        logstr = " Set unack OnOff: " + ("on" if value > 0 else "off")
        self.logger.info(logstr)

        try:
            self.__generic_on_off_server_set_cb(value, src)
        except:
            self.logger.error(
                "Failed trying generic on off server set unack callback: ",
                self.__generic_on_off_server_set_unack_cb)

    def __generic_on_off_server_get_event_handler(self, opcode, message):
        self.logger.info("Server GET event {} {}".format(opcode, message.data))

    def status(self, value):
        # TODO what are transition_time_ms and delay_ms doing?

        message = bytearray()
        message += struct.pack("<?", int(value > 0))

        self.send(self.GENERIC_ON_OFF_STATUS, message)

    def __str__(self):
        return json.dumps({"model": "GenericOnOffServer", "tid": self.__tid})

    def to_json(self):
        return {"model": "GenericOnOffServer", "tid": self.__tid}
Exemple #7
0
class NodeConfigClient(Model):

    NODE_CONFIG_GET = Opcode(0x8205, None, "Node Config Get")
    NODE_CONFIG_SET = Opcode(0x8206, None, "Node Config Set")
    NODE_CONFIG_SET_UNACKNOWLEDGED = Opcode(0x8207, None,
                                            "Node Config Set Unacknowledged")
    NODE_CONFIG_STATUS = Opcode(0x8208, None, "Node Config Status")

    def __init__(self):
        self.opcodes = [(self.NODE_CONFIG_STATUS,
                         self.__generic_level_status_handler)]
        self.__tid = 0
        super(NodeConfigClient, self).__init__(self.opcodes)

    def set(self,
            valueCh1,
            valueCh2,
            valueCh3,
            valueCh4,
            lpnState,
            lpnPollIntervall,
            lpnDelay,
            lpnReceiveWindow,
            transition_time_ms=0,
            delay_ms=0,
            ack=True):
        message = bytearray()

        valueCh1bin = int(valueCh1) << 4
        message_1 = valueCh1bin | int(valueCh2)
        valueCh3bin = int(valueCh3 << 4)
        message_2 = valueCh3bin | int(valueCh4)
        lpnStatebin = int(lpnState << 4)
        message_3 = lpnStatebin | int(lpnPollIntervall)
        lpnDelaybin = int(lpnDelay << 4)
        message_4 = lpnDelaybin | int(lpnReceiveWindow)

        message += struct.pack("<BBBBB", int(message_4), int(message_3),
                               int(message_2), int(message_1), self._tid)

        if transition_time_ms > 0:
            message += TransitionTime.pack(transition_time_ms, delay_ms)

        logstr1 = "Send Data1: " + str(message_1)
        logstr2 = "Send Data2: " + str(message_2)
        logstr3 = "Send Data3: " + str(message_3)
        logstr4 = "Send Data4: " + str(message_4)

        logstr = "Send Data: " + str(message)
        self.logger.info(logstr1)
        self.logger.info(logstr2)
        self.logger.info(logstr3)
        self.logger.info(logstr4)
        self.logger.info(logstr)

        if ack:
            self.send(self.NODE_CONFIG_SET, message)
        else:
            self.send(self.NODE_CONFIG_SET_UNACKNOWLEDGED, message)

    def get(self):
        self.send(self.NODE_CONFIG_GET)

    @property
    def _tid(self):
        tid = self.__tid
        self.__tid += 1
        if self.__tid >= 255:
            self.__tid = 0
        return tid

    def __generic_level_status_handler(self, opcode, message):
        logstr = "Present Level: " + str(message.data)
        if len(message.data) > 1:
            logstr += " Target Level: " + str(message.data)

        if len(message.data) == 3:
            logstr += " Remaining time: %d ms" % (TransitionTime.decode(
                message.data[2]))

        self.logger.info(logstr)