コード例 #1
0
    def send_serial(self, data):
        """
        Send what is assumed a Modbus serial packet. If the serial port is
        NOT open (so self.ser == None), this this routine tries to open it.

        If the open fails, a ConnectError is raised, which is the SDK apps'
        signal to quit/abort running.

        :param bytes data: the Modbus serial message, which could be either
                           Modbus/RTU or ASCII
        :return: the response data, which might be b'' (empty/no response)
        """
        import logging

        if self.ser is None:
            self.logger.info("Open serial port:{}".format(self.serial_name))
            try:
                self.ser = serial.Serial(port=self.serial_name,
                                         baudrate=self.serial_baud,
                                         bytesize=8,
                                         parity=self.serial_parity,
                                         stopbits=1,
                                         timeout=1,
                                         xonxoff=0,
                                         rtscts=0)
                self.ser.setDTR(True)
                # self.ser.setRTS(True)

            except serial.SerialException:
                self.ser = None
                self.logger.exception("Open of serial port failed")
                raise ConnectionError("Open of serial port failed")

            self.logger.info("Serial Protocol:{}".format(self.serial_protocol))

        if self.logger.getEffectiveLevel() <= logging.DEBUG:
            if self.serial_protocol == IA_PROTOCOL_MBASC:
                # for ASCII, just print as string
                self.logger.debug("ASC-REQ:{}".format(data))
            else:  # for RTU, we want HEX form
                logger_buffer_dump(self.logger, "RTU-REQ", data)

        self.ser.write(data)

        # we have 1 second response timeout in the Serial() open
        time.sleep(0.25)
        response = self.ser.read(256)

        if self.logger.getEffectiveLevel() <= logging.DEBUG:
            if response is None or response == b'':
                self.logger.debug("SER-RSP:None/No response")
            elif self.serial_protocol == IA_PROTOCOL_MBASC:
                # for ASCII, just print as string
                self.logger.debug("ASC-RSP:{}".format(response))
            else:  # for RTU, we want HEX form
                logger_buffer_dump(self.logger, "RTU-RSP", response)

        return response
コード例 #2
0
    def handle_data(self, data):
        """

        :param bytes data:
        :return:
        """
        logger_buffer_dump(self.logger, "TCP-REQ", data)
        self.last_activity = time.time()

        response = None

        try:
            # parse the MB/TCP request
            self.modbus.set_request(data, self.host_protocol)
            # self.logger.debug("REQ:{}".format(self.modbus.attrib))

        except (ModbusBadForm, ModbusBadChecksum):
            # this could be a bad setting
            self.logger.warning("Bad Modbus Form")
            return None

        # retrieve as MB/RTU request
        modbus_rtu = self.modbus.get_request(self.serial_protocol)
        # self.logger.debug("SER:{}".format(modbus_rtu))

        # if Serial() open fails, we'll throw ConnectionError, which this
        # code assumes the CALLER of handle_data() handles
        response = self.send_serial(modbus_rtu)
        if response and len(response):
            # parse the MB/RTU in
            try:
                self.modbus.set_response(response, self.serial_protocol)
                response = self.modbus.get_response(self.host_protocol)

            except ModbusBadForm:
                # this could be a bad setting
                self.logger.warning("Bad Modbus Form")
                response = None

            except ModbusBadChecksum:
                # likely line noise or loose wire
                self.logger.warning("Bad Checksum")
                response = None

        else:
            # in truth, if client is:
            #  Modbus/TCP - should return exception 0x0B
            # Modbus/RTU - no response
            self.logger.debug("No response")
            response = None

        if response is None:
            # then re-form as the correct err response, which may be None
            response = self.modbus.get_no_response_error(self.host_protocol)

        logger_buffer_dump(self.logger, "TCP-RSP", response)
        # else was in error, hang-up
        return response
