<reponame>nugget/python-insteonplm<filename>insteonplm/messages/extendedSend.py
"""INSTEON Extended Send Message Type 0x62."""

from insteonplm.constants import (
    MESSAGE_ACK,
    MESSAGE_NAK,
    MESSAGE_SEND_EXTENDED_MESSAGE_0X62,
    MESSAGE_SEND_EXTENDED_MESSAGE_RECEIVED_SIZE,
    MESSAGE_SEND_EXTENDED_MESSAGE_SIZE,
)
from insteonplm.address import Address
from insteonplm.messages.message import Message
from insteonplm.messages.messageFlags import MessageFlags
from insteonplm.messages.userdata import Userdata


class ExtendedSend(Message):
    """Send an INSTEON Extended message.

    address: A device hex address in any form.
    cmd1: Required command in the cmd1 field
    cmd2: Required command in the cmd2 field
    **kwarg: otional userdata dictionary with the following keys:
        'd1' - User data byte 0 as byte or int
        'd2' - user data byte 1 as byte or int
        ...
        'd14' - user data byte 14 as byte or int
        'd1' to 'd14' are assumed to equal 0x00 unless explicitly set
    """

    _code = MESSAGE_SEND_EXTENDED_MESSAGE_0X62
    _sendSize = MESSAGE_SEND_EXTENDED_MESSAGE_SIZE
    _receivedSize = MESSAGE_SEND_EXTENDED_MESSAGE_RECEIVED_SIZE
    _description = "INSTEON Standard Message Send"

    def __init__(
        self, address, commandtuple, userdata, cmd2=None, flags=0x10, acknak=None
    ):
        """Init the ExtendedSend message class."""
        if commandtuple.get("cmd1", None) is not None:
            cmd1 = commandtuple["cmd1"]
            cmd2out = commandtuple["cmd2"]
        else:
            raise ValueError

        if cmd2 is not None:
            cmd2out = cmd2

        if cmd2out is None:
            raise ValueError

        self._address = Address(address)
        self._messageFlags = MessageFlags(flags)
        self._messageFlags.extended = 1
        self._cmd1 = cmd1
        self._cmd2 = cmd2out
        self._userdata = Userdata(userdata)
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create a message from a raw byte stream."""
        userdata_dict = Userdata(rawmessage[8:22])
        return ExtendedSend(
            rawmessage[2:5],
            {"cmd1": rawmessage[6], "cmd2": rawmessage[7]},
            userdata_dict,
            flags=rawmessage[5],
            acknak=rawmessage[22:23],
        )

    # pylint: disable=protected-access
    @classmethod
    def template(
        cls,
        address=None,
        commandtuple=None,
        userdata=None,
        cmd2=-1,
        flags=None,
        acknak=None,
    ):
        """Create a message template used for callbacks."""
        msgraw = bytearray([0x02, cls._code])
        msgraw.extend(bytes(cls._receivedSize))
        msg = ExtendedSend.from_raw_message(msgraw)

        if commandtuple:
            cmd1 = commandtuple.get("cmd1")
            cmd2out = commandtuple.get("cmd2")
        else:
            cmd1 = None
            cmd2out = None

        if cmd2 is not -1:
            cmd2out = cmd2

        msg._address = Address(address)
        msg._messageFlags = MessageFlags(flags)
        msg._messageFlags.extended = 1
        msg._cmd1 = cmd1
        msg._cmd2 = cmd2out
        msg._userdata = Userdata.template(userdata)
        msg._acknak = acknak
        return msg

    @property
    def address(self):
        """Return the address of the device."""
        return self._address

    @property
    def cmd1(self):
        """Return the cmd1 property of the message."""
        return self._cmd1

    @property
    def cmd2(self):
        """Return the cmd2 property of the message."""
        return self._cmd2

    @property
    def userdata(self):
        """Return the extended message user data."""
        return self._userdata

    @property
    def flags(self):
        """Return the message flags."""
        return self._messageFlags

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @acknak.setter
    def acknak(self, val):
        """Set the ACK/NAK byte."""
        if val in [None, 0x06, 0x15]:
            self._acknak = val
        else:
            raise ValueError

    @property
    def isack(self):
        """Test if message is an ACK."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if message is a NAK."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def set_checksum(self):
        """Set byte 14 of the userdata to a checksum value."""
        data_sum = self.cmd1 + self.cmd2
        for i in range(1, 14):
            data_sum += self._userdata["d{:d}".format(i)]
        chksum = 0xFF - (data_sum & 0xFF) + 1
        self._userdata["d14"] = chksum & 0xff

    def set_crc(self):
        """Set Userdata[13] and Userdata[14] to the CRC value."""
        data = self.bytes[6:20]
        crc = int(0)
        for b in data:
            # pylint: disable=unused-variable
            for bit in range(0, 8):
                fb = b & 0x01
                fb = fb ^ 0x01 if (crc & 0x8000) else fb
                fb = fb ^ 0x01 if (crc & 0x4000) else fb
                fb = fb ^ 0x01 if (crc & 0x1000) else fb
                fb = fb ^ 0x01 if (crc & 0x0008) else fb
                crc = ((crc << 1) | fb) & 0xFFFF
                b = b >> 1
        self._userdata["d13"] = (crc >> 8) & 0xFF
        self._userdata["d14"] = crc & 0xFF

    def _message_properties(self):
        return [
            {"address": self._address},
            {"flags": self._messageFlags},
            {"cmd1": self._cmd1},
            {"cmd2": self._cmd2},
            {"userdata": self._userdata},
            {"acknak": self._acknak},
        ]

