예제 #1
0
    def test_failed_modbus_connect(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_failure
                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_connect.yaml',
                                               MQTT_TOPIC_PREFIX)
                self.connect_attempts = 0
                m.modbus_connect_retries = 3
                m.modbus_reconnect_sleep_interval = 0.1

                def replacement():
                    # Normally this would kill the program. We don't want that.
                    pass

                m.modbus_connection_failed = replacement
                m.connect()
                self.assertEqual(self.connect_attempts, 3)

                mock_mqtt().username_pw_set.assert_called_with(
                    'brengis', 'pranto')
                mock_mqtt().connect.assert_called_with('kroopit', 1885, 60)
                m._on_connect(None, None, None, rc=1)
                # TODO implement some more thorough checks?
                mock_mqtt().publish.assert_no_call(
                    MQTT_TOPIC_PREFIX + '/modbus4mqtt',
                    'modbus4mqtt v{} connected.'.format(
                        modbus4mqtt.version.version))
예제 #2
0
    def test_default_table(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register

                m = modbus4mqtt.mqtt_interface(
                    'kroopit', 1885, 'brengis', 'pranto',
                    './tests/test_default_table.yaml', MQTT_TOPIC_PREFIX)
                m.connect()
                self.modbus_tables['holding'][1] = 1
                self.modbus_tables['holding'][2] = 2
                self.modbus_tables['input'][1] = 3
                m.poll()

                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/holding',
                                                    1,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/input',
                                                    3,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/default',
                                                    2,
                                                    retain=False)
예제 #3
0
    def test_json_key(self):
        # Validating the various json_key rules is among the responsibilities of test_register_validation() below.
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register

                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_json_key.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()

                self.modbus_tables['holding'][0] = 0
                self.modbus_tables['holding'][1] = 1
                self.modbus_tables['holding'][2] = 2
                self.modbus_tables['holding'][3] = 3
                m.poll()

                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish2',
                                                    '{"A": 3}',
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish',
                                                    '{"A": 1, "B": "off"}',
                                                    retain=True)
예제 #4
0
    def test_scale(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register
                mock_modbus(
                ).set_value.side_effect = self.write_modbus_register
                self.modbus_tables['holding'][1] = 1
                self.modbus_tables['holding'][2] = 2
                self.modbus_tables['holding'][3] = 3

                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_scale.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()
                m.poll()

                mock_modbus().add_monitor_register.assert_any_call(
                    'holding', 1)
                mock_modbus().add_monitor_register.assert_any_call(
                    'holding', 2)
                mock_modbus().add_monitor_register.assert_any_call(
                    'holding', 2)
                mock_mqtt().publish.assert_any_call(
                    'prefix/scale_up_no_value_map', 2, retain=False)
                mock_mqtt().publish.assert_any_call(
                    'prefix/scale_down_no_value_map', 1, retain=False)
                mock_mqtt().publish.assert_any_call(
                    'prefix/scale_with_value_map', 'b', retain=False)

                msg = MQTTMessage(topic=bytes(
                    MQTT_TOPIC_PREFIX + '/scale_up_no_value_map_set', 'utf-8'))
                msg.payload = b'6'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][1], 3)

                msg = MQTTMessage(topic=bytes(
                    MQTT_TOPIC_PREFIX +
                    '/scale_down_no_value_map_set', 'utf-8'))
                msg.payload = b'1'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][2], 2)

                msg = MQTTMessage(topic=bytes(
                    MQTT_TOPIC_PREFIX + '/scale_with_value_map_set', 'utf-8'))
                msg.payload = b'b'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][3], 3)