コード例 #3
0
def _fetch_content_length(data, logger=None):
    """
    handle the situation where response might be either:

    form A = 'content-length: 12\n\r\n\r\n"IBR1150LPE"'
    form B = 'content-length: 189'

    :param data:
    :return:
    """
    if isinstance(data, bytes):
        data = data.decode('ascii')
    data = data.strip()

    if logger is not None:
        logger_buffer_dump(logger, 'length', data, show_ascii=True)

    if not data.startswith('content-length: '):
        # then it is mal-formed!
        return None, None

    # chop off the 'content-length: ' with the ending space
    data = data[16:]
    # form A now = '12\n\r\n\r\n"IBR1150LPE"'
    # form B now = '189'

    offset = data.find('\n')
    if offset <= 0:
        # then form B like, so data_length = '189'
        data_length = data.strip()
        all_data = ""

    else:
        # else is form A like
        #  data_length = '12'
        #  data = '"IBR1150LPE"'
        data_length = data[:offset].strip()
        all_data = data[offset + 4:].strip()

    try:
        data_length = int(data_length)
    except ValueError:
        # then bad length field!
        if logger is not None:
            logger.error("CSClient() content length not INT()")
        return None, None

    if logger is not None:
        logger.debug("data_length={}".format(data_length))
        if len(all_data):
            logger_buffer_dump(logger, 'ready', all_data, show_ascii=True)
        # else is empty

    # change to be REMAINING data
    data_length -= len(all_data)

    return data_length, all_data
コード例 #4
0
    def delete(self, base, query=''):
        """
        Send a delete request.

        :param str base: 'tree' element path, like '/status/gpio/LED_USB1_G'
        :param str query: the text
        :return str:
        """
        self._logger.debug("CSClient() DEL={}".format(base))
        self.last_url = "delete\n{}\n{}\n".format(base, query)
        logger_buffer_dump(self._logger,
                           'DELETE',
                           self.last_url,
                           show_ascii=True)
        return self._dispatch(self.last_url)
コード例 #5
0
    def get(self, base, query='', tree=0):
        """
        Send a get request.
        - example: self.state = self.client.get('/status/gpio/%s' % self.name)

        :param str base: 'tree' element path, like '/status/gpio/LED_USB1_G'
        :param str query: ???
        :param int tree: ???
        """
        self._logger.debug("CSClient() GET={}".format(base))
        self.last_url = "get\n{}\n{}\n{}\n".format(base, query, tree)
        if self.show_rsp:
            logger_buffer_dump(self._logger,
                               'GET',
                               self.last_url,
                               show_ascii=True)
        return self._dispatch(self.last_url)
コード例 #6
0
    def append(self, base, value, query=''):
        """
        Send an append request.

        :param str base: 'tree' element path, like '/status/gpio/LED_USB1_G'
        :param value: the payload, as JSON - such as {"LED_USB1_G":1}
        :type value: str or dict
        :param str query: ???
        :return str:
        """
        value = json.dumps(value).replace(' ', '')
        self._logger.debug("CSClient() APPEND={} data={}".format(base, value))
        self.last_url = "post\n{}\n{}\n{}\n".format(base, query, value)
        logger_buffer_dump(self._logger,
                           'APPEND',
                           self.last_url,
                           show_ascii=True)
        return self._dispatch(self.last_url)
コード例 #7
0
    def put(self, base, value, query='', tree=0):
        """
        Send a put request.
        - example: self.client.put('/control/gpio', {self.name: self.state})

        :param str base: 'tree' element path, like '/status/gpio/LED_USB1_G'
        :param value: the payload, as JSON - such as {"LED_USB1_G":1}
        :type value: str or dict
        :param str query: ???
        :param int tree: ???
        """
        if isinstance(value, dict):
            # convert dictionary to JSON without white-space
            value = json.dumps(value).replace(' ', '')

        self._logger.debug("CSClient() PUT={} data={}".format(base, value))
        self.last_url = "put\n{}\n{}\n{}\n{}\n".format(base, query, tree,
                                                       value)
        logger_buffer_dump(self._logger, 'PUT', self.last_url, show_ascii=True)
        return self._dispatch(self.last_url)