"""INSTEON Extended Send Message Type 0x62."""

from insteonplm.constants import (
    MESSAGE_ACK,
    MESSAGE_NAK,
    MESSAGE_SEND_EXTENDED_MESSAGE_0X62,
    MESSAGE_SEND_EXTENDED_MESSAGE_RECEIVED_SIZE,
    MESSAGE_SEND_EXTENDED_MESSAGE_SIZE,
)
from insteonplm.address import Address
from insteonplm.messages.message import Message
from insteonplm.messages.messageFlags import MessageFlags
from insteonplm.messages.userdata import Userdata


class ExtendedSend(Message):
    """Send an INSTEON Extended message.

    address: A device hex address in any form.
    cmd1: Required command in the cmd1 field
    cmd2: Required command in the cmd2 field
    **kwarg: otional userdata dictionary with the following keys:
        'd1' - User data byte 0 as byte or int
        'd2' - user data byte 1 as byte or int
        ...
        'd14' - user data byte 14 as byte or int
        'd1' to 'd14' are assumed to equal 0x00 unless explicitly set
    """

    _code = MESSAGE_SEND_EXTENDED_MESSAGE_0X62
    _sendSize = MESSAGE_SEND_EXTENDED_MESSAGE_SIZE
    _receivedSize = MESSAGE_SEND_EXTENDED_MESSAGE_RECEIVED_SIZE
    _description = "INSTEON Standard Message Send"

    def __init__(
        self, address, commandtuple, userdata, cmd2=None, flags=0x10, acknak=None
    ):
        """Init the ExtendedSend message class."""
        if commandtuple.get("cmd1", None) is not None:
            cmd1 = commandtuple["cmd1"]
            cmd2out = commandtuple["cmd2"]
        else:
            raise ValueError

        if cmd2 is not None:
            cmd2out = cmd2

        if cmd2out is None:
            raise ValueError

        self._address = Address(address)
        self._messageFlags = MessageFlags(flags)
        self._messageFlags.extended = 1
        self._cmd1 = cmd1
        self._cmd2 = cmd2out
        self._userdata = Userdata(userdata)
        self._acknak = self._setacknak(acknak)

    @classmethod
    def from_raw_message(cls, rawmessage):
        """Create a message from a raw byte stream."""
        userdata_dict = Userdata(rawmessage[8:22])
        return ExtendedSend(
            rawmessage[2:5],
            {"cmd1": rawmessage[6], "cmd2": rawmessage[7]},
            userdata_dict,
            flags=rawmessage[5],
            acknak=rawmessage[22:23],
        )

    # pylint: disable=protected-access
    @classmethod
    def template(
        cls,
        address=None,
        commandtuple=None,
        userdata=None,
        cmd2=-1,
        flags=None,
        acknak=None,
    ):
        """Create a message template used for callbacks."""
        msgraw = bytearray([0x02, cls._code])
        msgraw.extend(bytes(cls._receivedSize))
        msg = ExtendedSend.from_raw_message(msgraw)

        if commandtuple:
            cmd1 = commandtuple.get("cmd1")
            cmd2out = commandtuple.get("cmd2")
        else:
            cmd1 = None
            cmd2out = None

        if cmd2 is not -1:
            cmd2out = cmd2

        msg._address = Address(address)
        msg._messageFlags = MessageFlags(flags)
        msg._messageFlags.extended = 1
        msg._cmd1 = cmd1
        msg._cmd2 = cmd2out
        msg._userdata = Userdata.template(userdata)
        msg._acknak = acknak
        return msg

    @property
    def address(self):
        """Return the address of the device."""
        return self._address

    @property
    def cmd1(self):
        """Return the cmd1 property of the message."""
        return self._cmd1

    @property
    def cmd2(self):
        """Return the cmd2 property of the message."""
        return self._cmd2

    @property
    def userdata(self):
        """Return the extended message user data."""
        return self._userdata

    @property
    def flags(self):
        """Return the message flags."""
        return self._messageFlags

    @property
    def acknak(self):
        """Return the ACK/NAK byte."""
        return self._acknak

    @acknak.setter
    def acknak(self, val):
        """Set the ACK/NAK byte."""
        if val in [None, 0x06, 0x15]:
            self._acknak = val
        else:
            raise ValueError

    @property
    def isack(self):
        """Test if message is an ACK."""
        return self._acknak is not None and self._acknak == MESSAGE_ACK

    @property
    def isnak(self):
        """Test if message is a NAK."""
        return self._acknak is not None and self._acknak == MESSAGE_NAK

    def set_checksum(self):
        """Set byte 14 of the userdata to a checksum value."""
        data_sum = self.cmd1 + self.cmd2
        for i in range(1, 14):
            data_sum += self._userdata["d{:d}".format(i)]
        chksum = 0xFF - (data_sum & 0xFF) + 1
        self._userdata["d14"] = chksum & 0xff

    def set_crc(self):
        """Set Userdata[13] and Userdata[14] to the CRC value."""
        data = self.bytes[6:20]
        crc = int(0)
        for b in data:
            # pylint: disable=unused-variable
            for bit in range(0, 8):
                fb = b & 0x01
                fb = fb ^ 0x01 if (crc & 0x8000) else fb
                fb = fb ^ 0x01 if (crc & 0x4000) else fb
                fb = fb ^ 0x01 if (crc & 0x1000) else fb
                fb = fb ^ 0x01 if (crc & 0x0008) else fb
                crc = ((crc << 1) | fb) & 0xFFFF
                b = b >> 1
        self._userdata["d13"] = (crc >> 8) & 0xFF
        self._userdata["d14"] = crc & 0xFF

    def _message_properties(self):
        return [
            {"address": self._address},
            {"flags": self._messageFlags},
            {"cmd1": self._cmd1},
            {"cmd2": self._cmd2},
            {"userdata": self._userdata},
            {"acknak": self._acknak},
        ]
INSTEON Extended Send Message Type 0x62. Send an INSTEON Extended message.      address: A device hex address in any form.     cmd1: Required command in the cmd1 field     cmd2: Required command in the cmd2 field     **kwarg: otional userdata dictionary with the following keys:         'd1' - User data byte 0 as byte or int         'd2' - user data byte 1 as byte or int         ...         'd14' - user data byte 14 as byte or int         'd1' to 'd14' are assumed to equal 0x00 unless explicitly set Init the ExtendedSend message class. Create a message from a raw byte stream. # pylint: disable=protected-access Create a message template used for callbacks. Return the address of the device. Return the cmd1 property of the message. Return the cmd2 property of the message. Return the extended message user data. Return the message flags. Return the ACK/NAK byte. Set the ACK/NAK byte. Test if message is an ACK. Test if message is a NAK. Set byte 14 of the userdata to a checksum value. Set Userdata[13] and Userdata[14] to the CRC value. # pylint: disable=unused-variable