예제 #5
0
    def test_mask(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register
                mock_modbus(
                ).set_value.side_effect = self.write_modbus_register
                self.modbus_tables['holding'][1] = 0xFEF0

                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_mask.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()
                m.poll()

                mock_mqtt().publish.assert_any_call('prefix/mask_no_scale',
                                                    0xFE00,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call('prefix/no_mask_no_scale',
                                                    0xFEF0,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call('prefix/mask_and_scale',
                                                    0xFE,
                                                    retain=False)

                msg = MQTTMessage(
                    topic=bytes(MQTT_TOPIC_PREFIX +
                                '/mask_and_scale_set', 'utf-8'))
                msg.payload = b'255'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][1], 0xFFF0)

                # This register isn't scaled, so 255 won't fill up the MSB.
                msg = MQTTMessage(
                    topic=bytes(MQTT_TOPIC_PREFIX +
                                '/mask_no_scale_set', 'utf-8'))
                msg.payload = b'255'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][1], 0x00F0)

                # This should set the LSb of the MSB.
                msg = MQTTMessage(
                    topic=bytes(MQTT_TOPIC_PREFIX +
                                '/mask_no_scale_set', 'utf-8'))
                msg.payload = b'511'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][1], 0x01F0)
예제 #6
0
    def test_value_map(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register

                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_value_map.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()
                self.modbus_tables['holding'][1] = 1
                self.modbus_tables['holding'][2] = 2
                self.modbus_tables['holding'][3] = 1
                m.poll()

                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/value_map_absent',
                                                    1,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/value_map_present',
                                                    'b',
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(
                    MQTT_TOPIC_PREFIX + '/value_map_misinterpretation',
                    'on',
                    retain=False)
                mock_mqtt().publish.reset_mock()

                # This value is outside the map, check it comes through in raw form
                self.modbus_tables['holding'][2] = 3
                self.modbus_tables['holding'][3] = 2
                m.poll()

                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/value_map_present',
                                                    3,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(
                    MQTT_TOPIC_PREFIX + '/value_map_misinterpretation',
                    'off',
                    retain=False)
                mock_mqtt().publish.reset_mock()
예제 #7
0
    def test_connect(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_connect.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()

                mock_mqtt().username_pw_set.assert_called_with(
                    'brengis', 'pranto')
                mock_mqtt().connect.assert_called_with('kroopit', 1885, 60)

                m._on_connect(None, None, None, rc=0)
                mock_mqtt().publish.assert_called_with(
                    MQTT_TOPIC_PREFIX + '/modbus4mqtt', 'hi')
                mock_mqtt().subscribe.assert_called_with(MQTT_TOPIC_PREFIX +
                                                         '/subscribe')
                mock_mqtt().subscribe.assert_no_call(MQTT_TOPIC_PREFIX +
                                                     '/publish')
예제 #8
0
    def test_invalid_address(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register

                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_value_map.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()
                # Don't set up address 2, so the register polling it throws an exception
                self.modbus_tables['holding'][1] = 1
                m.poll()
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/value_map_absent',
                                                    1,
                                                    retain=False)
                mock_mqtt().publish.assert_no_call(MQTT_TOPIC_PREFIX +
                                                   '/value_map_present',
                                                   'b',
                                                   retain=False)
예제 #9
0
    def test_set_topics(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                with self.assertLogs() as mock_logger:
                    mock_modbus(
                    ).get_value.side_effect = self.read_modbus_register
                    mock_modbus(
                    ).set_value.side_effect = self.write_modbus_register
                    self.modbus_tables['holding'][1] = 1
                    self.modbus_tables['holding'][2] = 2

                    m = modbus4mqtt.mqtt_interface(
                        'kroopit', 1885, 'brengis', 'pranto',
                        './tests/test_set_topics.yaml', MQTT_TOPIC_PREFIX)
                    m.connect()

                    mock_modbus().add_monitor_register.assert_any_call(
                        'holding', 1)
                    mock_modbus().add_monitor_register.assert_any_call(
                        'holding', 2)

                    mock_mqtt().username_pw_set.assert_called_with(
                        'brengis', 'pranto')
                    mock_mqtt().connect.assert_called_with('kroopit', 1885, 60)

                    m._on_connect(None, None, None, rc=0)
                    mock_mqtt().subscribe.assert_any_call(MQTT_TOPIC_PREFIX +
                                                          '/no_value_map')
                    mock_mqtt().subscribe.assert_any_call(MQTT_TOPIC_PREFIX +
                                                          '/value_map')
                    mock_mqtt().publish.reset_mock()

                    self.assertEqual(self.modbus_tables['holding'][2], 2)
                    # Publish a human-readable value invalid for this topic, because there's no value map.
                    msg = MQTTMessage(
                        topic=bytes(MQTT_TOPIC_PREFIX +
                                    '/no_value_map', 'utf-8'))
                    msg.payload = b'a'
                    m._on_message(None, None, msg)
                    self.assertIn(
                        "Failed to convert register value for writing. Bad/missing value_map? Topic: no_value_map, Value: b'a'",
                        mock_logger.output[-1])
                    self.assertEqual(self.modbus_tables['holding'][2], 2)

                    self.assertEqual(self.modbus_tables['holding'][1], 1)
                    # Publish a raw value valid for this topic
                    msg = MQTTMessage(
                        topic=bytes(MQTT_TOPIC_PREFIX +
                                    '/no_value_map', 'utf-8'))
                    msg.payload = b'3'
                    m._on_message(None, None, msg)
                    self.assertEqual(self.modbus_tables['holding'][1], 3)

                    # Publish a human-readable value valid for this topic, because there's a value map.
                    self.assertEqual(self.modbus_tables['holding'][2], 2)
                    msg = MQTTMessage(
                        topic=bytes(MQTT_TOPIC_PREFIX + '/value_map', 'utf-8'))
                    msg.payload = b'a'
                    m._on_message(None, None, msg)
                    self.assertEqual(self.modbus_tables['holding'][2], 1)

                    # Publish a raw value invalid for this topic, ensure the value doesn't change.
                    msg = MQTTMessage(
                        topic=bytes(MQTT_TOPIC_PREFIX + '/value_map', 'utf-8'))
                    msg.payload = bytes([3])
                    m._on_message(None, None, msg)
                    self.assertIn(
                        "Value not in value_map. Topic: value_map, value:",
                        mock_logger.output[-1])
                    self.assertEqual(self.modbus_tables['holding'][2], 1)

                    # Publish a value that can't decode as utf-8, ensure the value doesn't change.
                    msg = MQTTMessage(
                        topic=bytes(MQTT_TOPIC_PREFIX + '/value_map', 'utf-8'))
                    msg.payload = b'\xff'
                    m._on_message(None, None, msg)
                    self.assertIn(
                        "Failed to decode MQTT payload as UTF-8. Can't compare it to the value_map for register",
                        mock_logger.output[-1])
                    self.assertEqual(self.modbus_tables['holding'][2], 1)
예제 #10
0
    def test_pub_on_change(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success

                mock_modbus().get_value.side_effect = self.read_modbus_register

                m = modbus4mqtt.mqtt_interface(
                    'kroopit', 1885, 'brengis', 'pranto',
                    './tests/test_pub_on_change.yaml', MQTT_TOPIC_PREFIX)
                m.connect()

                self.modbus_tables['holding'][1] = 85
                self.modbus_tables['holding'][2] = 86
                self.modbus_tables['holding'][3] = 87
                m.poll()

                # Check that every topic was published initially
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/pub_on_change_false',
                                                    85,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/pub_on_change_true',
                                                    86,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/pub_on_change_absent',
                                                    87,
                                                    retain=False)
                mock_mqtt().publish.reset_mock()

                self.modbus_tables['holding'][1] = 15
                self.modbus_tables['holding'][2] = 16
                self.modbus_tables['holding'][3] = 17
                m.poll()

                # Check that every topic was published if everything changed
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/pub_on_change_false',
                                                    15,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/pub_on_change_true',
                                                    16,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/pub_on_change_absent',
                                                    17,
                                                    retain=False)
                mock_mqtt().publish.reset_mock()

                m.poll()

                # Check that the register with pub_only_on_change: true does not re-publish
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/pub_on_change_false',
                                                    15,
                                                    retain=False)
                mock_mqtt().publish.assert_no_call(MQTT_TOPIC_PREFIX +
                                                   '/pub_on_change_true',
                                                   16,
                                                   retain=False)
                mock_mqtt().publish.assert_no_call(MQTT_TOPIC_PREFIX +
                                                   '/pub_on_change_absent',
                                                   17,
                                                   retain=False)
예제 #11
0
    def test_word_order_setting(self):
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register
                mock_modbus(
                ).set_value.side_effect = self.write_modbus_register

                # Default value
                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_type.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()
                mock_modbus.assert_any_call(
                    '192.168.1.90',
                    502,
                    5,
                    scan_batching=None,
                    variant=None,
                    word_order=modbus4mqtt.modbus_interface.WordOrder.HighLow)

        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register
                mock_modbus(
                ).set_value.side_effect = self.write_modbus_register

                # Explicit HighLow
                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_word_order.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()
                mock_modbus.assert_any_call(
                    '192.168.1.90',
                    502,
                    5,
                    scan_batching=None,
                    variant=None,
                    word_order=modbus4mqtt.modbus_interface.WordOrder.HighLow)

        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register
                mock_modbus(
                ).set_value.side_effect = self.write_modbus_register

                # Explicit HighLow
                m = modbus4mqtt.mqtt_interface(
                    'kroopit', 1885, 'brengis', 'pranto',
                    './tests/test_word_order_low_high.yaml', MQTT_TOPIC_PREFIX)
                m.connect()
                mock_modbus.assert_any_call(
                    '192.168.1.90',
                    502,
                    5,
                    scan_batching=None,
                    variant=None,
                    word_order=modbus4mqtt.modbus_interface.WordOrder.LowHigh)
예제 #12
0
    def test_type(self):
        # Validating the various json_key rules is among the responsibilities of test_register_validation() below.
        with patch('paho.mqtt.client.Client') as mock_mqtt:
            with patch('modbus4mqtt.modbus_interface.modbus_interface'
                       ) as mock_modbus:
                mock_modbus().connect.side_effect = self.connect_success
                mock_modbus().get_value.side_effect = self.read_modbus_register
                mock_modbus(
                ).set_value.side_effect = self.write_modbus_register

                m = modbus4mqtt.mqtt_interface('kroopit', 1885, 'brengis',
                                               'pranto',
                                               './tests/test_type.yaml',
                                               MQTT_TOPIC_PREFIX)
                m.connect()

                self.modbus_tables['holding'][0] = 0
                self.modbus_tables['holding'][1] = 32767
                self.modbus_tables['holding'][2] = 32768
                self.modbus_tables['holding'][3] = 65535
                m.poll()

                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish_uint16_1',
                                                    0,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish_uint16_2',
                                                    32767,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish_uint16_3',
                                                    32768,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish_uint16_4',
                                                    65535,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish_int16_1',
                                                    0,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish_int16_2',
                                                    32767,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish_int16_3',
                                                    -32768,
                                                    retain=False)
                mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX +
                                                    '/publish_int16_4',
                                                    -1,
                                                    retain=False)

                msg = MQTTMessage(
                    topic=bytes(MQTT_TOPIC_PREFIX +
                                '/publish_int16_1_set', 'utf-8'))
                msg.payload = b'-2'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][0], 65534)

                msg = MQTTMessage(
                    topic=bytes(MQTT_TOPIC_PREFIX +
                                '/publish_int16_1_set', 'utf-8'))
                msg.payload = b'2'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][0], 2)

                msg = MQTTMessage(
                    topic=bytes(MQTT_TOPIC_PREFIX +
                                '/publish_uint16_1_set', 'utf-8'))
                msg.payload = b'65533'
                m._on_message(None, None, msg)
                self.assertEqual(self.modbus_tables['holding'][0], 65533)