コード例 #8
0
    def _dispatch(self, cmd):
        """
        Send the command and return the response.

        How the router actually responds is a bit fuzzy, and I've seen
        several conflicting solutions which assume a more line-by-line
        behavior, which I am not seeing in 6.1 (Mar-16)

        :param str cmd: the prepared command
        :return str:
        """
        self.last_reply = None
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.connect((self.DEFAULT_HOST, self.DEFAULT_PORT))
            sock.sendall(cmd.encode('ascii'))

            data = sock.recv(self.DEFAULT_SIZE).decode('ascii').strip()
            """ :type data: str """
            # logger_buffer_dump(self._logger, 'header', data, show_ascii=True)
            if data.startswith('status: ok'):

                if len(data) > 18:
                    # on some occasions, 'content-length: ' will be appended!
                    # the length 18 is a bit arbitrary. The desired status
                    # is "status: ok\n", so 11 bytes
                    self._logger.debug(
                        "Special STATUS() packing, len={}".format(len(data)))
                    data = data[11:].strip()

                else:
                    data = sock.recv(self.DEFAULT_SIZE)

                if self.show_rsp:
                    data_expected, self.last_reply = _fetch_content_length(
                        data, self._logger)
                else:
                    data_expected, self.last_reply = _fetch_content_length(
                        data, None)

                retry = 0
                while data_expected > 0:
                    # data = sock.recv(self.DEFAULT_SIZE).decode('ascii')
                    data = sock.recv(self.DEFAULT_SIZE).decode('ascii')
                    if self.show_rsp:
                        logger_buffer_dump(self._logger,
                                           'loop',
                                           data,
                                           show_ascii=True)
                    if data.startswith('\r\n\r\n'):
                        # this was form B, so second block of data started
                        # with the 2 dummy lines
                        data = data[4:]

                    if len(data) == 0:
                        if retry > 3:
                            self._logger.debug("CSClient len(data)==0, BREAK")
                            break
                        retry += 1
                        self._logger.debug(
                            "CSClient() len(data)==0, retry={}".format(retry))
                    self.last_reply += data
                    data_expected -= len(data)
                    if data_expected:
                        # only show is NOT zero
                        self._logger.debug(
                            "pst expected={}".format(data_expected))

        if len(self.last_reply) >= 2:
            # reply might be nothing, but if string, we make sure to handle
            if self.last_reply[0] == '\"':
                # remove leading/trailing quotes, so make "\"IBR1100LPE\""
                # into "IBR1100LPE"
                self.last_reply = unquote_string(self.last_reply)

            elif self.last_reply[0] == '{':
                # convert JSON string to dict(), like:
                # '{"enable_gps_keepalive": false, "pwd_enabled": false,
                #   "enabled": true}'

                # self._logger.debug("final{}".format(self.last_reply))
                try:
                    self.last_reply = json.loads(self.last_reply)

                except ValueError:
                    # some idiotic API calls return malformed JSON such as
                    # "{'enabled': true}" so not double quotes!
                    self.last_reply = self.last_reply.replace("\'", "\"")

                    # if it still fails, then assume worse error
                    self.last_reply = json.loads(self.last_reply)

        return self.last_reply
コード例 #9
0
    def test_logger_buffer_dump(self):
        """
        Test buffer to lines function
        :return:
        """
        from cp_lib.buffer_dump import logger_buffer_dump

        tests = [
            {
                "msg": "fruit",
                "dat": "Apple",
                "asc": False,
                "exp": ['dump:fruit, len=5', '[000] 41 70 70 6C 65']
            },
            {
                "msg": "fruit",
                "dat": "Apple",
                "asc": True,
                "exp": ['dump:fruit, len=5', '[000] 41 70 70 6C 65 \'Apple\'']
            },
            {
                "msg": "fruit",
                "dat": b"Apple",
                "asc": False,
                "exp": ['dump:fruit, len=5 bytes()', '[000] 41 70 70 6C 65']
            },
            {
                "msg":
                "fruit",
                "dat":
                b"Apple",
                "asc":
                True,
                "exp": [
                    'dump:fruit, len=5 bytes()',
                    '[000] 41 70 70 6C 65 b\'Apple\''
                ]
            },
            {
                "msg":
                "longer",
                "dat":
                "Apple\nIs found in the country of my birth\n",
                "asc":
                False,
                "exp": [
                    'dump:longer, len=42',
                    '[000] 41 70 70 6C 65 0A 49 73 20 66 6F 75 6E 64 20 69',
                    '[016] 6E 20 74 68 65 20 63 6F 75 6E 74 72 79 20 6F 66',
                    '[032] 20 6D 79 20 62 69 72 74 68 0A'
                ]
            },
            {
                "msg":
                "longer",
                "dat":
                "Apple\nIs found in the country of my birth\n",
                "asc":
                True,
                "exp": [
                    'dump:longer, len=42',
                    '[000] 41 70 70 6C 65 0A 49 73 20 66 6F 75 6E 64 20 69' +
                    ' \'Apple\\nIs found i\'',
                    '[016] 6E 20 74 68 65 20 63 6F 75 6E 74 72 79 20 6F 66' +
                    ' \'n the country of\'',
                    '[032] 20 6D 79 20 62 69 72 74 68 0A \' my birth\\n\''
                ]
            },
            {
                "msg":
                "longer",
                "dat":
                b"Apple\nIs found in the country of my birth\n",
                "asc":
                False,
                "exp": [
                    'dump:longer, len=42 bytes()',
                    '[000] 41 70 70 6C 65 0A 49 73 20 66 6F 75 6E 64 20 69',
                    '[016] 6E 20 74 68 65 20 63 6F 75 6E 74 72 79 20 6F 66',
                    '[032] 20 6D 79 20 62 69 72 74 68 0A'
                ]
            },
            {
                "msg":
                "longer",
                "dat":
                b"Apple\nIs found in the country of my birth\n",
                "asc":
                True,
                "exp": [
                    'dump:longer, len=42 bytes()',
                    '[000] 41 70 70 6C 65 0A 49 73 20 66 6F 75 6E 64 20 69' +
                    ' b\'Apple\\nIs found i\'',
                    '[016] 6E 20 74 68 65 20 63 6F 75 6E 74 72 79 20 6F 66' +
                    ' b\'n the country of\'',
                    '[032] 20 6D 79 20 62 69 72 74 68 0A b\' my birth\\n\''
                ]
            },
            {
                "msg":
                "Modbus",
                "dat":
                "\x01\x1F\x00\x01\x02\x03\x04\x05\x06\x08\x09",
                "asc":
                False,
                "exp": [
                    'dump:Modbus, len=11',
                    '[000] 01 1F 00 01 02 03 04 05 06 08 09'
                ]
            },
            {
                "msg":
                "Modbus",
                "dat":
                b"\x01\x1F\x00\x01\x02\x03\x04\x05\x06\x08\x09",
                "asc":
                False,
                "exp": [
                    'dump:Modbus, len=11 bytes()',
                    '[000] 01 1F 00 01 02 03 04 05 06 08 09'
                ]
            },
            {
                "msg":
                "Modbus+",
                "asc":
                False,
                "dat":
                "\x01\x1F\x00\x01\x02\x03\x04\x05\x06\x08\x09\x00\x01\x02\x03\x04\x05\x06\x08",
                "exp": [
                    'dump:Modbus+, len=19',
                    '[000] 01 1F 00 01 02 03 04 05 06 08 09 00 01 02 03 04',
                    '[016] 05 06 08'
                ]
            },
            {
                "msg":
                "Modbus+",
                "asc":
                False,
                "dat":
                b"\x01\x1F\x00\x01\x02\x03\x04\x05\x06\x08\x09\x00\x01\x02\x03\x04\x05\x06\x08",
                "exp": [
                    'dump:Modbus+, len=19 bytes()',
                    '[000] 01 1F 00 01 02 03 04 05 06 08 09 00 01 02 03 04',
                    '[016] 05 06 08'
                ]
            },
        ]

        logging.info("")

        logger = logging.getLogger('unitest')
        logger.setLevel(logging.DEBUG)

        for test in tests:
            # logging.debug("Test:{}".format(test))

            logger_buffer_dump(logger, test['msg'], test['dat'], test['asc'])
            logging.info("")

        return