From 41f5010386c3c083f46f8eadf789be3d4a7d6f3b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:22:40 +0200 Subject: [PATCH 001/109] Delete .github/workflows directory --- .github/workflows/python-app.yml | 37 -------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml deleted file mode 100644 index 053cb48..0000000 --- a/.github/workflows/python-app.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Unit Tests - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.11 - uses: actions/setup-python@v2 - with: - python-version: 3.11 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 nose2 codecov - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with nose2 - run: | - nose2 - codecov --token=${{secrets.CODECOV_TOKEN}} From b0e68e1ddc45cfabcda67afb5e8aa05133c2030e Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:23:24 +0200 Subject: [PATCH 002/109] Delete tests directory --- tests/__init__.py | 0 tests/test.yaml | 12 - tests/test_address_offset.yaml | 7 - tests/test_connect.yaml | 8 - tests/test_default_table.yaml | 11 - tests/test_json_key.yaml | 15 - tests/test_mask.yaml | 17 - tests/test_modbus.py | 392 --------------------- tests/test_mqtt.py | 529 ---------------------------- tests/test_pub_on_change.yaml | 12 - tests/test_retain_flag.yaml | 12 - tests/test_scale.yaml | 22 -- tests/test_set_topics.yaml | 11 - tests/test_type.yaml | 28 -- tests/test_value_map.yaml | 16 - tests/test_word_order.yaml | 7 - tests/test_word_order_low_high.yaml | 7 - 17 files changed, 1106 deletions(-) delete mode 100644 tests/__init__.py delete mode 100644 tests/test.yaml delete mode 100644 tests/test_address_offset.yaml delete mode 100644 tests/test_connect.yaml delete mode 100644 tests/test_default_table.yaml delete mode 100644 tests/test_json_key.yaml delete mode 100644 tests/test_mask.yaml delete mode 100644 tests/test_modbus.py delete mode 100644 tests/test_mqtt.py delete mode 100644 tests/test_pub_on_change.yaml delete mode 100644 tests/test_retain_flag.yaml delete mode 100644 tests/test_scale.yaml delete mode 100644 tests/test_set_topics.yaml delete mode 100644 tests/test_type.yaml delete mode 100644 tests/test_value_map.yaml delete mode 100644 tests/test_word_order.yaml delete mode 100644 tests/test_word_order_low_high.yaml diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test.yaml b/tests/test.yaml deleted file mode 100644 index 1ba6377..0000000 --- a/tests/test.yaml +++ /dev/null @@ -1,12 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - pub_topic: "pub_on_change_false" - pub_only_on_change: false - address: 1 - - pub_topic: "pub_on_change_true" - pub_only_on_change: true - address: 2 - - pub_topic: "pub_on_change_absent" - address: 3 \ No newline at end of file diff --git a/tests/test_address_offset.yaml b/tests/test_address_offset.yaml deleted file mode 100644 index e44434b..0000000 --- a/tests/test_address_offset.yaml +++ /dev/null @@ -1,7 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -address_offset: 1 -registers: - - pub_topic: "publish" - address: 1 \ No newline at end of file diff --git a/tests/test_connect.yaml b/tests/test_connect.yaml deleted file mode 100644 index 2c1db7b..0000000 --- a/tests/test_connect.yaml +++ /dev/null @@ -1,8 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - set_topic: "subscribe" - address: 1 - - pub_topic: "publish" - address: 2 \ No newline at end of file diff --git a/tests/test_default_table.yaml b/tests/test_default_table.yaml deleted file mode 100644 index 753f216..0000000 --- a/tests/test_default_table.yaml +++ /dev/null @@ -1,11 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - pub_topic: "holding" - address: 1 - - pub_topic: "input" - table: "input" - address: 1 - - pub_topic: "default" - address: 2 \ No newline at end of file diff --git a/tests/test_json_key.yaml b/tests/test_json_key.yaml deleted file mode 100644 index ca9d324..0000000 --- a/tests/test_json_key.yaml +++ /dev/null @@ -1,15 +0,0 @@ -ip: 192.168.1.90 -registers: - - pub_topic: "publish" - json_key: "A" - address: 1 - retain: true - - pub_topic: "publish" - address: 2 - json_key: "B" - value_map: - on: 1 - off: 2 - - pub_topic: "publish2" - address: 3 - json_key: "A" \ No newline at end of file diff --git a/tests/test_mask.yaml b/tests/test_mask.yaml deleted file mode 100644 index 6c11598..0000000 --- a/tests/test_mask.yaml +++ /dev/null @@ -1,17 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - set_topic: "mask_no_scale_set" - pub_topic: "mask_no_scale" - address: 1 - mask: 0xFF00 - - set_topic: "no_mask_no_scale_set" - pub_topic: "no_mask_no_scale" - address: 1 - - set_topic: "mask_and_scale_set" - pub_topic: "mask_and_scale" - address: 1 - mask: 0xFF00 - scale: 0.00390625 - diff --git a/tests/test_modbus.py b/tests/test_modbus.py deleted file mode 100644 index 61fbb55..0000000 --- a/tests/test_modbus.py +++ /dev/null @@ -1,392 +0,0 @@ -import os -from collections import namedtuple -import unittest -from unittest.mock import patch, call, Mock -from paho.mqtt.client import MQTTMessage - -from modbus4mqtt import modbus_interface - -def assert_no_call(self, *args, **kwargs): - try: - self.assert_any_call(*args, **kwargs) - except AssertionError: - return - raise AssertionError('Expected %s to not have been called.' % self._format_mock_call_signature(args, kwargs)) - -Mock.assert_no_call = assert_no_call - -MQTT_TOPIC_PREFIX = 'prefix' - -class ModbusTests(unittest.TestCase): - modbusRegister = namedtuple('modbusRegister', 'registers') - - def setUp(self): - modbus_interface.DEFAULT_SCAN_BATCHING = 10 - self.input_registers = self.modbusRegister(registers=list(range(0,modbus_interface.DEFAULT_SCAN_BATCHING*2))) - self.holding_registers = self.modbusRegister(registers=list(range(0,modbus_interface.DEFAULT_SCAN_BATCHING*2))) - - def tearDown(self): - pass - - def read_input_registers(self, start, count, unit): - return self.modbusRegister(registers=self.input_registers.registers[start:start+count]) - - def read_holding_registers(self, start, count, unit): - return self.modbusRegister(registers=self.holding_registers.registers[start:start+count]) - - def write_holding_register(self, address, value, unit): - self.holding_registers.registers[address] = value - - def connect_success(self): - return False - - def connect_failure(self): - return True - - def throw_exception(self, addr, value, unit): - raise ValueError('Oh noooo!') - - def test_connect(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - mock_modbus().read_input_registers.side_effect = self.read_input_registers - mock_modbus().read_holding_registers.side_effect = self.read_holding_registers - - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2) - m.connect() - mock_modbus.assert_called_with('1.1.1.1', 111, RetryOnEmpty=True, framer=modbus_interface.ModbusSocketFramer, retries=1, timeout=1) - - # Confirm registers are added to the correct tables. - m.add_monitor_register('holding', 5) - m.add_monitor_register('input', 6) - self.assertIn(5, m._tables['holding']) - self.assertNotIn(5, m._tables['input']) - self.assertIn(6, m._tables['input']) - self.assertNotIn(6, m._tables['holding']) - - m.poll() - - self.assertEqual(m.get_value('holding', 5), 5) - self.assertEqual(m.get_value('input', 6), 6) - - # Ensure we read a batch of DEFAULT_SCAN_BATCHING registers even though we only - # added one register in each table as interesting - mock_modbus().read_holding_registers.assert_any_call(0, 10, unit=1) - mock_modbus().read_input_registers.assert_any_call(0, 10, unit=1) - - m.set_value('holding', 5, 7) - m.poll() - mock_modbus().write_register.assert_any_call(5, 7, unit=1) - - self.assertRaises(ValueError, m.set_value, 'input', 5, 7) - - mock_modbus().read_holding_registers.reset_mock() - mock_modbus().read_input_registers.reset_mock() - - # Ensure this causes two batched reads per table, one from 0-9 and one from 10-19. - m.add_monitor_register('holding', 15) - m.add_monitor_register('input', 16) - - self.assertIn(15, m._tables['holding']) - self.assertIn(16, m._tables['input']) - - m.poll() - - mock_modbus().read_holding_registers.assert_any_call(0, 10, unit=1) - mock_modbus().read_holding_registers.assert_any_call(10, 10, unit=1) - mock_modbus().read_input_registers.assert_any_call(0, 10, unit=1) - mock_modbus().read_input_registers.assert_any_call(10, 10, unit=1) - - def test_invalid_tables_and_addresses(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2) - m.connect() - - m.add_monitor_register('holding', 5) - m.add_monitor_register('input', 6) - self.assertRaises(ValueError, m.get_value, 'beupe', 5) - self.assertRaises(ValueError, m.add_monitor_register, 'beupe', 5) - self.assertRaises(ValueError, m.get_value, 'holding', 1000) - - def test_write_queuing(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2) - m.connect() - - m.add_monitor_register('holding', 5) - m.add_monitor_register('input', 6) - - # Check that the write queuing works properly. - mock_modbus().write_register.reset_mock() - m._writing = True - m.set_value('holding', 5, 7) - m.poll() - mock_modbus().write_register.assert_not_called() - m._writing = False - m.set_value('holding', 6, 8) - mock_modbus().write_register.assert_any_call(5, 7, unit=1) - mock_modbus().write_register.assert_any_call(6, 8, unit=1) - - def test_exception_on_write(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - with self.assertLogs() as mock_logger: - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2) - m.connect() - - m.add_monitor_register('holding', 5) - # Have the write_register throw an exception - mock_modbus().write_register.side_effect = self.throw_exception - m.set_value('holding', 5, 7) - self.assertIn("ERROR:root:Failed to write to modbus device: Oh noooo!", mock_logger.output[-1]) - - def test_masked_writes(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - mock_modbus().read_input_registers.side_effect = self.read_input_registers - mock_modbus().read_holding_registers.side_effect = self.read_holding_registers - mock_modbus().write_register.side_effect = self.write_holding_register - - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2) - m.connect() - - self.holding_registers.registers[1] = 0 - m.add_monitor_register('holding', 1) - - m.set_value('holding', 1, 0x00FF, 0x00F0) - self.assertEqual(self.holding_registers.registers[1], 0x00F0) - - m.set_value('holding', 1, 0x00FF, 0x000F) - self.assertEqual(self.holding_registers.registers[1], 0x00FF) - - m.set_value('holding', 1, 0xFFFF, 0xFF00) - self.assertEqual(self.holding_registers.registers[1], 0xFFFF) - - m.set_value('holding', 1, 0x0000, 0x0F00) - self.assertEqual(self.holding_registers.registers[1], 0xF0FF) - - def test_scan_batching_of_one(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - mock_modbus().read_input_registers.side_effect = self.read_input_registers - mock_modbus().read_holding_registers.side_effect = self.read_holding_registers - - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2, scan_batching=1) - m.connect() - mock_modbus.assert_called_with('1.1.1.1', 111, RetryOnEmpty=True, framer=modbus_interface.ModbusSocketFramer, retries=1, timeout=1) - - # Confirm registers are added to the correct tables. - m.add_monitor_register('holding', 5) - m.add_monitor_register('holding', 6) - m.add_monitor_register('input', 6) - m.add_monitor_register('input', 7) - - m.poll() - - self.assertEqual(m.get_value('holding', 5), 5) - self.assertEqual(m.get_value('holding', 6), 6) - self.assertEqual(m.get_value('input', 6), 6) - self.assertEqual(m.get_value('input', 7), 7) - - # Ensure each register is scanned with a separate read call. - mock_modbus().read_holding_registers.assert_any_call(5, 1, unit=1) - mock_modbus().read_holding_registers.assert_any_call(6, 1, unit=1) - mock_modbus().read_input_registers.assert_any_call(6, 1, unit=1) - mock_modbus().read_input_registers.assert_any_call(7, 1, unit=1) - - def test_scan_batching_bad_value(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - with self.assertLogs() as mock_logger: - mock_modbus().connect.side_effect = self.connect_success - mock_modbus().read_input_registers.side_effect = self.read_input_registers - mock_modbus().read_holding_registers.side_effect = self.read_holding_registers - - bad_scan_batching = modbus_interface.MAX_SCAN_BATCHING+1 - modbus_interface.modbus_interface('1.1.1.1', 111, 2, scan_batching=bad_scan_batching) - self.assertIn("Bad value for scan_batching: {}. Enforcing maximum value of {}".format(bad_scan_batching, modbus_interface.MAX_SCAN_BATCHING), mock_logger.output[-1]) - - bad_scan_batching = modbus_interface.MIN_SCAN_BATCHING-1 - modbus_interface.modbus_interface('1.1.1.1', 111, 2, scan_batching=bad_scan_batching) - self.assertIn("Bad value for scan_batching: {}. Enforcing minimum value of {}".format(bad_scan_batching, modbus_interface.MIN_SCAN_BATCHING), mock_logger.output[-1]) - - def test_type_conversions(self): - a = modbus_interface._convert_from_type_to_bytes(-1, 'int16') - self.assertEqual(a, b'\xff\xff') - a = modbus_interface._convert_from_bytes_to_type(a, 'int16') - self.assertEqual(a, -1) - a = modbus_interface._convert_from_type_to_bytes(10, 'uint16') - self.assertEqual(a, b'\x00\x0a') - a = modbus_interface._convert_from_bytes_to_type(a, 'uint16') - self.assertEqual(a, 10) - - a = modbus_interface._convert_from_type_to_bytes(-1, 'int32') - self.assertEqual(a, b'\xff\xff\xff\xff') - a = modbus_interface._convert_from_bytes_to_type(a, 'int32') - self.assertEqual(a, -1) - a = modbus_interface._convert_from_type_to_bytes(689876135, 'uint32') - self.assertEqual(a, b'\x29\x1E\xAC\xA7') - a = modbus_interface._convert_from_bytes_to_type(a, 'uint32') - self.assertEqual(a, 689876135) - - a = modbus_interface._convert_from_type_to_bytes(-1, 'int64') - self.assertEqual(a, b'\xff\xff\xff\xff\xff\xff\xff\xff') - a = modbus_interface._convert_from_bytes_to_type(a, 'int64') - self.assertEqual(a, -1) - a = modbus_interface._convert_from_type_to_bytes(5464681683516384647, 'uint64') - self.assertEqual(a, b'\x4B\xD6\x73\x09\xBC\x93\xE5\x87') - a = modbus_interface._convert_from_bytes_to_type(a, 'uint64') - self.assertEqual(a, 5464681683516384647) - - try: - a = modbus_interface._convert_from_bytes_to_type(10, 'float16') - self.fail("Silently accepted an invalid type conversion.") - except: - pass - try: - a = modbus_interface._convert_from_type_to_bytes(10, 'float16') - self.fail("Silently accepted an invalid type conversion.") - except: - pass - try: - a = modbus_interface.type_length('float16') - self.fail("Silently accepted an invalid type conversion.") - except: - pass - - def test_multi_byte_write_counts(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2, word_order=modbus_interface.WordOrder.HighLow) - # m = modbus_interface.modbus_interface('1.1.1.1', 111, 2, word_order=modbus_interface.WordOrder.LowHigh) - m.connect() - mock_modbus.assert_called_with('1.1.1.1', 111, RetryOnEmpty=True, framer=modbus_interface.ModbusSocketFramer, retries=1, timeout=1) - - for i in range(1,11): - m.add_monitor_register('holding', i) - - m.poll() - # Write a value in. - m.set_value('holding', 1, 65535, 0xFFFF, 'uint16') - m.poll() - # Confirm that it only wrote one register. - mock_modbus().write_register.assert_any_call(1, 65535, unit=1) - mock_modbus().reset_mock() - - m.set_value('holding', 1, 689876135, 0xFFFF, 'uint32') - m.poll() - - mock_modbus().write_register.assert_any_call(1, int.from_bytes(b'\x29\x1E','big'), unit=1) - mock_modbus().write_register.assert_any_call(2, int.from_bytes(b'\xAC\xA7','big'), unit=1) - mock_modbus().reset_mock() - - m.set_value('holding', 1, 5464681683516384647, 0xFFFF, 'uint64') - m.poll() - - mock_modbus().write_register.assert_any_call(1, int.from_bytes(b'\x4B\xD6','big'), unit=1) - mock_modbus().write_register.assert_any_call(2, int.from_bytes(b'\x73\x09','big'), unit=1) - mock_modbus().write_register.assert_any_call(3, int.from_bytes(b'\xBC\x93','big'), unit=1) - mock_modbus().write_register.assert_any_call(4, int.from_bytes(b'\xE5\x87','big'), unit=1) - mock_modbus().reset_mock() - - def test_multi_byte_write_counts_LowHigh_order(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2, word_order=modbus_interface.WordOrder.LowHigh) - m.connect() - mock_modbus.assert_called_with('1.1.1.1', 111, RetryOnEmpty=True, framer=modbus_interface.ModbusSocketFramer, retries=1, timeout=1) - - for i in range(1,11): - m.add_monitor_register('holding', i) - m.set_value('holding', 1, 689876135, 0xFFFF, 'uint32') - m.poll() - - mock_modbus().write_register.assert_any_call(1, int.from_bytes(b'\xAC\xA7','big'), unit=1) - mock_modbus().write_register.assert_any_call(2, int.from_bytes(b'\x29\x1E','big'), unit=1) - mock_modbus().reset_mock() - - m.set_value('holding', 1, 5464681683516384647, 0xFFFF, 'uint64') - m.poll() - - mock_modbus().write_register.assert_any_call(1, int.from_bytes(b'\xE5\x87','big'), unit=1) - mock_modbus().write_register.assert_any_call(2, int.from_bytes(b'\xBC\x93','big'), unit=1) - mock_modbus().write_register.assert_any_call(3, int.from_bytes(b'\x73\x09','big'), unit=1) - mock_modbus().write_register.assert_any_call(4, int.from_bytes(b'\x4B\xD6','big'), unit=1) - mock_modbus().reset_mock() - - def test_multi_byte_read_write_values(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - mock_modbus().read_holding_registers.side_effect = self.read_holding_registers - mock_modbus().write_register.side_effect = self.write_holding_register - - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2, scan_batching=1) - m.connect() - mock_modbus.assert_called_with('1.1.1.1', 111, RetryOnEmpty=True, framer=modbus_interface.ModbusSocketFramer, retries=1, timeout=1) - - for i in range(1,11): - m.add_monitor_register('holding', i) - - m.poll() - # Write a value in. - m.set_value('holding', 1, 65535, 0xFFFF, 'uint16') - m.poll() - # Read the value out. - self.assertEqual(m.get_value('holding', 1, 'uint16'), 65535) - # Read the value out as a different type. - self.assertEqual(m.get_value('holding', 1, 'int16'), -1) - m.poll() - - m.set_value('holding', 1, 4294927687, 0xFFFF, 'uint32') - m.poll() - # Read the value out. - self.assertEqual(m.get_value('holding', 1, 'uint32'), 4294927687) - # Read the value out as a different type. - self.assertEqual(m.get_value('holding', 1, 'int32'), -39609) - - m.set_value('holding', 1, 18446573203856197441, 0xFFFF, 'uint64') - m.poll() - # Read the value out. - self.assertEqual(m.get_value('holding', 1, 'uint64'), 18446573203856197441) - # Read the value out as a different type. - self.assertEqual(m.get_value('holding', 1, 'int64'), -170869853354175) - - def test_multi_byte_read_write_values_LowHigh(self): - with patch('modbus4mqtt.modbus_interface.ModbusTcpClient') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - mock_modbus().read_holding_registers.side_effect = self.read_holding_registers - mock_modbus().write_register.side_effect = self.write_holding_register - - m = modbus_interface.modbus_interface('1.1.1.1', 111, 2, scan_batching=1, word_order=modbus_interface.WordOrder.LowHigh) - m.connect() - mock_modbus.assert_called_with('1.1.1.1', 111, RetryOnEmpty=True, framer=modbus_interface.ModbusSocketFramer, retries=1, timeout=1) - - for i in range(1,11): - m.add_monitor_register('holding', i) - - m.poll() - # Write a value in. - m.set_value('holding', 1, 65535, 0xFFFF, 'uint16') - m.poll() - # Read the value out. - self.assertEqual(m.get_value('holding', 1, 'uint16'), 65535) - # Read the value out as a different type. - self.assertEqual(m.get_value('holding', 1, 'int16'), -1) - m.poll() - - m.set_value('holding', 1, 4294927687, 0xFFFF, 'uint32') - m.poll() - # Read the value out. - self.assertEqual(m.get_value('holding', 1, 'uint32'), 4294927687) - # Read the value out as a different type. - self.assertEqual(m.get_value('holding', 1, 'int32'), -39609) - - m.set_value('holding', 1, 18446573203856197441, 0xFFFF, 'uint64') - m.poll() - # Read the value out. - self.assertEqual(m.get_value('holding', 1, 'uint64'), 18446573203856197441) - # Read the value out as a different type. - self.assertEqual(m.get_value('holding', 1, 'int64'), -170869853354175) diff --git a/tests/test_mqtt.py b/tests/test_mqtt.py deleted file mode 100644 index e19d4e1..0000000 --- a/tests/test_mqtt.py +++ /dev/null @@ -1,529 +0,0 @@ -import os -import unittest -from unittest.mock import patch, call, Mock -from paho.mqtt.client import MQTTMessage - -from modbus4mqtt import modbus4mqtt - -from click.testing import CliRunner - -def assert_no_call(self, *args, **kwargs): - try: - self.assert_any_call(*args, **kwargs) - except AssertionError: - return - raise AssertionError('Expected %s to not have been called.' % self._format_mock_call_signature(args, kwargs)) - -Mock.assert_no_call = assert_no_call - -MQTT_TOPIC_PREFIX = 'prefix' - -class MQTTTests(unittest.TestCase): - - def setUp(self): - self.modbus_tables = {'input': {}, 'holding': {}} - self.connect_attempts = 0 - - def tearDown(self): - pass - - def read_modbus_register(self, table, address, type='uint16'): - if address not in self.modbus_tables[table]: - raise ValueError("Invalid address {} in table {}".format(address, table)) - value = bytes(0) - for i in range(modbus4mqtt.modbus_interface.type_length(type)): - data = self.modbus_tables[table][address + i] - value = data.to_bytes(2,'big') + value - value = modbus4mqtt.modbus_interface._convert_from_bytes_to_type(value, type) - return value - - def write_modbus_register(self, table, address, value, mask=0xFFFF, type='uint16'): - old_value = self.modbus_tables[table][address] - and_mask = (1<<16)-1-mask - or_mask = value - new_value = (old_value & and_mask) | (or_mask & (mask)) - self.modbus_tables[table][address] = new_value - - def connect_success(self): - self.connect_attempts += 1 - return False - - def connect_failure(self): - self.connect_attempts += 1 - return True - - def test_main(self): - with patch('paho.mqtt.client.Client') as mock_mqtt: - with patch('modbus4mqtt.modbus4mqtt.mqtt_interface.loop_forever') as mock_mainloop: - with patch('modbus4mqtt.modbus_interface.modbus_interface') as mock_modbus: - mock_modbus().connect.side_effect = self.connect_success - runner = CliRunner() - args = [] - args += ['--hostname', 'kroopit'] - args += ['--port', '1885'] - args += ['--username', 'brengis'] - args += ['--password', 'pranto'] - args += ['--config', './tests/test_connect.yaml'] - args += ['--mqtt_topic_prefix', MQTT_TOPIC_PREFIX] - - runner.invoke(modbus4mqtt.main, args) - mock_mainloop.assert_called_with() - - mock_mqtt().username_pw_set.assert_called_with('brengis', 'pranto') - mock_mqtt().connect.assert_called_with('kroopit', 1885, 60) - - 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', 'modbus4mqtt v{} connected.'.format(modbus4mqtt.version.version)) - mock_mqtt().subscribe.assert_called_with(MQTT_TOPIC_PREFIX+'/subscribe') - mock_mqtt().subscribe.assert_no_call(MQTT_TOPIC_PREFIX+'/publish') - - 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)) - - 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) - - def test_retain_flag(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_retain_flag.yaml', MQTT_TOPIC_PREFIX) - m.connect() - 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+'/retain_on', 1, retain=True) - mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX+'/retain_off', 2, retain=False) - mock_mqtt().publish.assert_any_call(MQTT_TOPIC_PREFIX+'/retain_absent', 3, retain=False) - - 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) - - 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() - - 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) - - 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, 'uint16') - mock_modbus().add_monitor_register.assert_any_call('holding', 2, 'uint16') - - 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) - - - 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, 'uint16') - mock_modbus().add_monitor_register.assert_any_call('holding', 2, 'uint16') - mock_modbus().add_monitor_register.assert_any_call('holding', 3, 'uint16') - 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) - # print(mock_mqtt.mock_calls) - # print(mock_modbus.mock_calls) - - 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) - - def test_address_offset(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_address_offset.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+'/publish', 2, retain=False) - - 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) - - 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) - - def test_register_validation(self): - valids = [[ # Different json_keys for same topic - {'address': 13049, 'json_key': 'a', 'pub_topic': 'ems/EMS_MODE'}, - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODE'}, - {'address': 13050, 'json_key': 'b', 'pub_topic': 'ems/EMS_MODE'} - ], - [ # Different topics, duplicate json_key - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODEA'}, - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODEB'} - ], - [ # Different topic, no json_key - {'address': 13050, 'pub_topic': 'ems/EMS_MODEA'}, - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODEB'} - ], - [ # Retain specified twice and consistent - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODE', 'retain': True}, - {'address': 13050, 'json_key': 'B', 'pub_topic': 'ems/EMS_MODE', 'retain': True} - ], - [ # Valid types specified - {'address': 13050, 'pub_topic': 'ems/EMS_MODEA', 'type': 'uint16'}, - {'address': 13050, 'pub_topic': 'ems/EMS_MODEB', 'type': 'int16'}, - {'address': 13050, 'pub_topic': 'ems/EMS_MODEC', 'type': 'uint32'}, - {'address': 13050, 'pub_topic': 'ems/EMS_MODED', 'type': 'int32'}, - {'address': 13050, 'pub_topic': 'ems/EMS_MODEE', 'type': 'uint64'}, - {'address': 13050, 'pub_topic': 'ems/EMS_MODEF', 'type': 'int64'} - ]] - invalids = [[ # Duplicate json_key for a topic - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODE'}, - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODE'} - ], - [ # Missing json_key for a register with a duplicated pub_topic - {'address': 13049, 'pub_topic': 'ems/EMS_MODE'}, - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODE'} - ], - [ # Retain specified twice and inconsistent - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODE', 'retain': True}, - {'address': 13050, 'json_key': 'B', 'pub_topic': 'ems/EMS_MODE', 'retain': False} - ], - [ # set_topic and json_key both specified - {'address': 13050, 'json_key': 'A', 'pub_topic': 'ems/EMS_MODE', 'set_topic': 'ems/EMS_MODE/set', 'retain': True}, - {'address': 13050, 'json_key': 'B', 'pub_topic': 'ems/EMS_MODE', 'retain': False} - ], - [ # Invalid types specified - {'address': 13050, 'pub_topic': 'ems/EMS_MODEB', 'type': 'float64'} - ]] - for valid in valids: - try: - modbus4mqtt.mqtt_interface._validate_registers(valid) - except: - self.fail("Threw an exception checking a valid register configuration") - for invalid in invalids: - fail = False - try: - modbus4mqtt.mqtt_interface._validate_registers(invalid) - except: - fail = True - if not fail: - self.fail("Didn't throw an exception checking an invalid register configuration") - - 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) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_pub_on_change.yaml b/tests/test_pub_on_change.yaml deleted file mode 100644 index 1ba6377..0000000 --- a/tests/test_pub_on_change.yaml +++ /dev/null @@ -1,12 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - pub_topic: "pub_on_change_false" - pub_only_on_change: false - address: 1 - - pub_topic: "pub_on_change_true" - pub_only_on_change: true - address: 2 - - pub_topic: "pub_on_change_absent" - address: 3 \ No newline at end of file diff --git a/tests/test_retain_flag.yaml b/tests/test_retain_flag.yaml deleted file mode 100644 index 5fd1fdb..0000000 --- a/tests/test_retain_flag.yaml +++ /dev/null @@ -1,12 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - pub_topic: "retain_on" - retain: true - address: 1 - - pub_topic: "retain_off" - retain: false - address: 2 - - pub_topic: "retain_absent" - address: 3 \ No newline at end of file diff --git a/tests/test_scale.yaml b/tests/test_scale.yaml deleted file mode 100644 index 17cd7fb..0000000 --- a/tests/test_scale.yaml +++ /dev/null @@ -1,22 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - set_topic: "scale_up_no_value_map_set" - pub_topic: "scale_up_no_value_map" - pub_only_on_change: false - address: 1 - scale: 2 - - set_topic: "scale_down_no_value_map_set" - pub_topic: "scale_down_no_value_map" - pub_only_on_change: false - address: 2 - scale: 0.5 - - set_topic: "scale_with_value_map_set" - pub_topic: "scale_with_value_map" - pub_only_on_change: false - address: 3 - scale: 2 - value_map: - a: 4 - b: 6 diff --git a/tests/test_set_topics.yaml b/tests/test_set_topics.yaml deleted file mode 100644 index 42a0cad..0000000 --- a/tests/test_set_topics.yaml +++ /dev/null @@ -1,11 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - set_topic: "no_value_map" - address: 1 - - set_topic: "value_map" - address: 2 - value_map: - a: 1 - b: 2 diff --git a/tests/test_type.yaml b/tests/test_type.yaml deleted file mode 100644 index e5163c4..0000000 --- a/tests/test_type.yaml +++ /dev/null @@ -1,28 +0,0 @@ -ip: 192.168.1.90 -registers: - - pub_topic: "publish_uint16_1" - set_topic: "publish_uint16_1_set" - address: 0 - type: uint16 - - pub_topic: "publish_uint16_2" - address: 1 - type: uint16 - - pub_topic: "publish_uint16_3" - address: 2 - type: uint16 - - pub_topic: "publish_uint16_4" - address: 3 - type: uint16 - - pub_topic: "publish_int16_1" - set_topic: "publish_int16_1_set" - address: 0 - type: int16 - - pub_topic: "publish_int16_2" - address: 1 - type: int16 - - pub_topic: "publish_int16_3" - address: 2 - type: int16 - - pub_topic: "publish_int16_4" - address: 3 - type: int16 diff --git a/tests/test_value_map.yaml b/tests/test_value_map.yaml deleted file mode 100644 index 8449e5e..0000000 --- a/tests/test_value_map.yaml +++ /dev/null @@ -1,16 +0,0 @@ -ip: 192.168.1.90 -port: 502 -update_rate: 1 -registers: - - pub_topic: "value_map_absent" - address: 1 - - pub_topic: "value_map_present" - address: 2 - value_map: - a: 1 - b: 2 - - pub_topic: "value_map_misinterpretation" - address: 3 - value_map: - on: 1 - off: 2 \ No newline at end of file diff --git a/tests/test_word_order.yaml b/tests/test_word_order.yaml deleted file mode 100644 index 7f5cb8c..0000000 --- a/tests/test_word_order.yaml +++ /dev/null @@ -1,7 +0,0 @@ -ip: 192.168.1.90 -word_order: highlow -registers: - - pub_topic: "publish_uint16_1" - set_topic: "publish_uint16_1_set" - address: 0 - type: uint16 \ No newline at end of file diff --git a/tests/test_word_order_low_high.yaml b/tests/test_word_order_low_high.yaml deleted file mode 100644 index 72aaddc..0000000 --- a/tests/test_word_order_low_high.yaml +++ /dev/null @@ -1,7 +0,0 @@ -ip: 192.168.1.90 -word_order: lowhigh -registers: - - pub_topic: "publish_uint16_1" - set_topic: "publish_uint16_1_set" - address: 0 - type: uint16 \ No newline at end of file From d2480e86975f7b47fe2253e7ed19aabf59ea5766 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:23:42 +0200 Subject: [PATCH 003/109] Delete .gitignore --- .gitignore | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 91b7940..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.coverage -build -__pycache__ -.vscode -dist -*.egg-info -go.sh -*,cover From e1eca46ee091341869f371043fb968850fb185fd Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:23:55 +0200 Subject: [PATCH 004/109] Delete .pep8speaks.yml --- .pep8speaks.yml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .pep8speaks.yml diff --git a/.pep8speaks.yml b/.pep8speaks.yml deleted file mode 100644 index 7dbb67a..0000000 --- a/.pep8speaks.yml +++ /dev/null @@ -1,2 +0,0 @@ -pycodestyle: # Same as scanner.linter value. Other option is flake8 - max-line-length: 120 # Default is 79 in PEP 8 - sorry IBM 3270 terminal users From 290a08bae54f4562978801810e955e8838b519de Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:24:05 +0200 Subject: [PATCH 005/109] Delete Dockerfile --- Dockerfile | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 72f96e4..0000000 --- a/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -from python:3-alpine - -run apk add --no-cache --virtual .build-deps gcc g++ make libffi-dev openssl-dev - -copy ["README.md", "setup.py", "/modbus4mqtt/"] -copy ["./modbus4mqtt/*", "/modbus4mqtt/modbus4mqtt/"] - -run pip install /modbus4mqtt - -run apk del .build-deps - -entrypoint ["modbus4mqtt"] From 6d2419b2ef9908d968d020c9d9b3af4bc4d856f7 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:24:26 +0200 Subject: [PATCH 006/109] Delete hacs.json --- hacs.json | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 hacs.json diff --git a/hacs.json b/hacs.json deleted file mode 100644 index aaa4d8e..0000000 --- a/hacs.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "modbus4mqtt", - "render_readme": true -} \ No newline at end of file From f4657bb25bfe09888ab02db1812133a81dd7f8f2 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:25:04 +0200 Subject: [PATCH 007/109] Delete package_and_upload.sh --- package_and_upload.sh | 9 --------- 1 file changed, 9 deletions(-) delete mode 100755 package_and_upload.sh diff --git a/package_and_upload.sh b/package_and_upload.sh deleted file mode 100755 index 3ab3244..0000000 --- a/package_and_upload.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -version=v`cat ./modbus4mqtt/version.py | cut -d\" -f2` -rm ./dist/modbus4mqtt*.whl -rm ./dist/modbus4mqtt*.tar.gz -python3 setup.py sdist bdist_wheel -python3 -m twine upload dist/* -docker build -t tjhowse/modbus4mqtt:latest -t tjhowse/modbus4mqtt:"$version" . -docker push tjhowse/modbus4mqtt:latest -docker push tjhowse/modbus4mqtt:"$version" From da600247d67fbb1f8518ad47b7fa7e6f8942595e Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:26:01 +0200 Subject: [PATCH 008/109] Delete setup.py --- setup.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index ad49f14..0000000 --- a/setup.py +++ /dev/null @@ -1,39 +0,0 @@ -import setuptools -from modbus4mqtt.version import version - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="modbus4mqtt", - version=version, - author="Travis Howse", - author_email="tjhowse@gmail.com", - description="A YAML-defined bidirectional Modbus to MQTT interface", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/tjhowse/modbus4mqtt", - packages=setuptools.find_packages(exclude=['tests']), - install_requires=[ - 'ruamel.yaml>=0.16.12', - 'paho-mqtt>=1.5.0', - 'pymodbus>=2.3.0,<3.0.0', - 'click>=6.7', - 'SungrowModbusTcpClient>=0.1.6', - ], - tests_require=[ - 'nose2>=0.9.2', - 'nose2[coverage_plugin]>=0.6.5', - ], - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Operating System :: OS Independent", - ], - python_requires='>=3.5.0', - test_suite='nose2.collector.collector', - entry_points=''' - [console_scripts] - modbus4mqtt=modbus4mqtt.modbus4mqtt:main - ''', -) From d76b4c82c20fa65ab3f5a81a54ab1060f53bf6e3 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:26:13 +0200 Subject: [PATCH 009/109] Delete unittest.cfg --- unittest.cfg | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 unittest.cfg diff --git a/unittest.cfg b/unittest.cfg deleted file mode 100644 index 84743c4..0000000 --- a/unittest.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[coverage] -always-on = True From 59600bd072bdbae3e78c1123b7d9755c987b2039 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:26:28 +0200 Subject: [PATCH 010/109] Delete modbus4mqtt directory --- modbus4mqtt/E3DC-S10.yaml | 49 ---- modbus4mqtt/SG5K-D.yaml | 65 ----- modbus4mqtt/SG8K-D.yaml | 61 ----- modbus4mqtt/Sungrow_SH5k_20.yaml | 451 ------------------------------- modbus4mqtt/__init__.py | 0 modbus4mqtt/modbus4mqtt.py | 297 -------------------- modbus4mqtt/modbus_interface.py | 215 --------------- modbus4mqtt/version.py | 1 - 8 files changed, 1139 deletions(-) delete mode 100644 modbus4mqtt/E3DC-S10.yaml delete mode 100644 modbus4mqtt/SG5K-D.yaml delete mode 100644 modbus4mqtt/SG8K-D.yaml delete mode 100644 modbus4mqtt/Sungrow_SH5k_20.yaml delete mode 100644 modbus4mqtt/__init__.py delete mode 100755 modbus4mqtt/modbus4mqtt.py delete mode 100644 modbus4mqtt/modbus_interface.py delete mode 100644 modbus4mqtt/version.py diff --git a/modbus4mqtt/E3DC-S10.yaml b/modbus4mqtt/E3DC-S10.yaml deleted file mode 100644 index c2f2797..0000000 --- a/modbus4mqtt/E3DC-S10.yaml +++ /dev/null @@ -1,49 +0,0 @@ -ip: 192.168.1.xxx #your inverter ip address -port: 502 -update_rate: 5 -address_offset: -1 -word_order: lowhigh -registers: - - pub_topic: "magic" # control token, must contain 0xE3DC if interpreted correctly - address: 40001 - type: uint16 - pub_only_on_change: false - - pub_topic: "power/sun" # current yield solar panels [kw] - address: 40068 - type: int32 - pub_only_on_change: false - - pub_topic: "power/battery" # current net yield battery [kW] - address: 40070 - type: int32 - pub_only_on_change: false - - pub_topic: "power/house" # current consumption [kW] - address: 40072 - type: int32 - pub_only_on_change: false - - pub_topic: "power/grid" # current yield grid [kW] - address: 40074 - type: int32 - pub_only_on_change: false - - pub_topic: "status/autarchy_percentage" # current percentage of autarchy [0..100][%] - address: 40082 - mask: 0x00FF - type: uint16 - pub_only_on_change: false - - pub_topic: "status/domestic_cons_percentage" # current domestic consumption [0..100][%] - address: 40082 - mask: 0xFF00 - scale: 0.00390625 - type: uint16 - pub_only_on_change: false - - pub_topic: "battery/soc" # current State of charge battery [0..100][%] - address: 40083 - type: uint16 - pub_only_on_change: false - - pub_topic: "power/sun/string1" # current yield of solar panels attached to string1 [kW] - address: 40102 - type: uint16 - pub_only_on_change: false - - pub_topic: "power/sun/string2" # current yield of solar panels attached to string2 [kW] - address: 40103 - type: uint16 - pub_only_on_change: false diff --git a/modbus4mqtt/SG5K-D.yaml b/modbus4mqtt/SG5K-D.yaml deleted file mode 100644 index d1e4c47..0000000 --- a/modbus4mqtt/SG5K-D.yaml +++ /dev/null @@ -1,65 +0,0 @@ -ip: 192.168.1.xxx #your inverter ip address -port: 502 -update_rate: 60 -address_offset: 0 -variant: sungrow -scan_batching: 1 -registers: - - pub_topic: "output_power" #total output power kWh - address: 5000 - table: 'input' - - pub_topic: "daily_yield" #daily yield kWh - address: 5002 - table: 'input' - - pub_topic: "total_yield" #Total yield kWh - address: 5003 - table: 'input' - - pub_topic: "total_running_time" #Total running time (h) - address: 5003 - table: 'input' - - pub_topic: "internal_temperature" #inverter internal temperature 0.1C - address: 5007 - table: 'input' - - pub_topic: "dc_output" #dc output power (W) - address: 5016 - table: 'input' - - pub_topic: "phase_a_voltage" #Phase A Voltage (0.1V) - address: 5018 - table: 'input' - - pub_topic: "phase_a_current" #Phase A Current (0.1A) - address: 5021 - table: 'input' - - pub_topic: "ac_output" #AC output power, total active power (W) - address: 5030 - table: 'input' - - pub_topic: "power_factor" #Power factor (0.001) - address: 5034 - table: 'input' - - pub_topic: "grid_frequency" #Grid Frequency (0.1Hz) - address: 5035 - table: 'input' - - pub_topic: "device_state" #Device State (see comments below for states) - address: 5037 - table: 'input' - - pub_topic: "daily_running_time" #Daily running time (1m) - address: 5112 - table: 'input' - - #see https://github.com/tjhowse/modbus4mqtt/files/5732710/TI_20190704_Communication.Protocol.for.Residential.Single-phase.Grid-Connected.Inverters_V10_EN.pdf for full list of registers and details - - # Device States (register 5037) - # - #0 Run - #1 Stop (normal stop) - #2 Initial Standby - #3 Key stop - #4 Standby - #5 Emergency Stop - #6 Startup - #7 Stopping - #9 Fault stop - #10 Alarm Run - #11 Derating run - #12 Limited run - #13 Communication fault - #16 Sleeping diff --git a/modbus4mqtt/SG8K-D.yaml b/modbus4mqtt/SG8K-D.yaml deleted file mode 100644 index 382eabc..0000000 --- a/modbus4mqtt/SG8K-D.yaml +++ /dev/null @@ -1,61 +0,0 @@ -ip: 192.168.0.xxx -port: 502 -update_rate: 5 -address_offset: 0 -variant: sungrow -scan_batching: 1 -registers: - - pub_topic: "energy_meter_power" # Feed-in power is negative and taken-back power is positive (W) - address: 5082 - table: 'input' - type: int32 - - pub_topic: "output_power" #total output power kWh - type: uint16 - address: 5000 - table: 'input' - scale: 0.1 - - pub_topic: "daily_yield" #daily yield kWh - address: 5002 - table: 'input' - scale: 0.1 - - pub_topic: "total_yield" #Total yield kWh - address: 5003 - table: 'input' - - pub_topic: "total_running_time" #Total running time (h) - address: 5005 - table: 'input' - - pub_topic: "internal_temperature" #inverter internal temperature 0.1C - address: 5007 - table: 'input' - scale: 0.1 - - pub_topic: "dc_output" #dc output power (W) - address: 5016 - table: 'input' - - pub_topic: "phase_a_voltage" #Phase A Voltage (0.1V) - address: 5018 - table: 'input' - scale: 0.1 - - pub_topic: "phase_a_current" #Phase A Current (0.1A) - address: 5021 - table: 'input' - scale: 0.1 - - pub_topic: "ac_output" #AC output power, total active power (W) - address: 5030 - table: 'input' - - pub_topic: "power_factor" #Power factor (0.001) - address: 5034 - table: 'input' - scale: 0.001 - - pub_topic: "grid_frequency" #Grid Frequency (0.1Hz) - address: 5035 - table: 'input' - scale: 0.1 - - pub_topic: "device_state" #Device State (see comments below for states) - address: 5037 - table: 'input' - - pub_topic: "daily_running_time" #Daily running time (1m) - address: 5112 - table: 'input' - - #see https://github.com/tjhowse/modbus4mqtt/files/5732710/TI_20190704_Communication.Protocol.for.Residential.Single-phase.Grid-Connected.Inverters_V10_EN.pdf for full list of registers and details - diff --git a/modbus4mqtt/Sungrow_SH5k_20.yaml b/modbus4mqtt/Sungrow_SH5k_20.yaml deleted file mode 100644 index afb7e42..0000000 --- a/modbus4mqtt/Sungrow_SH5k_20.yaml +++ /dev/null @@ -1,451 +0,0 @@ -ip: 192.168.1.89 -port: 502 -update_rate: 5 -address_offset: 0 -variant: sungrow -scan_batching: 100 -word_order: lowhigh -registers: - - pub_topic: "ems/EMS_MODE" - set_topic: "ems/EMS_MODE/set" - address: 13049 - value_map: - self: 0 - forced: 2 - external: 3 - - pub_topic: "ems/CHARGE_DISCHARGE_COMMAND" - set_topic: "ems/CHARGE_DISCHARGE_COMMAND/set" - address: 13050 - value_map: - charge: 0xAA - discharge: 0xBB - stop: 0xCC - - pub_topic: "ems/CHARGE_DISCHARGE_POWER" - set_topic: "ems/CHARGE_DISCHARGE_POWER/set" - address: 13051 - - pub_topic: "no_export/partial/limit" - set_topic: "no_export/partial/limit/set" - address: 13073 - - pub_topic: "forced_charge/mode" - set_topic: "forced_charge/mode/set" - retain: true - address: 13139 - value_map: - enabled: 170 - disabled: 85 - - pub_topic: "forced_charge/weekdays" - set_topic: "forced_charge/weekdays/set" - retain: true - address: 13140 - value_map: - all_days: 1 - weekdays: 0 - - pub_topic: "forced_charge/period_1/start_hours" - set_topic: "forced_charge/period_1/start_hours/set" - address: 13141 - - pub_topic: "forced_charge/period_1/start_minutes" - set_topic: "forced_charge/period_1/start_minutes/set" - address: 13142 - - pub_topic: "forced_charge/period_1/end_hours" - set_topic: "forced_charge/period_1/end_hours/set" - address: 13143 - - pub_topic: "forced_charge/period_1/end_minutes" - set_topic: "forced_charge/period_1/end_minutes/set" - address: 13144 - - pub_topic: "forced_charge/period_1/target_soc" - set_topic: "forced_charge/period_1/target_soc/set" - address: 13145 - - pub_topic: "forced_charge/period_2/start_hours" - set_topic: "forced_charge/period_2/start_hours/set" - address: 13146 - - pub_topic: "forced_charge/period_2/start_minutes" - set_topic: "forced_charge/period_2/start_minutes/set" - address: 13147 - - pub_topic: "forced_charge/period_2/end_hours" - set_topic: "forced_charge/period_2/end_hours/set" - address: 13148 - - pub_topic: "forced_charge/period_2/end_minutes" - set_topic: "forced_charge/period_2/end_minutes/set" - address: 13149 - - pub_topic: "forced_charge/period_2/target_soc" - set_topic: "forced_charge/period_2/target_soc/set" - address: 13150 - - #pub_topic: "date_time/minutes" - address: 5003 - - #pub_topic: "date_time/seconds" - address: 5004 - - pub_topic: "load_control/mode" - value_map: - timer: 0 - manual: 1 - optimized: 2 - address: 13001 - - pub_topic: "load_control/timer/start_time_1_hours" - address: 13002 - - pub_topic: "load_control/timer/start_time_1_minutes" - address: 13003 - - pub_topic: "load_control/timer/end_time_1_hours" - address: 13004 - - pub_topic: "load_control/timer/end_time_1_minutes" - address: 13005 - - pub_topic: "load_control/timer/start_time_2_hours" - address: 13006 - - pub_topic: "load_control/timer/start_time_2_minutes" - address: 13007 - - pub_topic: "load_control/timer/end_time_2_hours" - address: 13008 - - pub_topic: "load_control/timer/end_time_2_minutes" - address: 13009 - - pub_topic: "load_control/on_off " - value_map: - on: 170 - off: 85 - address: 13010 - - pub_topic: "load_control/optimized/start_time_hours" - address: 13011 - - pub_topic: "load_control/optimized/start_time_minutes" - address: 13012 - - pub_topic: "load_control/optimized/end_time_hours" - address: 13013 - - pub_topic: "load_control/optimized/end_time_minutes" - address: 13014 - - pub_topic: "load_control/optimized/power" - address: 13015 - - pub_topic: "bat_usage_time/weekday_usage/start_time_1_hours" - address: 13122 - - pub_topic: "bat_usage_time/weekday_usage/start_time_1_minutes" - address: 13123 - - pub_topic: "bat_usage_time/weekday_usage/end_time_1_hours" - set_topic: "bat_usage_time/weekday_usage/end_time_1_hours/set" - address: 13124 - - pub_topic: "bat_usage_time/weekday_usage/end_time_1_minutes" - address: 13125 - - pub_topic: "bat_usage_time/weekday_usage/start_time_2_hours" - address: 13126 - - pub_topic: "bat_usage_time/weekday_usage/start_time_2_minutes" - address: 13127 - - pub_topic: "bat_usage_time/weekday_usage/end_time_2_hours" - set_topic: "bat_usage_time/weekday_usage/end_time_2_hours/set" - address: 13128 - - pub_topic: "bat_usage_time/weekday_usage/end_time_2_minutes" - address: 13129 - - pub_topic: "bat_usage_time/weekend_usage" - set_topic: "bat_usage_time/weekend_usage/set" - value_map: - disabled: 85 - enabled: 170 - address: 13130 - - pub_topic: "bat_usage_time/weekend_usage/start_time_1_hours" - address: 13131 - - pub_topic: "bat_usage_time/weekend_usage/start_time_1_minutes" - address: 13132 - - pub_topic: "bat_usage_time/weekend_usage/end_time_1_hours" - address: 13133 - - pub_topic: "bat_usage_time/weekend_usage/end_time_1_minutes" - address: 13134 - - pub_topic: "bat_usage_time/weekend_usage/start_time_2_hours" - address: 13135 - - pub_topic: "bat_usage_time/weekend_usage/start_time_2_minutes" - address: 13136 - - pub_topic: "bat_usage_time/weekend_usage/end_time_2_hours" - address: 13137 - - pub_topic: "bat_usage_time/weekend_usage/end_time_2_minutes" - address: 13138 - - # The following registers are published together in one JSON message to replicate the functionality - # of pvstats because I didn't want to change my MQTT -> influxdb pipeline when I migrated from pvstats - # to modbus4mqtt for these numbers. - - - pub_topic: "solar" - json_key: daily_pv_power - type: uint16 - address: 5002 - table: input - pub_only_on_change: false - scale: 100 - - pub_topic: "solar" - json_key: lifetime_pv_power - type: uint16 - address: 5003 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: total_run_time - type: uint16 - address: 5005 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: internal_temp - type: uint16 - address: 5007 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: pv1_voltage - type: uint16 - address: 5010 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: pv1_current - type: uint16 - address: 5011 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: pv2_voltage - type: uint16 - address: 5012 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: pv2_current - type: uint16 - address: 5013 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: total_pv_power - type: uint16 - address: 5016 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: grid_voltage - type: uint16 - address: 5018 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: inverter_current - type: uint16 - address: 5021 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: inverter_ac_output - type: uint16 - address: 5030 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: grid_frequency - type: uint16 - address: 5035 - table: input - pub_only_on_change: false - scale: 0.1 - - - pub_topic: "solar" - json_key: running_state - type: uint16 - address: 13000 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: daily_pv_energy_10 - type: uint16 - address: 13001 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: total_pv_energy_10 - type: uint32 - address: 13002 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: daily_export_energy_10 - type: uint16 - address: 13004 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: total_export_energy_10 - type: uint32 - address: 13005 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: load_power - type: uint16 - address: 13007 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: export_power - type: int16 - address: 13009 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: grid_import_or_export - type: int16 - address: 13010 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: daily_charge_energy_10 - type: uint16 - address: 13011 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: total_charge_energy_10 - type: uint16 - address: 13012 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: co2_emission_reduction - type: uint32 - address: 13014 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: daily_use_energy_10 - type: uint16 - address: 13016 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: total_use_energy_10 - type: uint32 - address: 13017 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: battery_voltage_10 - type: uint16 - address: 13019 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: battery_current_10 - type: uint16 - address: 13020 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: battery_power - type: uint16 - address: 13021 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: battery_level_10 - type: uint16 - address: 13022 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: battery_health_10 - type: uint16 - address: 13023 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: battery_temp_10 - type: uint16 - address: 13024 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: daily_discharge_energy_10 - type: uint16 - address: 13025 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: total_discharge_energy_10 - type: uint16 - address: 13026 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: use_power - type: uint16 - address: 13028 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: inverter_current_10 - type: uint16 - address: 13030 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: pv_power - type: uint16 - address: 13033 - table: input - pub_only_on_change: false - scale: 0.1 - - pub_topic: "solar" - json_key: mystery_5001 - type: uint16 - address: 5001 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: mystery_5033 # Possibly -100% to 100% ? - type: int16 - address: 5033 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: mystery_5034 # -1 to 0. - type: int16 - address: 5034 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: mystery_5035 # -1000 to 1000. Possibly needs a 0.1 scale? - type: int16 - address: 5035 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: mystery_13030 # Constant 85 - type: uint16 - address: 13030 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: mystery_13036 # 0-15. Four-bit state? Possibly? - type: uint16 - address: 13036 - table: input - pub_only_on_change: false - - pub_topic: "solar" - json_key: mystery_13037 # Accumulates. Possibly total kWh consumed? - type: uint16 - address: 13037 - table: input - pub_only_on_change: false diff --git a/modbus4mqtt/__init__.py b/modbus4mqtt/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/modbus4mqtt/modbus4mqtt.py b/modbus4mqtt/modbus4mqtt.py deleted file mode 100755 index 33b9eda..0000000 --- a/modbus4mqtt/modbus4mqtt.py +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/python3 - -from time import sleep -import json -import logging -from ruamel.yaml import YAML -import click -import paho.mqtt.client as mqtt - -from . import modbus_interface -from . import version - -MAX_DECIMAL_POINTS = 8 - - -class mqtt_interface(): - def __init__(self, hostname, port, username, password, config_file, mqtt_topic_prefix, - use_tls=True, insecure=False, cafile=None, cert=None, key=None): - self.hostname = hostname - self._port = port - self.username = username - self.password = password - self.config = self._load_modbus_config(config_file) - self.use_tls = use_tls - self.insecure = insecure - self.cafile = cafile - self.cert = cert - self.key = key - if not mqtt_topic_prefix.endswith('/'): - mqtt_topic_prefix = mqtt_topic_prefix + '/' - self.prefix = mqtt_topic_prefix - self.address_offset = self.config.get('address_offset', 0) - self.registers = self.config['registers'] - for register in self.registers: - register['address'] += self.address_offset - self.modbus_connect_retries = -1 # Retry forever by default - self.modbus_reconnect_sleep_interval = 5 # Wait this many seconds between modbus connection attempts - - def connect(self): - # Connects to modbus and MQTT. - self.connect_modbus() - self.connect_mqtt() - - def connect_modbus(self): - if self.config.get('word_order', 'highlow').lower() == 'lowhigh': - word_order = modbus_interface.WordOrder.LowHigh - else: - word_order = modbus_interface.WordOrder.HighLow - - self._mb = modbus_interface.modbus_interface(self.config['ip'], - self.config.get('port', 502), - self.config.get('update_rate', 5), - variant=self.config.get('variant', None), - scan_batching=self.config.get('scan_batching', None), - word_order=word_order) - failed_attempts = 1 - while self._mb.connect(): - logging.warning("Modbus connection attempt {} failed. Retrying...".format(failed_attempts)) - failed_attempts += 1 - if self.modbus_connect_retries != -1 and failed_attempts > self.modbus_connect_retries: - logging.error("Failed to connect to modbus. Giving up.") - self.modbus_connection_failed() - # This weird break is here because we mock out modbus_connection_failed in the tests - break - sleep(self.modbus_reconnect_sleep_interval) - # Tells the modbus interface about the registers we consider interesting. - for register in self.registers: - self._mb.add_monitor_register(register.get('table', 'holding'), register['address'], register.get('type', 'uint16')) - register['value'] = None - - def modbus_connection_failed(self): - exit(1) - - def connect_mqtt(self): - self._mqtt_client = mqtt.Client() - self._mqtt_client.username_pw_set(self.username, self.password) - self._mqtt_client._on_connect = self._on_connect - self._mqtt_client._on_disconnect = self._on_disconnect - self._mqtt_client._on_message = self._on_message - self._mqtt_client._on_subscribe = self._on_subscribe - if self.use_tls: - self._mqtt_client.tls_set(ca_certs=self.cafile, certfile=self.cert, keyfile=self.key) - self._mqtt_client.tls_insecure_set(self.insecure) - self._mqtt_client.connect(self.hostname, self._port, 60) - self._mqtt_client.loop_start() - - def _get_registers_with(self, required_key): - # Returns the registers containing the required_key - return [register for register in self.registers if required_key in register] - - def poll(self): - try: - self._mb.poll() - except Exception as e: - logging.exception("Failed to poll modbus device, attempting to reconnect: {}".format(e)) - self.connect_modbus() - return - - # This is used to store values that are published as JSON messages rather than individual values - json_messages = {} - json_messages_retain = {} - - for register in self._get_registers_with('pub_topic'): - try: - value = self._mb.get_value( register.get('table', 'holding'), - register['address'], - register.get('type', 'uint16')) - except Exception: - logging.warning("Couldn't get value from register {} in table {}".format(register['address'], - register.get('table', 'holding'))) - continue - # Filter the value through the mask, if present. - if 'mask' in register: - # masks only make sense for uint - if register.get('type', 'uint16') in ['uint16', 'uint32', 'uint64']: - value &= register.get('mask') - # Scale the value, if required. - value *= register.get('scale', 1) - # Clamp the number of decimal points - value = round(value, MAX_DECIMAL_POINTS) - changed = False - if value != register['value']: - changed = True - register['value'] = value - if not changed and register.get('pub_only_on_change', True): - continue - # Map from the raw number back to the human-readable form - if 'value_map' in register: - if value in register['value_map'].values(): - # This is a bit weird... - value = [human for human, raw in register['value_map'].items() if raw == value][0] - if register.get('json_key', False): - # This value won't get published to MQTT immediately. It gets stored and sent at the end of the poll. - if register['pub_topic'] not in json_messages: - json_messages[register['pub_topic']] = {} - json_messages_retain[register['pub_topic']] = False - json_messages[register['pub_topic']][register['json_key']] = value - if 'retain' in register: - json_messages_retain[register['pub_topic']] = register['retain'] - else: - retain = register.get('retain', False) - self._mqtt_client.publish(self.prefix+register['pub_topic'], value, retain=retain) - - # Transmit the queued JSON messages. - for topic, message in json_messages.items(): - m = json.dumps(message, sort_keys=True) - self._mqtt_client.publish(self.prefix+topic, m, retain=json_messages_retain[topic]) - - def _on_connect(self, client, userdata, flags, rc): - if rc == 0: - logging.info("Connected to MQTT.") - else: - logging.error("Couldn't connect to MQTT.") - return - # Subscribe to all the set topics. - for register in self._get_registers_with('set_topic'): - self._mqtt_client.subscribe(self.prefix+register['set_topic']) - print("Subscribed to {}".format(self.prefix+register['set_topic'])) - self._mqtt_client.publish(self.prefix+'modbus4mqtt', 'modbus4mqtt v{} connected.'.format(version.version)) - - def _on_disconnect(self, client, userdata, rc): - logging.warning("Disconnected from MQTT. Attempting to reconnect.") - - def _on_subscribe(self, client, userdata, mid, granted_qos): - pass - - def _on_message(self, client, userdata, msg): - # print("got a message: {}: {}".format(msg.topic, msg.payload)) - # TODO Handle json_key writes. https://github.com/tjhowse/modbus4mqtt/issues/23 - topic = msg.topic[len(self.prefix):] - for register in [register for register in self.registers if 'set_topic' in register]: - if topic != register['set_topic']: - continue - # We received a set topic message for this topic. - value = msg.payload - if 'value_map' in register: - try: - value = str(value, 'utf-8') - if value not in register['value_map']: - logging.warning("Value not in value_map. Topic: {}, value: {}, valid values: {}".format(topic, - value, register['value_map'].keys())) - continue - # Map the value from the human-readable form into the raw modbus number - value = register['value_map'][value] - except UnicodeDecodeError: - logging.warning("Failed to decode MQTT payload as UTF-8. " - "Can't compare it to the value_map for register {}".format(register)) - continue - try: - # Scale the value, if required. - value = float(value) - value = round(value/register.get('scale', 1)) - except ValueError: - logging.error("Failed to convert register value for writing. " - "Bad/missing value_map? Topic: {}, Value: {}".format(topic, value)) - continue - type = register.get('type', 'uint16') - self._mb.set_value(register.get('table', 'holding'), register['address'], int(value), - register.get('mask', 0xFFFF), type) - - # This throws ValueError exceptions if the imported registers are invalid - @staticmethod - def _validate_registers(registers): - all_pub_topics = set() - duplicate_pub_topics = set() - # Key: shared pub_topics, value: list of json_keys - duplicate_json_keys = {} - # Key: shared pub_topics, value: set of retain values (true/false) - retain_setting = {} - valid_types = ['uint16', 'int16', 'uint32', 'int32', 'uint64', 'int64'] - - # Look for duplicate pub_topics - for register in registers: - type = register.get('type', 'uint16') - if type not in valid_types: - raise ValueError("Bad YAML configuration. Register has invalid type '{}'.".format(type)) - if register['pub_topic'] in all_pub_topics: - duplicate_pub_topics.add(register['pub_topic']) - duplicate_json_keys[register['pub_topic']] = [] - retain_setting[register['pub_topic']] = set() - if 'json_key' in register and 'set_topic' in register: - raise ValueError("Bad YAML configuration. Register with set_topic '{}' has a json_key specified. " - "This is invalid. See https://github.com/tjhowse/modbus4mqtt/issues/23 for details." - .format(register['set_topic'])) - all_pub_topics.add(register['pub_topic']) - - # Check that all registers with duplicate pub topics have json_keys - for register in registers: - if register['pub_topic'] in duplicate_pub_topics: - if 'json_key' not in register: - raise ValueError("Bad YAML configuration. pub_topic '{}' duplicated across registers without " - "json_key field. Registers that share a pub_topic must also have a unique " - "json_key.".format(register['pub_topic'])) - if register['json_key'] in duplicate_json_keys[register['pub_topic']]: - raise ValueError("Bad YAML configuration. pub_topic '{}' duplicated across registers with a " - "duplicated json_key field. Registers that share a pub_topic must also have " - "a unique json_key.".format(register['pub_topic'])) - duplicate_json_keys[register['pub_topic']] += [register['json_key']] - if 'retain' in register: - retain_setting[register['pub_topic']].add(register['retain']) - # Check that there are no disagreements as to whether this pub_topic should be retained or not. - for topic, retain_set in retain_setting.items(): - if len(retain_set) > 1: - raise ValueError("Bad YAML configuration. pub_topic '{}' has conflicting retain settings." - .format(topic)) - - def _load_modbus_config(self, path): - yaml = YAML(typ='safe') - result = yaml.load(open(path, 'r').read()) - registers = [register for register in result['registers'] if 'pub_topic' in register] - mqtt_interface._validate_registers(registers) - return result - - def loop_forever(self): - while True: - # TODO this properly. - self.poll() - sleep(self.config['update_rate']) - - -@click.command() -@click.option('--hostname', default='localhost', - help='The hostname or IP address of the MQTT server.', show_default=True) -@click.option('--port', default=1883, - help='The port of the MQTT server.', show_default=True) -@click.option('--username', default='username', - help='The username to authenticate to the MQTT server.', show_default=True) -@click.option('--password', default='password', - help='The password to authenticate to the MQTT server.', show_default=True) -@click.option('--mqtt_topic_prefix', default='modbus4mqtt', - help='A prefix for published MQTT topics.', show_default=True) -@click.option('--config', default='./Sungrow_SH5k_20.yaml', - help='The YAML config file for your modbus device.', show_default=True) -@click.option('--use_tls', default=False, - help='Configure network encryption and authentication options. Enables SSL/TLS.', show_default=True) -@click.option('--insecure', default=True, - help='Do not check that the server certificate hostname matches the remote hostname.', show_default=True) -@click.option('--cafile', default=None, - help='The path to a file containing trusted CA certificates to enable encryption.', show_default=True) -@click.option('--cert', default=None, - help='Client certificate for authentication, if required by server.', show_default=True) -@click.option('--key', default=None, - help='Client private key for authentication, if required by server.', show_default=True) -def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key): - logging.basicConfig( - format='%(asctime)s %(levelname)-8s %(message)s', - level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') - logging.info("Starting modbus4mqtt v{}".format(version.version)) - i = mqtt_interface(hostname, port, username, password, config, mqtt_topic_prefix, - use_tls, insecure, cafile, cert, key) - i.connect() - i.loop_forever() - - -if __name__ == '__main__': - main() diff --git a/modbus4mqtt/modbus_interface.py b/modbus4mqtt/modbus_interface.py deleted file mode 100644 index 5db2baf..0000000 --- a/modbus4mqtt/modbus_interface.py +++ /dev/null @@ -1,215 +0,0 @@ -from time import time, sleep -from enum import Enum -import logging -from queue import Queue -try: - # Pymodbus >= 3.0 - # TODO: Once SungrowModbusTcpClient 0.1.7 is released, - # we can remove the "<3.0.0" pymodbus restriction and this - # will make sense again. - from pymodbus.client import ModbusTcpClient - from pymodbus.transaction import ModbusSocketFramer -except ImportError: - # Pymodbus < 3.0 - from pymodbus.client.sync import ModbusTcpClient, ModbusSocketFramer -from SungrowModbusTcpClient import SungrowModbusTcpClient - -DEFAULT_SCAN_RATE_S = 5 -DEFAULT_SCAN_BATCHING = 100 -MIN_SCAN_BATCHING = 1 -MAX_SCAN_BATCHING = 100 -DEFAULT_WRITE_BLOCK_INTERVAL_S = 0.2 -DEFAULT_WRITE_SLEEP_S = 0.05 -DEFAULT_READ_SLEEP_S = 0.05 - -class WordOrder(Enum): - HighLow = 1 - LowHigh = 2 - -class modbus_interface(): - - def __init__(self, ip, port=502, update_rate_s=DEFAULT_SCAN_RATE_S, variant=None, scan_batching=None, word_order=WordOrder.HighLow): - self._ip = ip - self._port = port - # This is a dict of sets. Each key represents one table of modbus registers. - # At the moment it has 'input' and 'holding' - self._tables = {'input': set(), 'holding': set()} - - # This is a dicts of dicts. These hold the current values of the interesting registers - self._values = {'input': {}, 'holding': {}} - - self._planned_writes = Queue() - self._writing = False - self._variant = variant - self._scan_batching = DEFAULT_SCAN_BATCHING - self._word_order = word_order - if scan_batching is not None: - if scan_batching < MIN_SCAN_BATCHING: - logging.warning("Bad value for scan_batching: {}. Enforcing minimum value of {}".format(scan_batching, MIN_SCAN_BATCHING)) - self._scan_batching = MIN_SCAN_BATCHING - elif scan_batching > MAX_SCAN_BATCHING: - logging.warning("Bad value for scan_batching: {}. Enforcing maximum value of {}".format(scan_batching, MAX_SCAN_BATCHING)) - self._scan_batching = MAX_SCAN_BATCHING - else: - self._scan_batching = scan_batching - - def connect(self): - # Connects to the modbus device - if self._variant == 'sungrow': - # Some later versions of the sungrow inverter firmware encrypts the payloads of - # the modbus traffic. https://github.com/rpvelloso/Sungrow-Modbus is a drop-in - # replacement for ModbusTcpClient that manages decrypting the traffic for us. - self._mb = SungrowModbusTcpClient.SungrowModbusTcpClient(host=self._ip, port=self._port, - framer=ModbusSocketFramer, timeout=1, - RetryOnEmpty=True, retries=1) - else: - self._mb = ModbusTcpClient(self._ip, self._port, - framer=ModbusSocketFramer, timeout=1, - RetryOnEmpty=True, retries=1) - - def add_monitor_register(self, table, addr, type='uint16'): - # Accepts a modbus register and table to monitor - if table not in self._tables: - raise ValueError("Unsupported table type. Please only use: {}".format(self._tables.keys())) - # Register enough sequential addresses to fill the size of the register type. - # Note: Each address provides 2 bytes of data. - for i in range(type_length(type)): - self._tables[table].add(addr+i) - - def poll(self): - # Polls for the values marked as interesting in self._tables. - for table in self._tables: - # This batches up modbus reads in chunks of self._scan_batching - start = -1 - for k in sorted(self._tables[table]): - group = int(k) - int(k) % self._scan_batching - if (start < group): - try: - values = self._scan_value_range(table, group, self._scan_batching) - for x in range(0, self._scan_batching): - key = group + x - self._values[table][key] = values[x] - # Avoid back-to-back read operations that could overwhelm some modbus devices. - sleep(DEFAULT_READ_SLEEP_S) - except ValueError as e: - logging.exception("{}".format(e)) - start = group + self._scan_batching-1 - self._process_writes() - - def get_value(self, table, addr, type='uint16'): - if table not in self._values: - raise ValueError("Unsupported table type. Please only use: {}".format(self._values.keys())) - if addr not in self._values[table]: - raise ValueError("Unpolled address. Use add_monitor_register(addr, table) to add a register to the polled list.") - # Read sequential addresses to get enough bytes to satisfy the type of this register. - # Note: Each address provides 2 bytes of data. - value = bytes(0) - type_len = type_length(type) - for i in range(type_len): - if self._word_order == WordOrder.HighLow: - data = self._values[table][addr + i] - else: - data = self._values[table][addr + (type_len-i-1)] - value += data.to_bytes(2,'big') - value = _convert_from_bytes_to_type(value, type) - return value - - def set_value(self, table, addr, value, mask=0xFFFF, type='uint16'): - if table != 'holding': - # I'm not sure if this is true for all devices. I might support writing to coils later, - # so leave this door open. - raise ValueError("Can only set values in the holding table.") - - bytes_to_write = _convert_from_type_to_bytes(value, type) - # Put the bytes into _planned_writes stitched into two-byte pairs - - type_len = type_length(type) - for i in range(type_len): - if self._word_order == WordOrder.HighLow: - value = _convert_from_bytes_to_type(bytes_to_write[i*2:i*2+2], 'uint16') - else: - value = _convert_from_bytes_to_type(bytes_to_write[(type_len-i-1)*2:(type_len-i-1)*2+2], 'uint16') - self._planned_writes.put((addr+i, value, mask)) - - self._process_writes() - - def _process_writes(self, max_block_s=DEFAULT_WRITE_BLOCK_INTERVAL_S): - # TODO I am not entirely happy with this system. It's supposed to prevent - # anything overwhelming the modbus interface with a heap of rapid writes, - # but without its own event loop it could be quite a while between calls to - # .poll()... - if self._writing: - return - write_start_time = time() - try: - self._writing = True - while not self._planned_writes.empty() and (time() - write_start_time) < max_block_s: - addr, value, mask = self._planned_writes.get() - if mask == 0xFFFF: - self._mb.write_register(addr, value, unit=0x01) - else: - # https://pymodbus.readthedocs.io/en/latest/source/library/pymodbus.client.html?highlight=mask_write_register#pymodbus.client.common.ModbusClientMixin.mask_write_register - # https://www.mathworks.com/help/instrument/modify-the-contents-of-a-holding-register-using-a-mask-write.html - # Result = (register value AND andMask) OR (orMask AND (NOT andMask)) - # This bit-shift weirdness is to avoid a mask of 0x0001 resulting in a ~mask of -2, which pymodbus doesn't like. - # This means the result will be 65534, AKA 0xFFFE. - # This specific read-before-write operation doesn't work on my modbus solar inverter - - # I get "Modbus Error: [Input/Output] Modbus Error: [Invalid Message] Incomplete message received, expected at least 8 bytes (0 received)" - # I suspect it's a different modbus opcode that tries to do clever things that my device doesn't support. - # result = self._mb.mask_write_register(address=addr, and_mask=(1<<16)-1-mask, or_mask=value, unit=0x01) - # print("Result: {}".format(result)) - old_value = self._scan_value_range('holding', addr, 1)[0] - and_mask = (1<<16)-1-mask - or_mask = value - new_value = (old_value & and_mask) | (or_mask & (mask)) - self._mb.write_register(addr, new_value, unit=0x01) - sleep(DEFAULT_WRITE_SLEEP_S) - except Exception as e: - # BUG catch only the specific exception that means pymodbus failed to write to a register - # the modbus device doesn't support, not an error at the TCP layer. - logging.exception("Failed to write to modbus device: {}".format(e)) - finally: - self._writing = False - - def _scan_value_range(self, table, start, count): - result = None - if table == 'input': - result = self._mb.read_input_registers(start, count, unit=0x01) - elif table == 'holding': - result = self._mb.read_holding_registers(start, count, unit=0x01) - try: - return result.registers - except: - # The result doesn't have a registers attribute, something has gone wrong! - raise ValueError("Failed to read {} {} table registers starting from {}: {}".format(count, table, start, result)) - -def type_length(type): - # Return the number of addresses needed for the type. - # Note: Each address provides 2 bytes of data. - if type in ['int16', 'uint16']: - return 1 - elif type in ['int32', 'uint32']: - return 2 - elif type in ['int64', 'uint64']: - return 4 - raise ValueError ("Unsupported type {}".format(type)) - -def type_signed(type): - # Returns whether the provided type is signed - if type in ['uint16', 'uint32', 'uint64']: - return False - elif type in ['int16', 'int32', 'int64']: - return True - raise ValueError ("Unsupported type {}".format(type)) - -def _convert_from_bytes_to_type(value, type): - type = type.strip().lower() - signed = type_signed(type) - return int.from_bytes(value,byteorder='big',signed=signed) - -def _convert_from_type_to_bytes(value, type): - type = type.strip().lower() - signed = type_signed(type) - # This can throw an OverflowError in various conditons. This will usually - # percolate upwards and spit out an exception from on_message. - return int(value).to_bytes(type_length(type)*2,byteorder='big',signed=signed) diff --git a/modbus4mqtt/version.py b/modbus4mqtt/version.py deleted file mode 100644 index 974881c..0000000 --- a/modbus4mqtt/version.py +++ /dev/null @@ -1 +0,0 @@ -version = "0.6.1" From 223a28e88bdbc2a739f3a92306a047efd26432ae Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:33:17 +0200 Subject: [PATCH 011/109] Add files via upload thes are files made aut of multiple forks and repos. cleaned up for my needs. updated Readme ! reverted back to "localhost" as default broker. removed "modbus4mqtt version ** connected " message removed version file and made it EW.0.7 removed docker added a unix.service file with wildcard config for activating an service for known yaml in directory /etc/modbus4mqtt/ --- autostart | 10 + modbus4mqtt.py | 422 +++++++++++++++++++++++++++++++++++++++++++ modbus4mqtt@.service | 14 ++ modbus_interface.py | 334 ++++++++++++++++++++++++++++++++++ 4 files changed, 780 insertions(+) create mode 100644 autostart create mode 100644 modbus4mqtt.py create mode 100644 modbus4mqtt@.service create mode 100644 modbus_interface.py diff --git a/autostart b/autostart new file mode 100644 index 0000000..d988249 --- /dev/null +++ b/autostart @@ -0,0 +1,10 @@ +#!/bin/bash +#add this with "@reboot" to your crontab +#every config.yaml in directory will be launched as own instance +# adjust path to your destination +sleep 10 +cd /etc/modbus4mqtt/ +for f in $( ls *.yaml); do + systemctl start modbus4mqtt@$f.service + echo $f +done diff --git a/modbus4mqtt.py b/modbus4mqtt.py new file mode 100644 index 0000000..3d52b0c --- /dev/null +++ b/modbus4mqtt.py @@ -0,0 +1,422 @@ +#!/usr/bin/python3 + +from time import sleep +import time +import json +import logging +from collections import defaultdict, OrderedDict +from ccorp.ruamel.yaml.include import YAML +import click +import paho.mqtt.client as mqtt + +from . import modbus_interface +version = "EW.0.7" +MAX_DECIMAL_POINTS = 3 +DEFAULT_SCAN_RATE_S = 5 + +def set_json_message_value(message, json_key, value): + target = message + json_keys = json_key.split('.') + if len(json_keys) > 1: + for json_key in json_keys[:-1]: + if json_key not in target: + target[json_key] = dict() + target = target[json_key] + + old = target.get(json_keys[-1], None) + if old is not None: + if not isinstance(old, list): + old = [ old ] + old.append(value) + value = old + target[json_keys[-1]] = value + +class mqtt_interface(): + config = { } + def __init__(self, hostname, port, username, password, config_file, mqtt_topic_prefix, + use_tls=True, insecure=False, cafile=None, cert=None, key=None): + self.hostname = hostname + self._port = port + self.username = username + self.password = password + self.config = self._load_modbus_config(config_file) + self.use_tls = use_tls + self.insecure = insecure + self.cafile = cafile + self.cert = cert + self.key = key + if not mqtt_topic_prefix.endswith('/'): + mqtt_topic_prefix = mqtt_topic_prefix + '/' + self.prefix = mqtt_topic_prefix + self.modbus_connect_retries = -1 # Retry forever by default + self.modbus_reconnect_sleep_interval = 5 # Wait this many seconds between modbus connection attempts + + self._errors = { } + + def get_DeviceUnit(self, register, unit=None): + device = register.get('device', None) + if device: + return device + table = register.get('table', 'holding') + unit = register.get('unit', unit) + if unit is None: + unit = 0x03 + if 'options' in self.config: + unit = self.config['options'].get('unit', unit) + return modbus_interface.DeviceUnit(table=table, unit=unit) + + def connect(self): + # Connects to modbus and MQTT. + self.connect_modbus() + self.connect_mqtt() + + def connect_modbus(self): + self._mb = modbus_interface.modbus_interface(self.config['url'], + options=self.config.get('options', { }), + ) + failed_attempts = 1 + while self._mb.connect(): + logging.warning("Modbus connection attempt {} failed. Retrying...".format(failed_attempts)) + failed_attempts += 1 + if self.modbus_connect_retries != -1 and failed_attempts > self.modbus_connect_retries: + logging.error("Failed to connect to modbus. Giving up.") + self.modbus_connection_failed() + # This weird break is here because we mock out modbus_connection_failed in the tests + break + sleep(self.modbus_reconnect_sleep_interval) + # Tells the modbus interface about the registers we consider interesting. + logging.info("Connected to modbus on {}".format(self._mb.getDevice())) + cnt = set() + for register in self.registers: + address = register.get('address', None) + if address is not None: + device_unit = self.get_DeviceUnit(register) + key = (device_unit, address) + self._mb.add_monitor_register(device_unit, address, register.get('type', 'uint16')) + cnt.add( key ) + register['value'] = None + unit_cnt = len(set(map(lambda x: x[0].unit, cnt))) + logging.info("Added {} unique registers.".format(len(cnt))) + if unit_cnt > 1: + logging.info("Will poll {} units.".format(unit_cnt)) + self._mb.prepare() + + def modbus_connection_failed(self): + exit(1) + + def connect_mqtt(self): + self._mqtt_client = mqtt.Client() + self._mqtt_client.username_pw_set(self.username, self.password) + self._mqtt_client._on_connect = self._on_connect + self._mqtt_client._on_disconnect = self._on_disconnect + self._mqtt_client._on_message = self._on_message + self._mqtt_client._on_subscribe = self._on_subscribe + if self.use_tls: + self._mqtt_client.tls_set(ca_certs=self.cafile, certfile=self.cert, keyfile=self.key) + self._mqtt_client.tls_insecure_set(self.insecure) + self._mqtt_client.connect(self.hostname, self._port, 60) + self._mqtt_client.loop_start() + + def _get_registers_with(self, required_key): + # Returns the registers containing the required_key + return [register for register in self.registers if required_key in register] + + def getRegisterError(self, registerKey): + return self._errors.get(registerKey, False) + + def _setRegisterError(self, registerKey): + if registerKey in self._errors: + return False + self._errors[registerKey] = True + return True + + def _clearRegisterError(self, registerKey): + self._errors.pop(registerKey, None) + + def poll(self): + try: + self._mb.poll() + except Exception as e: + logging.exception("Failed to poll modbus device, attempting to reconnect: {}".format(e)) + self.connect_modbus() + return + + # This is used to store values that are published as JSON messages rather than individual values + json_messages = {} + json_messages_retain = {} + json_messages_changed = {} + json_messages_sort = {} + + for register in self._get_registers_with('pub_topic'): + deviceUnit = self.get_DeviceUnit(register) + special = register.get('special', None) + address = register.get('address', None) + registerKey = (deviceUnit, special or address) + + if special: + if special == 'epoch': + value = int( time.time() ) + elif special == 'time': + value = time.localtime() + format = register.get('format', '%c') + if format: + value = time.strftime(format, value) + else: + if self._setRegisterError(registerKey): + logging.warning("Register {}: Unknown special {}".format(deviceUnit, special)) + continue + elif address is not None: + try: + value = self._mb.get_value( deviceUnit, + address, + register.get('type', 'uint16')) + except Exception as e: + if self._setRegisterError(registerKey): + logging.warning("Couldn't get value from register {}, address {}".format(deviceUnit, address)) + logging.debug(e, stack_info=True) + continue + + # Filter the value through the mask, if present. + if 'mask' in register: + # masks only make sense for uint + if register.get('type', 'uint16') in ['uint16', 'uint32', 'uint64']: + value &= register.get('mask') + # Scale the value, if required. + value *= register.get('scale', 1) + # Clamp the number of decimal points + value = round(value, register.get('precision', MAX_DECIMAL_POINTS)) + else: + if self._setRegisterError(registerKey): + logging.warning("Unsupported register type for register {}".format(registerKey)) + continue + + changed = not register.get('pub_only_on_change', True) + if value != register['value']: + if not special: + changed = True + register['value'] = value + + # Map from the raw number back to the human-readable form + if 'value_map' in register: + if value in register['value_map'].values(): + # This is a bit weird... + value = [human for human, raw in register['value_map'].items() if raw == value][0] + + self._clearRegisterError(registerKey) + + if register.get('json_key', False): + # This value won't get published to MQTT immediately. It gets stored and sent at the end of the poll. + if register['pub_topic'] not in json_messages: + json_messages[register['pub_topic']] = OrderedDict() + json_messages_retain[register['pub_topic']] = False + json_messages_sort[register['pub_topic']] = register['json_key'] + json_messages_changed[register['pub_topic']] = changed + set_json_message_value(json_messages[register['pub_topic']], register['json_key'], value) + if changed and not register.get('json_ignore_changed', False): + json_messages_changed[register['pub_topic']] = True + if 'retain' in register: + json_messages_retain[register['pub_topic']] = register['retain'] + elif changed: + retain = register.get('retain', False) + self._mqtt_client.publish(self.prefix+register['pub_topic'], value, retain=retain) + + # Transmit the queued JSON messages. + for topic, message in json_messages.items(): + if json_messages_changed[topic]: + m = json.dumps(message, sort_keys=json_messages_sort[topic]) + self._mqtt_client.publish(self.prefix+topic, m, retain=json_messages_retain[topic]) + + def _on_connect(self, client, userdata, flags, rc): + if rc == 0: + logging.info("Connected to MQTT.") + else: + logging.error("Couldn't connect to MQTT.") + return + # Subscribe to all the set topics. + for register in self._get_registers_with('set_topic'): + self._mqtt_client.subscribe(self.prefix+register['set_topic']) + print("Subscribed to {}".format(self.prefix+register['set_topic'])) + # Publish info message with retain + # self._mqtt_client.publish(self.prefix+'modbus4mqtt', 'modbus4mqtt v{} connected.'.format(version.version), retain=True) + + def _on_disconnect(self, client, userdata, rc): + logging.warning("Disconnected from MQTT. Attempting to reconnect.") + + def _on_subscribe(self, client, userdata, mid, granted_qos): + pass + + def _on_message(self, client, userdata, msg): + # print("got a message: {}: {}".format(msg.topic, msg.payload)) + # TODO Handle json_key writes. https://github.com/tjhowse/modbus4mqtt/issues/23 + topic = msg.topic[len(self.prefix):] + for register in self._get_registers_with('set_topic'): + if topic != register['set_topic']: + continue + # We received a set topic message for this topic. + value = msg.payload + if 'value_map' in register: + try: + value = str(value, 'utf-8') + if value not in register['value_map']: + logging.warning("Value not in value_map. Topic: {}, value: {}, valid values: {}".format(topic, + value, register['value_map'].keys())) + continue + # Map the value from the human-readable form into the raw modbus number + value = register['value_map'][value] + except UnicodeDecodeError: + logging.warning("Failed to decode MQTT payload as UTF-8. " + "Can't compare it to the value_map for register {}".format(register)) + continue + try: + # Scale the value, if required. + value = float(value) + value = round(value/register.get('scale', 1)) + except ValueError: + logging.error("Failed to convert register value for writing. " + "Bad/missing value_map? Topic: {}, Value: {}".format(topic, value)) + continue + type = register.get('type', 'uint16') + self._mb.set_value(self.get_DeviceUnit(register), register['address'], int(value), + register.get('mask', 0xFFFF), type) + + # This throws ValueError exceptions if the imported registers are invalid + @staticmethod + def _validate_registers(registers, duplicate_json_key): + all_pub_topics = set() + duplicate_pub_topics = set() + # Key: shared pub_topics, value: list of json_keys + duplicate_json_keys = {} + # Key: shared pub_topics, value: set of retain values (true/false) + retain_setting = {} + valid_types = modbus_interface.valid_types + + # Look for duplicate pub_topics + for register in registers: + type = register.get('type', 'uint16') + if type not in valid_types: + raise ValueError("Bad YAML configuration. Register has invalid type '{}'.".format(type)) + if 'json_key' in register and 'set_topic' in register: + raise ValueError("Bad YAML configuration. Register with set_topic '{}' has a json_key specified. " + "This is invalid. See https://github.com/tjhowse/modbus4mqtt/issues/23 for details." + .format(register['set_topic'])) + if 'pub_topic' in register: + if register['pub_topic'] in all_pub_topics: + duplicate_pub_topics.add(register['pub_topic']) + duplicate_json_keys[register['pub_topic']] = [] + retain_setting[register['pub_topic']] = set() + all_pub_topics.add(register['pub_topic']) + + # Check that all registers with duplicate pub topics have json_keys + for register in registers: + if 'pub_topic' in register: + if register['pub_topic'] in duplicate_pub_topics: + if 'json_key' not in register: + raise ValueError("Bad YAML configuration. pub_topic '{}' duplicated across registers without " + "json_key field. Registers that share a pub_topic must also have a unique " + "json_key.".format(register['pub_topic'])) + if register['json_key'] in duplicate_json_keys[register['pub_topic']]: + if duplicate_json_key == 'error': + raise ValueError("Bad YAML configuration. pub_topic '{}' duplicated across registers with a " + "duplicated json_key field. Registers that share a pub_topic must also have " + "a unique json_key.".format(register['pub_topic'])) + if duplicate_json_key == 'warn': + logging.warning("Bogus YAML configuration. pub_topic '{}' duplicated across registers with a " + "duplicated json_key field. Registers that share a pub_topic should have " + "a unique json_key. Use duplicate_json_key to configure behavior.".format(register['pub_topic'])) + duplicate_json_keys[register['pub_topic']] += [register['json_key']] + if 'retain' in register: + retain_setting[register['pub_topic']].add(register['retain']) + + # Check that there are no disagreements as to whether this pub_topic should be retained or not. + for topic, retain_set in retain_setting.items(): + if len(retain_set) > 1: + raise ValueError("Bad YAML configuration. pub_topic '{}' has conflicting retain settings." + .format(topic)) + + def _load_modbus_config(self, path): + yaml = YAML(typ='safe') + result = yaml.load(open(path, 'r').read()) + if 'options' not in result: + result['options'] = { 'unit': 0x01 } + self.address_offset = result.get('address_offset', 0) + if 'registers' in result: + # make copies of the register, to materialize all yaml aliases in each register + registers = [dict(register) for register in result['registers'] if 'pub_topic' in register or 'set_topic' in register] + for register in registers: + register['address'] += self.address_offset + elif 'devices' in result: + registers = list() + for device in result['devices']: + # make copies of the register, to materialize all yaml aliases in each register + device_registers = [dict(register) for register in device['registers'] if 'pub_topic' in register or 'set_topic' in register] + + unit = device.get('unit', None) + device_topic = device.get('pub_topic', '') + set_topic = device.get('set_topic', device_topic) # Use device_topic as default for set_topic + address_offset = device.get('address_offset', self.address_offset) + duplicate_json_key = device.get('duplicate_json_key', 'warn') + sort_json_keys = device.get('sort_json_keys', True) + + for register in device_registers: + if unit is not None: + register['unit'] = unit + if 'json_key' in register: + register['sort_json_keys'] = sort_json_keys + if 'pub_topic' in register: + register['pub_topic'] = '/'.join(filter(None, [device_topic, register['pub_topic']])) + if 'set_topic' in register: + register['set_topic'] = '/'.join(filter(None, [set_topic, register['set_topic']])) + if 'address' in register: + register['address'] += address_offset + register['device'] = self.get_DeviceUnit(register, unit) + mqtt_interface._validate_registers(device_registers, duplicate_json_key) + registers += device_registers + + mqtt_interface._validate_registers(registers, 'ignore') + self.registers = registers + return result + + def loop_forever(self): + while True: + # TODO this properly. + self.poll() + sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) + + +@click.command() +@click.option('--hostname', default='localhost', + help='The hostname or IP address of the MQTT server.', show_default=True) +@click.option('--port', default=1883, + help='The port of the MQTT server.', show_default=True) +@click.option('--username', default='username', + help='The username to authenticate to the MQTT server.', show_default=True) +@click.option('--password', default='password', + help='The password to authenticate to the MQTT server.', show_default=True) +@click.option('--mqtt_topic_prefix', default='wgw12/energy/pzem-004t', + help='A prefix for published MQTT topics.', show_default=True) +@click.option('--config', default='./modbus4mqtt.yaml', + help='The YAML config file for your modbus device.', show_default=True) +@click.option('--use_tls', default=False, + help='Configure network encryption and authentication options. Enables SSL/TLS.', show_default=True) +@click.option('--insecure', default=True, + help='Do not check that the server certificate hostname matches the remote hostname.', show_default=True) +@click.option('--cafile', default=None, + help='The path to a file containing trusted CA certificates to enable encryption.', show_default=True) +@click.option('--cert', default=None, + help='Client certificate for authentication, if required by server.', show_default=True) +@click.option('--key', default=None, + help='Client private key for authentication, if required by server.', show_default=True) +def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key): + logging.basicConfig( + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + logging.info("Starting modbus4mqtt {}".format(version)) + i = mqtt_interface(hostname, port, username, password, config, mqtt_topic_prefix, + use_tls, insecure, cafile, cert, key) + i.connect() + i.loop_forever() + + +if __name__ == '__main__': + main() diff --git a/modbus4mqtt@.service b/modbus4mqtt@.service new file mode 100644 index 0000000..ed466c8 --- /dev/null +++ b/modbus4mqtt@.service @@ -0,0 +1,14 @@ +#/etc/systemd/system/modbus4mqtt@.service +[Unit] +Description=check easywind online +Documentation=none +Wants=network-online.target +After=network-online.target + +[Service] +ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --config /etc/modbus4mqtt/%I +Restart=always +RestartSec=30 + +[Install] +WantedBy=default.target diff --git a/modbus_interface.py b/modbus_interface.py new file mode 100644 index 0000000..c7c58f9 --- /dev/null +++ b/modbus_interface.py @@ -0,0 +1,334 @@ +from time import time, sleep +from enum import Enum +from collections import namedtuple, defaultdict +import struct +import logging +from queue import Queue +from urllib.parse import urlparse, parse_qs +from pymodbus import exceptions + +DEFAULT_SCAN_BATCHING = 100 +MIN_SCAN_BATCHING = 1 +MAX_SCAN_BATCHING = 100 +DEFAULT_WRITE_BLOCK_INTERVAL_S = 0.2 +DEFAULT_WRITE_SLEEP_S = 0.05 +DEFAULT_READ_SLEEP_S = 0.05 +VALID_TABLES = [ 'input', 'holding' ] + +class WordOrder(Enum): + HighLow = 1 + LowHigh = 2 + +class DeviceUnit(namedtuple('DeviceUnit', [ 'unit', 'table' ])): + __slots__ = () + + def __str__(self): + return '{}@unit#{}'.format(self.table, self.unit) + + @classmethod + def get_unit(cls, instance, unit): + if isinstance(instance, DeviceUnit): + return instance.unit + return unit + + @classmethod + def get_table(cls, instance): + if isinstance(instance, DeviceUnit): + return instance.table + if isinstance(instance, str): + return instance + return instance[1] + +class modbus_interface(): + + def __init__(self, url, scan_batching=None, options={}): + self._url = urlparse(url) + # This is a dict of sets. Each key represents one table of modbus registers. + # At the moment it has 'input' and 'holding' + self._tables = defaultdict(set) + + # This is a dicts of dicts. These hold the current values of the interesting registers + self._values = defaultdict(dict) + + self._planned_writes = Queue() + self._writing = False + if options.get('word_order', 'highlow').lower() == 'lowhigh': + self._word_order = WordOrder.LowHigh + else: + self._word_order = WordOrder.HighLow + + self._unit = options.get('unit', 0x01) + self._range_batching = options.get('range_batching', False) + scan_batching = options.get('scan_batching', None) + if scan_batching is None: + self._scan_batching = DEFAULT_SCAN_BATCHING + else: + if scan_batching < MIN_SCAN_BATCHING: + logging.warning("Bad value for scan_batching: {}. Enforcing minimum value of {}".format(scan_batching, MIN_SCAN_BATCHING)) + self._scan_batching = MIN_SCAN_BATCHING + elif scan_batching > MAX_SCAN_BATCHING: + logging.warning("Bad value for scan_batching: {}. Enforcing maximum value of {}".format(scan_batching, MAX_SCAN_BATCHING)) + self._scan_batching = MAX_SCAN_BATCHING + else: + self._scan_batching = scan_batching + + def getDevice(self): + return self._url._replace(query='').geturl() + + def connect(self): + # Connects to the modbus device + if self._url.scheme == 'serial': + from pymodbus.client.sync import ModbusSerialClient, ModbusRtuFramer + port = self._url.path or self._url.netloc + params = parse_qs(self._url.query) + baudrate = params.get('baud', [9600])[0] + comset = params.get('comset', [''])[0] + if comset: + (bytesize, parity, stopbits) = comset + else: + (bytesize, parity, stopbits) = ( + params.get('bytesize', [8])[0], + params.get('parity', ['N'])[0], + params.get('stopbits', [1])[0] + ) + # baudrate parity stopbits + self._mb = ModbusSerialClient(port=port, + method='rtu', + timeout=1,RetryOnEmpty=True, retries=1 + , baudrate=int(baudrate), parity=parity, stopbits=int(stopbits)) + else: + host=self._url.hostname + port=self._url.port or 502 + if self._url.scheme == 'sungrow': + from SungrowModbusTcpClient import SungrowModbusTcpClient + # Some later versions of the sungrow inverter firmware encrypts the payloads of + # the modbus traffic. https://github.com/rpvelloso/Sungrow-Modbus is a drop-in + # replacement for ModbusTcpClient that manages decrypting the traffic for us. + self._mb = SungrowModbusTcpClient.SungrowModbusTcpClient(host=host, port=port, + framer=ModbusSocketFramer, timeout=1, + RetryOnEmpty=True, retries=1) + else: + try: + # Pymodbus >= 3.0 + # TODO: Once SungrowModbusTcpClient 0.1.7 is released, + # we can remove the "<3.0.0" pymodbus restriction and this + # will make sense again. + from pymodbus.client import ModbusTcpClient + from pymodbus.transaction import ModbusSocketFramer + except ImportError: + # Pymodbus < 3.0 + from pymodbus.client.sync import ModbusTcpClient, ModbusSocketFramer + self._mb = ModbusTcpClient(host, port, + framer=ModbusSocketFramer, timeout=1, + RetryOnEmpty=True, retries=1) + + def add_monitor_register(self, device_unit, addr, type='uint16'): + # Accepts a modbus register and table to monitor + table = DeviceUnit.get_table(device_unit) + if table not in VALID_TABLES: + raise ValueError("Unsupported table type {}. Please only use: {}".format(table, VALID_TABLES)) + # initialize default values ... + self._tables[device_unit] + self._values[device_unit] + + # Register enough sequential addresses to fill the size of the register type. + # Note: Each address provides 2 bytes of data. + for i in range(type_length(type)): + self._tables[device_unit].add(addr+i) + + def prepare(self): + self._registers = dict() + for (table, registers) in self._tables.items(): + registers = sorted(registers) + self._registers[table] = registers + + def _get_scan_ranges(self, registers): + ranges = [ ] + if self._range_batching: + offset = -1 + count = 0 + for k in registers: + if count < self._scan_batching: + if offset + count == k: + count = count + 1 + continue + if count > 0: + ranges.append( ( offset, count ) ) + offset = k + count = 1 + else: + offset = registers[0] + count = self._scan_batching + while offset+count <= registers[-1]: + ranges.append( ( offset, count ) ) + offset = offset + count + count = registers[-1] - offset + 1 + + if count > 0: + ranges.append( ( offset, count ) ) + + return ranges + + + def poll(self): + # Polls for the values marked as interesting in self._registers. + for (table, registers) in self._registers.items(): + ranges = self._get_scan_ranges(registers) + + for ( group, count ) in ranges: + try: + logging.debug('{}: Read {} registers, starting at {}'.format(table, count, group)) + values = self._scan_value_range(table, group, count) + for x in range(0, count): + key = group + x + self._values[table][key] = values[x] + # Avoid back-to-back read operations that could overwhelm some modbus devices. + sleep(DEFAULT_READ_SLEEP_S) + except ValueError as e: + logging.error("{}".format(e)) + logging.debug(e, stack_info=True) + self._process_writes() + + def get_value(self, table, addr, type='uint16'): + if table not in self._values: + raise ValueError("Unsupported table type. Please only use: {}".format(VALID_TABLES)) + if addr not in self._values[table]: + raise ValueError("Unpolled address. Use add_monitor_register({}, {}) to add a register to the polled list.".format(table, addr)) + # Read sequential addresses to get enough bytes to satisfy the type of this register. + # Note: Each address provides 2 bytes of data. + value = bytes(0) + type_len = type_length(type) + for i in range(type_len): + if self._word_order == WordOrder.HighLow: + data = self._values[table][addr + i] + else: + data = self._values[table][addr + (type_len-i-1)] + value += data.to_bytes(2,'big') + value = _convert_from_bytes_to_type(value, type, self._word_order) + return value + + def set_value(self, table, addr, value, mask=0xFFFF, type='uint16'): + if DeviceUnit.get_table(table) != 'holding': + # I'm not sure if this is true for all devices. I might support writing to coils later, + # so leave this door open. + raise ValueError("Can only set values in the holding table.") + + bytes_to_write = _convert_from_type_to_bytes(value, type, self._word_order) + # Put the bytes into _planned_writes stitched into two-byte pairs + + type_len = type_length(type) + for i in range(type_len): + if self._word_order == WordOrder.HighLow: + value = _convert_from_bytes_to_type(bytes_to_write[i*2:i*2+2], 'uint16', self._word_order) + else: + value = _convert_from_bytes_to_type(bytes_to_write[(type_len-i-1)*2:(type_len-i-1)*2+2], 'uint16', self._word_order) + self._planned_writes.put((table, addr+i, value, mask)) + + self._process_writes() + + def _process_writes(self, max_block_s=DEFAULT_WRITE_BLOCK_INTERVAL_S): + # TODO I am not entirely happy with this system. It's supposed to prevent + # anything overwhelming the modbus interface with a heap of rapid writes, + # but without its own event loop it could be quite a while between calls to + # .poll()... + if self._writing: + return + write_start_time = time() + try: + self._writing = True + while not self._planned_writes.empty() and (time() - write_start_time) < max_block_s: + device_unit, addr, value, mask = self._planned_writes.get() + unit = DeviceUnit.get_unit(device_unit, self._unit) + if mask == 0xFFFF: + self._mb.write_register(addr, value, unit=unit) + else: + # https://pymodbus.readthedocs.io/en/latest/source/library/pymodbus.client.html?highlight=mask_write_register#pymodbus.client.common.ModbusClientMixin.mask_write_register + # https://www.mathworks.com/help/instrument/modify-the-contents-of-a-holding-register-using-a-mask-write.html + # Result = (register value AND andMask) OR (orMask AND (NOT andMask)) + # This bit-shift weirdness is to avoid a mask of 0x0001 resulting in a ~mask of -2, which pymodbus doesn't like. + # This means the result will be 65534, AKA 0xFFFE. + # This specific read-before-write operation doesn't work on my modbus solar inverter - + # I get "Modbus Error: [Input/Output] Modbus Error: [Invalid Message] Incomplete message received, expected at least 8 bytes (0 received)" + # I suspect it's a different modbus opcode that tries to do clever things that my device doesn't support. + # result = self._mb.mask_write_register(address=addr, and_mask=(1<<16)-1-mask, or_mask=value, unit=0x01) + # print("Result: {}".format(result)) + old_value = self._scan_value_range(device_unit, addr, 1)[0] + and_mask = (1<<16)-1-mask + or_mask = value + new_value = (old_value & and_mask) | (or_mask & (mask)) + self._mb.write_register(addr, new_value, unit=unit) + sleep(DEFAULT_WRITE_SLEEP_S) + except Exception as e: + # BUG catch only the specific exception that means pymodbus failed to write to a register + # the modbus device doesn't support, not an error at the TCP layer. + logging.error("Failed to write to modbus device: {}".format(e)) + logging.debug(e, stack_info=True) + finally: + self._writing = False + + def _scan_value_range(self, device_unit, start, count): + result = None + table = DeviceUnit.get_table(device_unit) + unit = DeviceUnit.get_unit(device_unit, self._unit) + if table == 'input': + result = self._mb.read_input_registers(start, count, unit=unit) + elif table == 'holding': + result = self._mb.read_holding_registers(start, count, unit=unit) + try: + return result.registers + except: + # The result doesn't have a registers attribute, something has gone wrong! + raise ValueError("Failed to read {} {} table registers from unit {} starting from {}: {}".format(count, table, unit, start, result)) + +valid_types = [ 'uint16', 'int16' + , 'uint32', 'int32' + , 'uint64', 'int64' + , 'float' + , 'float_be', '>float' + , 'float_le', 'float' ): + return struct.unpack('>f', value)[0] + elif type in ( 'float_le', 'float' ): + return struct.pack('>f', value) + elif type in ( 'float_le', ' Date: Mon, 7 Aug 2023 00:33:41 +0200 Subject: [PATCH 012/109] Delete README.md --- README.md | 121 ------------------------------------------------------ 1 file changed, 121 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 336fec5..0000000 --- a/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# Modbus4MQTT - -https://github.com/tjhowse/modbus4mqtt - -https://pypi.org/project/modbus4mqtt/ - -![](https://github.com/tjhowse/modbus4mqtt/workflows/Unit%20Tests/badge.svg) - -[![codecov](https://codecov.io/gh/tjhowse/modbus4mqtt/branch/master/graph/badge.svg)](https://codecov.io/gh/tjhowse/modbus4mqtt) - -This is a gateway that translates between modbus TCP/IP and MQTT. - -The mapping of modbus registers to MQTT topics is in a simple YAML file. - -The most up-to-date docs will always be on Github. - -## Similar software - -There is already good software out there that can do what Modbus4MQTT does, but none -that I could find that has this functionality as its focus. Do one thing and do it well. -Modbus4mqtt is designed to fit in as a component of other systems, rather than trying to -be a complete solution. - -* [Solariot](https://github.com/meltaxa/solariot) -* [PVStats](https://github.com/ptarcher/pvstats) ([Fork](https://github.com/tjhowse/pvstats)) - -## Installation - -### Python module - -```bash -$ pip3 install --user modbus4mqtt -$ modbus4mqtt --help -``` - -### Docker container - -Alternatively you can run Modbus4MQTT in a Docker container. A [Dockerfile](./Dockerfile) example is provided. - -```bash -$ docker pull tjhowse/modbus4mqtt:latest -$ docker run modbus4mqtt --help -``` -When launching inside the docker container you will either need to use one of the built-in YAMLs like `/modbus4mqtt/modbus4mqtt/Sungrow_SH5k_20.yaml`, or map your custom YAML into the container in a volume. - -## YAML definition - -Look at the [Sungrow SH5k-20](./modbus4mqtt/Sungrow_SH5k_20.yaml) configuration YAML for a working example. - -### Modbus device settings -```yaml -ip: 192.168.1.89 -port: 502 -update_rate: 5 -address_offset: 0 -variant: sungrow -scan_batching: 100 -word_order: highlow -``` -| Field name | Required | Default | Description | -| ---------- | -------- | ------- | ----------- | -| ip | Required | N/A | The IP address of the modbus device to be polled. Presently only modbus TCP/IP is supported. | -| port | Optional | 502 | The port on the modbus device to connect to. | -| update_rate | Optional | 5 | The number of seconds between polls of the modbus device. | -| address_offset | Optional | 0 | This offset is applied to every register address to accommodate different Modbus addressing systems. In many Modbus devices the first register is enumerated as 1, other times 0. See section 4.4 of the Modbus spec. | -| variant | Optional | N/A | Allows variants of the ModbusTcpClient library to be used. Setting this to 'sungrow' enables support of SungrowModbusTcpClient. This library transparently decrypts the modbus comms with sungrow SH inverters running newer firmware versions. | -| scan_batching | Optional | 100 | Must be between 1 and 100 inclusive. Modbus read operations are more efficient in bigger batches of contiguous registers, but different devices have different limits on the size of the batched reads. This setting can also be helpful when building a modbus register map for an uncharted device. In some modbus devices a single invalid register in a read range will fail the entire read operation. By setting `scan_batching` to `1` each register will be scanned individually. This will be very inefficient and should not be used in production as it will saturate the link with many read operations. | -| word_order | Optional | 'highlow' | Must be either `highlow` or `lowhigh`. This determines how multi-word values are interpreted. `highlow` means a 32-bit number at address 1 will have its high two bytes stored in register 1, and its low two bytes stored in register 2. The default is typically correct, as modbus has a big-endian memory structure, but this is not universal. | - -### Register settings -```yaml -registers: - - pub_topic: "forced_charge/mode" - set_topic: "forced_charge/mode/set" - retain: true - pub_only_on_change: false - table: 'holding' - address: 13140 - value_map: - enabled: 170 - disabled: 85 - - pub_topic: "forced_charge/period_1/start_hours" - set_topic: "forced_charge/period_1/start_hours/set" - pub_only_on_change: true - table: 'holding' - address: 13142 - - pub_topic: "voltage_in_mv" - address: 13000 - scale: 1000 - - pub_topic: "first_bit_of_second_byte" - address: 13001 - mask: 0x0010 - - pub_topic: "load_control/optimized/end_time" - address: 13013 - json_key: hours - - pub_topic: "load_control/optimized/end_time" - address: 13014 - json_key: minutes - - pub_topic: "external_temperature" - address: 13015 - type: int16 - - pub_topic: "minutes_online" - address: 13016 - type: uint32 -``` - -This section of the YAML lists all the modbus registers that you consider interesting. - -| Field name | Required | Default | Description | -| ---------- | -------- | ------- | ----------- | -| address | Required | N/A | The decimal address of the register to read from the device, starting at 0. Many modbus devices enumerate registers beginning at 1, so beware. | -| pub_topic | Optional | N/A | This is the topic to which the value of this register will be published. | -| set_topic | Optional | N/A | Values published to this topic will be written to the Modbus device. Cannot yet be combined with json_key. See https://github.com/tjhowse/modbus4mqtt/issues/23 for details. | -| retain | Optional | false | Controls whether the value of this register will be published with the retain bit set. | -| pub_only_on_change | Optional | true | Controls whether this register will only be published if its value changed from the previous poll. | -| table | Optional | holding | The Modbus table to read from the device. Must be 'holding' or 'input'. | -| value_map | Optional | N/A | A series of human-readable and raw values for the setting. This will be used to translate between human-readable values via MQTT to raw values via Modbus. If a value_map is set for a register the interface will reject raw values sent via MQTT. If value_map is not set the interface will try to set the Modbus register to that value. Note that the scale is applied after the value is read from Modbus and before it is written to Modbus. | -| scale | Optional | 1 | After reading a value from the Modbus register it will be multiplied by this scalar before being published to MQTT. Values published on this register's `set_topic` will be divided by this scalar before being written to Modbus. | -| mask | Optional | 0xFFFF | This is a 16-bit number that can be used to select a part of a Modbus register to be referenced by this register. For example a mask of `0xFF00` will map to the most significant byte of the 16-bit Modbus register at `address`. A mask of `0x0001` will reference only the least significant bit of this register. | -| json_key | Optional | N/A | The value of this register will be published to its pub_topic in JSON format. E.G. `{ key: value }` Registers with a json_key specified can share a pub_topic. All registers with shared pub_topics must have a json_key specified. In this way, multiple registers can be published to the same topic in a single JSON message. If any of the registers that share a pub_topic have the retain field set that will affect the published JSON message. Conflicting retain settings are invalid. The keys will be alphabetically sorted. | -| type | Optional | uint16 | The type of the value stored at the modbus address provided. Only uint16 (unsigned 16-bit integer), int16 (signed 16-bit integer), uint32, int32, uint64 and int64 are currently supported. | From 30498e6207a2325ec82dde6c9053eeb7ec9ebb75 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:33:57 +0200 Subject: [PATCH 013/109] Add files via upload --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a458eec --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# Modbus4MQTT +this is my fork from modbus4mqtt + +https://github.com/tjhowse/modbus4mqtt +https://pypi.org/project/modbus4mqtt/ + +![](https://github.com/tjhowse/modbus4mqtt/workflows/Unit%20Tests/badge.svg) + +[![codecov](https://codecov.io/gh/tjhowse/modbus4mqtt/branch/master/graph/badge.svg)](https://codecov.io/gh/tjhowse/modbus4mq + + + +## Installation + +```bash +pip3 install modbus4mqtt +pip3 install ccorp-yaml-include-relative-path + +ln -s /usr/local/lib/python3.6/dist-packages/modbus4mqtt /etc/modbus4mqtt + +git clone https://github.com/Pubaluba/modbus4mqtt_w_float + +cd modbus4mqtt_w_float/modbus4mqtt + +cp * /etc/modbus4mqtt/ +modbus4mqtt --help + +``` + +# Yaml Configuration + +### Modbus device settings +```yaml + +ip: 192.168.1.89 +port: 502 +update_rate: 30 +address_offset: 0 +scan_batching: 100 +word_order: highlow +``` +| Field name | Required | Default | Description | +| ---------- | -------- | ------- | ----------- | +| ip | Required | N/A | The IP address of the modbus device to be polled. Presently only modbus TCP/IP is supported. | +| port | Optional | 502 | The port on the modbus device to connect to. | +| update_rate | Optional | 5 | The number of seconds between polls of the modbus device. | +| address_offset | Optional | 0 | This offset is applied to every register address to accommodate different Modbus addressing systems. In many Modbus devices the first register is enumerated as 1, other times 0. See section 4.4 of the Modbus spec. | +| variant | Optional | N/A | Allows variants of the ModbusTcpClient library to be used. Setting this to 'sungrow' enables support of SungrowModbusTcpClient. This library transparently decrypts the modbus comms with sungrow SH inverters running newer firmware versions. | +| scan_batching | Optional | 100 | Must be between 1 and 100 inclusive. Modbus read operations are more efficient in bigger batches of contiguous registers, but different devices have different limits on the size of the batched reads. This setting can also be helpful when building a modbus register map for an uncharted device. In some modbus devices a single invalid register in a read range will fail the entire read operation. By setting `scan_batching` to `1` each register will be scanned individually. This will be very inefficient and should not be used in production as it will saturate the link with many read operations. | +| word_order | Optional | 'highlow' | Must be either `highlow` or `lowhigh`. This determines how multi-word values are interpreted. `highlow` means a 32-bit number at address 1 will have its high two bytes stored in register 1, and its low two bytes stored in register 2. The default is typically correct, as modbus has a big-endian memory structure, but this is not universal. | + +### Register settings +```yaml +registers: + - pub_topic: "forced_charge/mode" + set_topic: "forced_charge/mode/set" + retain: true + pub_only_on_change: false + table: 'holding' + address: 13140 + value_map: + enabled: 170 + disabled: 85 + - pub_topic: "forced_charge/period_1/start_hours" + set_topic: "forced_charge/period_1/start_hours/set" + pub_only_on_change: true + table: 'holding' + address: 13142 + - pub_topic: "voltage_in_mv" + address: 13000 + scale: 1000 + - pub_topic: "first_bit_of_second_byte" + address: 13001 + mask: 0x0010 + - pub_topic: "load_control/optimized/end_time" + address: 13013 + json_key: hours + - pub_topic: "load_control/optimized/end_time" + address: 13014 + json_key: minutes + - pub_topic: "external_temperature" + address: 13015 + type: int16 + - pub_topic: "minutes_online" + address: 13016 + type: uint32 +``` + +This section of the YAML lists all the modbus registers that you consider interesting. + +| Field name | Required | Default | Description | +| ---------- | -------- | ------- | ----------- | +| address | Required | N/A | The decimal address of the register to read from the device, starting at 0. Many modbus devices enumerate registers beginning at 1, so beware. | +| pub_topic | Optional | N/A | This is the topic to which the value of this register will be published. | +| set_topic | Optional | N/A | Values published to this topic will be written to the Modbus device. Cannot yet be combined with json_key. See https://github.com/tjhowse/modbus4mqtt/issues/23 for details. | +| retain | Optional | false | Controls whether the value of this register will be published with the retain bit set. | +| pub_only_on_change | Optional | true | Controls whether this register will only be published if its value changed from the previous poll. | +| table | Optional | holding | The Modbus table to read from the device. Must be 'holding' or 'input'. | +| value_map | Optional | N/A | A series of human-readable and raw values for the setting. This will be used to translate between human-readable values via MQTT to raw values via Modbus. If a value_map is set for a register the interface will reject raw values sent via MQTT. If value_map is not set the interface will try to set the Modbus register to that value. Note that the scale is applied after the value is read from Modbus and before it is written to Modbus. | +| scale | Optional | 1 | After reading a value from the Modbus register it will be multiplied by this scalar before being published to MQTT. Values published on this register's `set_topic` will be divided by this scalar before being written to Modbus. | +| mask | Optional | 0xFFFF | This is a 16-bit number that can be used to select a part of a Modbus register to be referenced by this register. For example a mask of `0xFF00` will map to the most significant byte of the 16-bit Modbus register at `address`. A mask of `0x0001` will reference only the least significant bit of this register. | +| json_key | Optional | N/A | The value of this register will be published to its pub_topic in JSON format. E.G. `{ key: value }` Registers with a json_key specified can share a pub_topic. All registers with shared pub_topics must have a json_key specified. In this way, multiple registers can be published to the same topic in a single JSON message. If any of the registers that share a pub_topic have the retain field set that will affect the published JSON message. Conflicting retain settings are invalid. The keys will be alphabetically sorted. | +| type | Optional | uint16 | The type of the value stored at the modbus address provided. Only uint16 (unsigned 16-bit integer), int16 (signed 16-bit integer), uint32, int32, uint64 and int64 are currently supported. | From 328af0a2a54736ec66401a0348e7b8b0ebe58285 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:54:24 +0200 Subject: [PATCH 014/109] Update README.md updated installation added installation of service unit --- README.md | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a458eec..8b36522 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,50 @@ # Modbus4MQTT -this is my fork from modbus4mqtt +this is my rebuild of my fork from a fork from a fork from modbus4mqtt by tjhowse. https://github.com/tjhowse/modbus4mqtt -https://pypi.org/project/modbus4mqtt/ - -![](https://github.com/tjhowse/modbus4mqtt/workflows/Unit%20Tests/badge.svg) - -[![codecov](https://codecov.io/gh/tjhowse/modbus4mqtt/branch/master/graph/badge.svg)](https://codecov.io/gh/tjhowse/modbus4mq +https://pypi.org/project/modbus4mqtt/ ## Installation ```bash +apt install python3-pip pip3 install modbus4mqtt pip3 install ccorp-yaml-include-relative-path +mkdir /etc/modbus4mqtt +git clone https://github.com/Pubaluba/modbus4mqtt_rebuild + +cd modbus4mqtt_rebuild +#move the service file to systemd +mv modbus4mqtt@.service /etc/systemd/system +systemctl daemon-reload +#move the python file to replace the original +mv *.py /usr/local/lib/python3.6/dist-packages/modbus4mqtt +#if there's an error +mv *.py /usr/local/lib/python3.10/dist-packages/modbus4mqtt +#move the remaining to configdir +mv * /etc/modbus4mqtt + +cd /etc/modbus4mqtt/ +modbus4mqtt --help -ln -s /usr/local/lib/python3.6/dist-packages/modbus4mqtt /etc/modbus4mqtt +test your config (example:) +modbus4mqtt --mqtt_topic_prefix "***" --hostname "***" --config /etc/modbus4mqtt/TCPRTU1.yaml +``` -git clone https://github.com/Pubaluba/modbus4mqtt_w_float +## to use a service: +copy a ./template/.yaml file to /etc/modbus4mqtt +systemctl start modbusmqtt@"yourfile(include!.yaml)" +systemctl status modbusmqtt@"yourfile(include!.yaml)" -cd modbus4mqtt_w_float/modbus4mqtt +the service uses the hostname as prefix +u can change this by editing the service file in /etc/systemd/system/ + + +# below to be done !! : -cp * /etc/modbus4mqtt/ -modbus4mqtt --help -``` # Yaml Configuration @@ -41,7 +60,7 @@ word_order: highlow ``` | Field name | Required | Default | Description | | ---------- | -------- | ------- | ----------- | -| ip | Required | N/A | The IP address of the modbus device to be polled. Presently only modbus TCP/IP is supported. | +|url | Required | N/A | The IP address of the modbus device to be polled. Presently only modbus TCP/IP is supported. | | port | Optional | 502 | The port on the modbus device to connect to. | | update_rate | Optional | 5 | The number of seconds between polls of the modbus device. | | address_offset | Optional | 0 | This offset is applied to every register address to accommodate different Modbus addressing systems. In many Modbus devices the first register is enumerated as 1, other times 0. See section 4.4 of the Modbus spec. | From b3feea379b2ef8ba43b6fd0e6e47dcabb8dfee88 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:54:54 +0200 Subject: [PATCH 015/109] Update modbus4mqtt@.service --- modbus4mqtt@.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt@.service b/modbus4mqtt@.service index ed466c8..94c684f 100644 --- a/modbus4mqtt@.service +++ b/modbus4mqtt@.service @@ -1,6 +1,6 @@ #/etc/systemd/system/modbus4mqtt@.service [Unit] -Description=check easywind online +Description=modbus4mqtt service Documentation=none Wants=network-online.target After=network-online.target From e1d6fa08b5c6bf0a979f71a13e4a49a9023ca04e Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:56:30 +0200 Subject: [PATCH 016/109] Update modbus4mqtt@.service --- modbus4mqtt@.service | 1 + 1 file changed, 1 insertion(+) diff --git a/modbus4mqtt@.service b/modbus4mqtt@.service index 94c684f..a1f031d 100644 --- a/modbus4mqtt@.service +++ b/modbus4mqtt@.service @@ -6,6 +6,7 @@ Wants=network-online.target After=network-online.target [Service] +# %H means Hostname of the system a topic_prefix. use complete name for service start "modbus4mqtt@TCPRTU1.yaml" ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --config /etc/modbus4mqtt/%I Restart=always RestartSec=30 From e5d2c5b29d846585367b18aafe69a688698ff924 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:58:37 +0200 Subject: [PATCH 017/109] Update autostart --- autostart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/autostart b/autostart index d988249..8d90b66 100644 --- a/autostart +++ b/autostart @@ -1,6 +1,9 @@ #!/bin/bash -#add this with "@reboot" to your crontab -#every config.yaml in directory will be launched as own instance +# add +# @reboot /etc/modbus4mqtt/autostart to your crontab +# u can also use +# */2 * * * * /etc/modbus4mqtt/autostart +# every config.yaml in directory will be launched as own instanceof the service # adjust path to your destination sleep 10 cd /etc/modbus4mqtt/ From 8845ab766ab5aa2d8e699ead7cc53db50f2f4089 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:58:58 +0200 Subject: [PATCH 018/109] Update autostart --- autostart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autostart b/autostart index 8d90b66..5e8817c 100644 --- a/autostart +++ b/autostart @@ -1,7 +1,7 @@ #!/bin/bash # add # @reboot /etc/modbus4mqtt/autostart to your crontab -# u can also use +# u can alternativly use # */2 * * * * /etc/modbus4mqtt/autostart # every config.yaml in directory will be launched as own instanceof the service # adjust path to your destination From 5afd4109c58f3a485b8b5da7b3c01a236179a0c5 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:59:14 +0200 Subject: [PATCH 019/109] Update autostart --- autostart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autostart b/autostart index 5e8817c..6d36050 100644 --- a/autostart +++ b/autostart @@ -3,7 +3,7 @@ # @reboot /etc/modbus4mqtt/autostart to your crontab # u can alternativly use # */2 * * * * /etc/modbus4mqtt/autostart -# every config.yaml in directory will be launched as own instanceof the service +# every config.yaml in directory will be launched as own instance of the service # adjust path to your destination sleep 10 cd /etc/modbus4mqtt/ From 491618b6bfe301b0cc0738af8ef65e8a3ba1558b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 01:00:51 +0200 Subject: [PATCH 020/109] Add files via upload --- templates/TCPRTU1.yaml | 136 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 templates/TCPRTU1.yaml diff --git a/templates/TCPRTU1.yaml b/templates/TCPRTU1.yaml new file mode 100644 index 0000000..623b56d --- /dev/null +++ b/templates/TCPRTU1.yaml @@ -0,0 +1,136 @@ +url: tcp://127.0.0.1:500 +#url: serial:///dev/ttysWK0 +update_rate: 30 +address_offset: 1 +scan_batching: 4 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: Netzanschluss + json_key: Bezug + unit: 0x01 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Netzanschluss + json_key: Einspeisung + unit: 0x01 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 2" + unit: 0x02 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 2" + unit: 0x02 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 3" + unit: 0x03 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 3" + unit: 0x03 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 4" + unit: 0x04 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 4" + unit: 0x04 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 5" + unit: 0x05 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 5" + unit: 0x05 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 6" + unit: 0x06 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 6" + unit: 0x06 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 7" + unit: 0x07 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 7" + unit: 0x07 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 8" + unit: 0x08 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 8" + unit: 0x08 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 \ No newline at end of file From 1546b289f50b8bdae7a4d7b751505dc8e2966984 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 01:35:55 +0200 Subject: [PATCH 021/109] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b36522..cee4eb5 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,13 @@ modbus4mqtt --mqtt_topic_prefix "***" --hostname "***" --config /etc/modbus4mqtt ## to use a service: copy a ./template/.yaml file to /etc/modbus4mqtt -systemctl start modbusmqtt@"yourfile(include!.yaml)" + +systemctl start modbusmqtt@"yourfile(include!.yaml)" + systemctl status modbusmqtt@"yourfile(include!.yaml)" the service uses the hostname as prefix + u can change this by editing the service file in /etc/systemd/system/ From 62027d24e1084dcab01481eab5fb19762a3fb1a1 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 01:39:03 +0200 Subject: [PATCH 022/109] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cee4eb5..25235bb 100644 --- a/README.md +++ b/README.md @@ -35,15 +35,18 @@ modbus4mqtt --mqtt_topic_prefix "***" --hostname "***" --config /etc/modbus4mqtt ## to use a service: copy a ./template/.yaml file to /etc/modbus4mqtt - +```bash systemctl start modbusmqtt@"yourfile(include!.yaml)" systemctl status modbusmqtt@"yourfile(include!.yaml)" +systemctl enable modbusmqtt@"yourfile(include!.yaml)" +``` the service uses the hostname as prefix u can change this by editing the service file in /etc/systemd/system/ - +the autostart file can be used instead: +every .yaml in /etc/modbus4mqtt/ will be started be this script. use cron or similar # below to be done !! : From 35202cc13c4a34ac60638ad5711dc4203e2b9761 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 19:46:24 +0200 Subject: [PATCH 023/109] Update modbus_interface.py --- modbus_interface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modbus_interface.py b/modbus_interface.py index c7c58f9..f77fa5d 100644 --- a/modbus_interface.py +++ b/modbus_interface.py @@ -286,6 +286,7 @@ def _scan_value_range(self, device_unit, start, count): , 'float' , 'float_be', '>float' , 'float_le', 'f', value)[0] elif type in ( 'float_le', 'f', value)[0] else: signed = type_signed(type) return int.from_bytes(value,byteorder='big',signed=signed) @@ -327,6 +332,8 @@ def _convert_from_type_to_bytes(value, type, word_order): return struct.pack('>f', value) elif type in ( 'float_le', 'f', value) else: signed = type_signed(type) # This can throw an OverflowError in various conditons. This will usually From d4396a5e99afec1fa783e4afc43d7cf35bc97535 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:03:39 +0200 Subject: [PATCH 024/109] Update modbus_interface.py Double intergrated to Python float_type register count Same as int64 --- modbus_interface.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/modbus_interface.py b/modbus_interface.py index f77fa5d..b24e99f 100644 --- a/modbus_interface.py +++ b/modbus_interface.py @@ -296,10 +296,8 @@ def type_length(type): return 1 elif type in ['int32', 'uint32', 'float']: return 2 - elif type in ['int64', 'uint64']: + elif type in ['int64', 'uint64', 'double']: return 4 - elif type in ['double'] - return 8 raise ValueError ("Unsupported type {}".format(type)) def type_signed(type): @@ -312,28 +310,24 @@ def type_signed(type): def _convert_from_bytes_to_type(value, type, word_order): type = type.strip().lower() - if type in ( 'float' ): + if type in ( 'float', 'double' ): type = '>float' if word_order == WordOrder.HighLow else 'float' ): return struct.unpack('>f', value)[0] elif type in ( 'float_le', 'f', value)[0] else: signed = type_signed(type) return int.from_bytes(value,byteorder='big',signed=signed) def _convert_from_type_to_bytes(value, type, word_order): type = type.strip().lower() - if type in ( 'float' ): + if type in ( 'float' , 'double'): type = '>float' if word_order == WordOrder.HighLow else 'float' ): return struct.pack('>f', value) elif type in ( 'float_le', 'f', value) else: signed = type_signed(type) # This can throw an OverflowError in various conditons. This will usually From 0a6e32cab36de3a091f6fa16714560ab6d779237 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:13:16 +0200 Subject: [PATCH 025/109] Update modbus_interface.py --- modbus_interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modbus_interface.py b/modbus_interface.py index b24e99f..d7bf69e 100644 --- a/modbus_interface.py +++ b/modbus_interface.py @@ -286,7 +286,7 @@ def _scan_value_range(self, device_unit, start, count): , 'float' , 'float_be', '>float' , 'float_le', 'float' ): return struct.unpack('>f', value)[0] @@ -322,7 +322,7 @@ def _convert_from_bytes_to_type(value, type, word_order): def _convert_from_type_to_bytes(value, type, word_order): type = type.strip().lower() - if type in ( 'float' , 'double'): + if type in ( 'float' , 'double', 'float64' ): type = '>float' if word_order == WordOrder.HighLow else 'float' ): return struct.pack('>f', value) From 9f7d331c1192575ac2ff7c30566999b812af5684 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 19:31:05 +0200 Subject: [PATCH 026/109] Update modbus_interface.py Added double, >double, float' , 'float_le', 'double', + , 'double_le', 'float' ): return struct.unpack('>f', value)[0] elif type in ( 'float_le', 'double' ): + return struct.unpack('>d', value)[0] + elif type in ( 'double_le', 'float' ): + if type in ( 'double'): + type = '>double' if word_order == WordOrder.HighLow else 'float' ): return struct.pack('>f', value) elif type in ( 'float_le', 'double' ): + return struct.pack('>d', value)[0] + elif type in ( 'double_le', ' Date: Fri, 11 Aug 2023 19:34:18 +0200 Subject: [PATCH 027/109] Update modbus_interface.py Bugfix Double Registercount --- modbus_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modbus_interface.py b/modbus_interface.py index 40caf5e..284ebc6 100644 --- a/modbus_interface.py +++ b/modbus_interface.py @@ -296,9 +296,9 @@ def type_length(type): # Note: Each address provides 2 bytes of data. if type in ['int16', 'uint16']: return 1 - elif type in ['int32', 'uint32', 'float']: + elif type in ['int32', 'uint32', 'float', 'float_be', '>float', 'float_le', 'double', 'double_le', ' Date: Fri, 11 Aug 2023 19:42:34 +0200 Subject: [PATCH 028/109] Update modbus4mqtt.py Added --loop Option to disable Infinite loop it can be used to start oneshot with cron to ensure correct timestamp of data --- modbus4mqtt.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index 3d52b0c..fcfb4ae 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -377,7 +377,9 @@ def _load_modbus_config(self, path): return result def loop_forever(self): - while True: + self.poll() + sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) + while loop == True: # TODO this properly. self.poll() sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) @@ -406,6 +408,9 @@ def loop_forever(self): help='Client certificate for authentication, if required by server.', show_default=True) @click.option('--key', default=None, help='Client private key for authentication, if required by server.', show_default=True) +@click.option('--loop', default=True, + help='use False if you want to disable loop.', show_default=True) + def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key): logging.basicConfig( format='%(asctime)s %(levelname)-8s %(message)s', From 180c9f8773894a507b384b20de635617e9ea99a0 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 19:43:11 +0200 Subject: [PATCH 029/109] Update Version to 0.8 --- modbus4mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index fcfb4ae..b7f8970 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -10,7 +10,7 @@ import paho.mqtt.client as mqtt from . import modbus_interface -version = "EW.0.7" +version = "EW.0.8" MAX_DECIMAL_POINTS = 3 DEFAULT_SCAN_RATE_S = 5 From 894805cd09e4057d41a83b5e5fa71024d992cc80 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 19:58:14 +0200 Subject: [PATCH 030/109] Update modbus_interface.py one extra comma removed --- modbus_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus_interface.py b/modbus_interface.py index 284ebc6..c5f3222 100644 --- a/modbus_interface.py +++ b/modbus_interface.py @@ -287,7 +287,7 @@ def _scan_value_range(self, device_unit, start, count): , 'float_be', '>float' , 'float_le', 'double', + , 'double_be', '>double' , 'double_le', ' Date: Fri, 11 Aug 2023 20:03:22 +0200 Subject: [PATCH 031/109] Update modbus4mqtt.py Bugfix --- modbus4mqtt.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index b7f8970..6753afc 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -376,10 +376,8 @@ def _load_modbus_config(self, path): self.registers = registers return result - def loop_forever(self): - self.poll() - sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) - while loop == True: + def loop_forever(self) + while True: # TODO this properly. self.poll() sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) @@ -420,8 +418,12 @@ def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, i = mqtt_interface(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key) i.connect() + if loop == 'True': i.loop_forever() - + else: + self.poll() + sleep(5) + if __name__ == '__main__': main() From f0e265c0666c39b2878b4eae641d73bb219e252d Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 22:15:43 +0200 Subject: [PATCH 032/109] Update modbus4mqtt.py Bugfix --- modbus4mqtt.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index 6753afc..f7c4280 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -381,7 +381,10 @@ def loop_forever(self) # TODO this properly. self.poll() sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) - + + def singlerun(self) + self.poll() + sleep(5) #grant time forbpublish data @click.command() @click.option('--hostname', default='localhost', @@ -406,10 +409,10 @@ def loop_forever(self) help='Client certificate for authentication, if required by server.', show_default=True) @click.option('--key', default=None, help='Client private key for authentication, if required by server.', show_default=True) -@click.option('--loop', default=True, - help='use False if you want to disable loop.', show_default=True) +@click.option('--loop', default='False', + help='use True if you want to use scanrate loop.', show_default=True) -def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key): +def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key, loop): logging.basicConfig( format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, @@ -421,8 +424,7 @@ def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, if loop == 'True': i.loop_forever() else: - self.poll() - sleep(5) + i.singlerun() if __name__ == '__main__': From 6c4e2c86c1797bc57182840d4688b8f032deb114 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 22:25:16 +0200 Subject: [PATCH 033/109] Update modbus4mqtt.py Bugfix --- modbus4mqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index f7c4280..1fa22f4 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -376,13 +376,13 @@ def _load_modbus_config(self, path): self.registers = registers return result - def loop_forever(self) + def loop_forever(self): while True: # TODO this properly. self.poll() sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) - def singlerun(self) + def singlerun(self): self.poll() sleep(5) #grant time forbpublish data From ff0ce5b4cbe08e1e3c15cb1d189152d2b67c3227 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 22:28:14 +0200 Subject: [PATCH 034/109] Update modbus4mqtt.py --- modbus4mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index 1fa22f4..28f0f18 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -377,7 +377,7 @@ def _load_modbus_config(self, path): return result def loop_forever(self): - while True: + while True: # TODO this properly. self.poll() sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) From 33d3336946c02ca11898f627f8395bf544a185c5 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 22:33:28 +0200 Subject: [PATCH 035/109] Update modbus4mqtt.py --- modbus4mqtt.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index 28f0f18..59fe731 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -420,12 +420,10 @@ def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, logging.info("Starting modbus4mqtt {}".format(version)) i = mqtt_interface(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key) - i.connect() + i.connect() if loop == 'True': i.loop_forever() - else: - i.singlerun() - - -if __name__ == '__main__': + else: + i.singlerun() + if __name__ == '__main__': main() From 8c0cff79fb65cf913040e5a63f4d39fcac383b45 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 22:56:19 +0200 Subject: [PATCH 036/109] Add files via upload fixed "tab/space" error --- modbus4mqtt.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index 59fe731..d21fde4 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -22,7 +22,7 @@ def set_json_message_value(message, json_key, value): if json_key not in target: target[json_key] = dict() target = target[json_key] - + old = target.get(json_keys[-1], None) if old is not None: if not isinstance(old, list): @@ -123,16 +123,16 @@ def _get_registers_with(self, required_key): def getRegisterError(self, registerKey): return self._errors.get(registerKey, False) - + def _setRegisterError(self, registerKey): if registerKey in self._errors: return False self._errors[registerKey] = True return True - + def _clearRegisterError(self, registerKey): self._errors.pop(registerKey, None) - + def poll(self): try: self._mb.poll() @@ -356,7 +356,7 @@ def _load_modbus_config(self, path): address_offset = device.get('address_offset', self.address_offset) duplicate_json_key = device.get('duplicate_json_key', 'warn') sort_json_keys = device.get('sort_json_keys', True) - + for register in device_registers: if unit is not None: register['unit'] = unit @@ -371,20 +371,20 @@ def _load_modbus_config(self, path): register['device'] = self.get_DeviceUnit(register, unit) mqtt_interface._validate_registers(device_registers, duplicate_json_key) registers += device_registers - + mqtt_interface._validate_registers(registers, 'ignore') self.registers = registers return result def loop_forever(self): - while True: + while True: # TODO this properly. self.poll() sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) - + def singlerun(self): self.poll() - sleep(5) #grant time forbpublish data + sleep(5) @click.command() @click.option('--hostname', default='localhost', @@ -409,8 +409,8 @@ def singlerun(self): help='Client certificate for authentication, if required by server.', show_default=True) @click.option('--key', default=None, help='Client private key for authentication, if required by server.', show_default=True) -@click.option('--loop', default='False', - help='use True if you want to use scanrate loop.', show_default=True) +@click.option('--loop', default='True', + help='use False if you want to disable loop.', show_default=True) def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key, loop): logging.basicConfig( @@ -420,10 +420,11 @@ def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, logging.info("Starting modbus4mqtt {}".format(version)) i = mqtt_interface(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key) - i.connect() - if loop == 'True': - i.loop_forever() + i.connect() + if loop == 'True': + i.loop_forever() else: - i.singlerun() - if __name__ == '__main__': + i.singlerun() + +if __name__ == '__main__': main() From f5b0ae5cb55a23851645885ebd06eafe361e541f Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 23:33:38 +0200 Subject: [PATCH 037/109] Update modbus4mqtt.py --- modbus4mqtt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index d21fde4..e6e4949 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -409,8 +409,8 @@ def singlerun(self): help='Client certificate for authentication, if required by server.', show_default=True) @click.option('--key', default=None, help='Client private key for authentication, if required by server.', show_default=True) -@click.option('--loop', default='True', - help='use False if you want to disable loop.', show_default=True) +@click.option('--loop', default='False', + help='use True if you want to disable oneshot and use update_rate in loop.', show_default=True) def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key, loop): logging.basicConfig( From b0d734b718a5b6b8ddb85cf9b393f1e0c3f18b89 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 23:54:08 +0200 Subject: [PATCH 038/109] Create dummy --- config/dummy | 1 + 1 file changed, 1 insertion(+) create mode 100644 config/dummy diff --git a/config/dummy b/config/dummy new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/config/dummy @@ -0,0 +1 @@ + From 9b2e4a4e18d4651abb7e4f9824281582b0ca8637 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 23:54:33 +0200 Subject: [PATCH 039/109] Create dummy --- pro/dummy | 1 + 1 file changed, 1 insertion(+) create mode 100644 pro/dummy diff --git a/pro/dummy b/pro/dummy new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pro/dummy @@ -0,0 +1 @@ + From 4d98742f92e88f70fea66b4f898b33d71f8fc485 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 23:55:36 +0200 Subject: [PATCH 040/109] Add files via upload --- pro/TCPRTU1.power.yaml | 73 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 pro/TCPRTU1.power.yaml diff --git a/pro/TCPRTU1.power.yaml b/pro/TCPRTU1.power.yaml new file mode 100644 index 0000000..0846ae4 --- /dev/null +++ b/pro/TCPRTU1.power.yaml @@ -0,0 +1,73 @@ +url: tcp://127.0.0.1:500 +#url: serial:///dev/ttysWK0 +update_rate: 30 +address_offset: 1 +scan_batching: 4 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: Leistungen + json_key: Leistung_1 + unit: 0x01 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_2 + unit: 0x02 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_3 + unit: 0x03 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_4 + unit: 0x04 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_5 + unit: 0x05 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_6 + unit: 0x06 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_7 + unit: 0x07 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_8 + unit: 0x08 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + \ No newline at end of file From a0b11eee600eca75e8793bfe9bf3d16dd3245391 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Fri, 11 Aug 2023 23:57:40 +0200 Subject: [PATCH 041/109] Add files via upload changed path removed "restart" --- modbus4mqtt@.service | 9 ++++----- modbus4mqttpro@.service | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 modbus4mqttpro@.service diff --git a/modbus4mqtt@.service b/modbus4mqtt@.service index a1f031d..4b30356 100644 --- a/modbus4mqtt@.service +++ b/modbus4mqtt@.service @@ -1,15 +1,14 @@ #/etc/systemd/system/modbus4mqtt@.service [Unit] -Description=modbus4mqtt service +Description=check easywind online Documentation=none Wants=network-online.target After=network-online.target [Service] -# %H means Hostname of the system a topic_prefix. use complete name for service start "modbus4mqtt@TCPRTU1.yaml" -ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --config /etc/modbus4mqtt/%I -Restart=always -RestartSec=30 +ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --config /etc/modbus4mqtt/config/%I +#Restart=always +#RestartSec=30 [Install] WantedBy=default.target diff --git a/modbus4mqttpro@.service b/modbus4mqttpro@.service new file mode 100644 index 0000000..49b9c61 --- /dev/null +++ b/modbus4mqttpro@.service @@ -0,0 +1,14 @@ +#/etc/systemd/system/modbus4mqtt@.service +[Unit] +Description=check easywind online +Documentation=none +Wants=network-online.target +After=network-online.target + +[Service] +ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --config /etc/modbus4mqtt/pro/%I +#Restart=always +#RestartSec=30 + +[Install] +WantedBy=default.target From 2e499646d0363ec9cdcf510774f59f1b8e5fa52a Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:09:10 +0200 Subject: [PATCH 042/109] Add files via upload --- config/TCPRTU1.yield.yaml | 136 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 config/TCPRTU1.yield.yaml diff --git a/config/TCPRTU1.yield.yaml b/config/TCPRTU1.yield.yaml new file mode 100644 index 0000000..623b56d --- /dev/null +++ b/config/TCPRTU1.yield.yaml @@ -0,0 +1,136 @@ +url: tcp://127.0.0.1:500 +#url: serial:///dev/ttysWK0 +update_rate: 30 +address_offset: 1 +scan_batching: 4 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: Netzanschluss + json_key: Bezug + unit: 0x01 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Netzanschluss + json_key: Einspeisung + unit: 0x01 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 2" + unit: 0x02 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 2" + unit: 0x02 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 3" + unit: 0x03 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 3" + unit: 0x03 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 4" + unit: 0x04 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 4" + unit: 0x04 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 5" + unit: 0x05 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 5" + unit: 0x05 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 6" + unit: 0x06 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 6" + unit: 0x06 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 7" + unit: 0x07 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 7" + unit: 0x07 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 8" + unit: 0x08 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 8" + unit: 0x08 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 \ No newline at end of file From 54ebc7bbbfbeafd09de91b970e9cfb264a551763 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:09:37 +0200 Subject: [PATCH 043/109] Delete dummy --- pro/dummy | 1 - 1 file changed, 1 deletion(-) delete mode 100644 pro/dummy diff --git a/pro/dummy b/pro/dummy deleted file mode 100644 index 8b13789..0000000 --- a/pro/dummy +++ /dev/null @@ -1 +0,0 @@ - From 874a552e4a42057c2932f34d865eed9d6a5842ba Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:25:00 +0200 Subject: [PATCH 044/109] Delete dummy --- config/dummy | 1 - 1 file changed, 1 deletion(-) delete mode 100644 config/dummy diff --git a/config/dummy b/config/dummy deleted file mode 100644 index 8b13789..0000000 --- a/config/dummy +++ /dev/null @@ -1 +0,0 @@ - From 3caba97b1bf3e06b4a88df48f309c5c56fb8a03b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:25:33 +0200 Subject: [PATCH 045/109] Add files via upload --- config/STPx.yield.yaml | 18 ++++++++++++++++++ config/TCPRTU1.yield.yaml | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 config/STPx.yield.yaml diff --git a/config/STPx.yield.yaml b/config/STPx.yield.yaml new file mode 100644 index 0000000..cc4f5d8 --- /dev/null +++ b/config/STPx.yield.yaml @@ -0,0 +1,18 @@ +url: tcp://172.31.12.202:502 +#url: serial:///dev/ttysWK0 +#unit: '0x13' wrong position ! +update_rate: 60 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Erzeugung" + json_key: 'PV' + unit: 0x03 + address: 529 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 0,001 + \ No newline at end of file diff --git a/config/TCPRTU1.yield.yaml b/config/TCPRTU1.yield.yaml index 623b56d..944f753 100644 --- a/config/TCPRTU1.yield.yaml +++ b/config/TCPRTU1.yield.yaml @@ -6,16 +6,16 @@ scan_batching: 4 word_order: highlow sort_json_keys: False registers: - - pub_topic: Netzanschluss - json_key: Bezug + - pub_topic: Verbrauch + json_key: "Zaehler 1" unit: 0x01 address: 73 type: float table: 'input' pub_only_on_change: true scale: 1 - - pub_topic: Netzanschluss - json_key: Einspeisung + - pub_topic: Erzeugung + json_key: "Zaehler 1" unit: 0x01 address: 75 type: float From 55516b9d8b8f80333823262aab4ea726e0adc285 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:27:39 +0200 Subject: [PATCH 046/109] Add files via upload --- pro/STPx.power.yaml | 17 +++++++++++++++++ pro/TCPRTU1.power.yaml | 16 ++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 pro/STPx.power.yaml diff --git a/pro/STPx.power.yaml b/pro/STPx.power.yaml new file mode 100644 index 0000000..29c3780 --- /dev/null +++ b/pro/STPx.power.yaml @@ -0,0 +1,17 @@ +url: tcp://172.31.12.202:502 +#url: serial:///dev/ttysWK0 +update_rate: 60 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Leistungen" + json_key: 'SMA_STP' + unit: 0x03 + address: 30775 + type: int32 + table: 'holding' + pub_only_on_change: true + scale: 1 + \ No newline at end of file diff --git a/pro/TCPRTU1.power.yaml b/pro/TCPRTU1.power.yaml index 0846ae4..2a90c74 100644 --- a/pro/TCPRTU1.power.yaml +++ b/pro/TCPRTU1.power.yaml @@ -7,7 +7,7 @@ word_order: highlow sort_json_keys: False registers: - pub_topic: Leistungen - json_key: Leistung_1 + json_key: "Leistung 1" unit: 0x01 address: 53 type: float @@ -15,7 +15,7 @@ registers: pub_only_on_change: false scale: 1 - pub_topic: Leistungen - json_key: Leistung_2 + json_key: "Leistung 2" unit: 0x02 address: 53 type: float @@ -23,7 +23,7 @@ registers: pub_only_on_change: false scale: 1 - pub_topic: Leistungen - json_key: Leistung_3 + json_key: "Leistung 3" unit: 0x03 address: 53 type: float @@ -31,7 +31,7 @@ registers: pub_only_on_change: false scale: 1 - pub_topic: Leistungen - json_key: Leistung_4 + json_key: "Leistung 4" unit: 0x04 address: 53 type: float @@ -39,7 +39,7 @@ registers: pub_only_on_change: false scale: 1 - pub_topic: Leistungen - json_key: Leistung_5 + json_key: "Leistung 5" unit: 0x05 address: 53 type: float @@ -47,7 +47,7 @@ registers: pub_only_on_change: false scale: 1 - pub_topic: Leistungen - json_key: Leistung_6 + json_key: "Leistung 6" unit: 0x06 address: 53 type: float @@ -55,7 +55,7 @@ registers: pub_only_on_change: false scale: 1 - pub_topic: Leistungen - json_key: Leistung_7 + json_key: "Leistung 7" unit: 0x07 address: 53 type: float @@ -63,7 +63,7 @@ registers: pub_only_on_change: false scale: 1 - pub_topic: Leistungen - json_key: Leistung_8 + json_key: "Leistung 8" unit: 0x08 address: 53 type: float From f99bca284d2840e872c90015c04dea97cedd2b36 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:30:20 +0200 Subject: [PATCH 047/109] Update autostart --- autostart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autostart b/autostart index 6d36050..3d6c19f 100644 --- a/autostart +++ b/autostart @@ -6,7 +6,7 @@ # every config.yaml in directory will be launched as own instance of the service # adjust path to your destination sleep 10 -cd /etc/modbus4mqtt/ +cd /etc/modbus4mqtt/config/ for f in $( ls *.yaml); do systemctl start modbus4mqtt@$f.service echo $f From 69159ce82fd59c74729d47fea817b5f1faedd260 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:31:06 +0200 Subject: [PATCH 048/109] Create autorunpro --- autorunpro | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 autorunpro diff --git a/autorunpro b/autorunpro new file mode 100644 index 0000000..dc19223 --- /dev/null +++ b/autorunpro @@ -0,0 +1,13 @@ +#!/bin/bash +# add +# @reboot /etc/modbus4mqtt/autostart to your crontab +# u can alternativly use +# */2 * * * * /etc/modbus4mqtt/autostart +# every config.yaml in directory will be launched as own instance of the service +# adjust path to your destination +sleep 10 +cd /etc/modbus4mqtt/pro/ +for f in $( ls *.yaml); do + systemctl start modbus4mqttpro@$f.service + echo $f +done From 0246811f33c48f1ed8e95e2adef69eec3c80a9f7 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:32:29 +0200 Subject: [PATCH 049/109] Create autorunconfig --- autorunconfig | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 autorunconfig diff --git a/autorunconfig b/autorunconfig new file mode 100644 index 0000000..6ef6c20 --- /dev/null +++ b/autorunconfig @@ -0,0 +1,13 @@ +#!/bin/bash +# add +# @reboot /etc/modbus4mqtt/autorunconfig to your crontab +# u can alternativly use +# */2 * * * * /etc/modbus4mqtt/autostart +# every config.yaml in directory will be launched as own instance of the service +# adjust path to your destination +sleep 10 +cd /etc/modbus4mqtt/config/ +for f in $( ls *.yaml); do + systemctl start modbus4mqtt@$f.service + echo $f +done From 5d12b6ff855420b9a1adf8c3b26b18b15168b161 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:34:00 +0200 Subject: [PATCH 050/109] Update autostart --- autostart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/autostart b/autostart index 3d6c19f..cca330b 100644 --- a/autostart +++ b/autostart @@ -1,13 +1,12 @@ #!/bin/bash # add # @reboot /etc/modbus4mqtt/autostart to your crontab -# u can alternativly use -# */2 * * * * /etc/modbus4mqtt/autostart +# # every config.yaml in directory will be launched as own instance of the service # adjust path to your destination sleep 10 cd /etc/modbus4mqtt/config/ for f in $( ls *.yaml); do - systemctl start modbus4mqtt@$f.service + systemctl start modbus4mqttloop@$f.service echo $f done From a9e11afdebdabbeaa65ab5bf5d96611a881f634c Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:35:54 +0200 Subject: [PATCH 051/109] Update autorunconfig --- autorunconfig | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/autorunconfig b/autorunconfig index 6ef6c20..13a4135 100644 --- a/autorunconfig +++ b/autorunconfig @@ -1,11 +1,9 @@ #!/bin/bash # add -# @reboot /etc/modbus4mqtt/autorunconfig to your crontab -# u can alternativly use -# */2 * * * * /etc/modbus4mqtt/autostart -# every config.yaml in directory will be launched as own instance of the service +# # */5 * * * * /etc/modbus4mqtt/autorunconfig to your crontab (every 5 min) +# every config.yaml in directory will be launched as own instance of the service +# for singlerun transmission # adjust path to your destination -sleep 10 cd /etc/modbus4mqtt/config/ for f in $( ls *.yaml); do systemctl start modbus4mqtt@$f.service From fa0ba66a6e447208fbaa9b14a61a6b41413363b0 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:36:58 +0200 Subject: [PATCH 052/109] Update autorunpro --- autorunpro | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/autorunpro b/autorunpro index dc19223..f0192ac 100644 --- a/autorunpro +++ b/autorunpro @@ -1,11 +1,9 @@ #!/bin/bash # add -# @reboot /etc/modbus4mqtt/autostart to your crontab -# u can alternativly use -# */2 * * * * /etc/modbus4mqtt/autostart -# every config.yaml in directory will be launched as own instance of the service +# # */10 * * * * * /etc/modbus4mqtt/autorunpro to your crontab (every 10 sec) +# every config.yaml in directory will be launched as own instance of the service +# for singlerun transmission # adjust path to your destination -sleep 10 cd /etc/modbus4mqtt/pro/ for f in $( ls *.yaml); do systemctl start modbus4mqttpro@$f.service From 9f796e60ca72f020690ae562591ebef448b1343d Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:38:35 +0200 Subject: [PATCH 053/109] Update modbus4mqtt@.service --- modbus4mqtt@.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt@.service b/modbus4mqtt@.service index 4b30356..6f6f84b 100644 --- a/modbus4mqtt@.service +++ b/modbus4mqtt@.service @@ -1,6 +1,6 @@ #/etc/systemd/system/modbus4mqtt@.service [Unit] -Description=check easywind online +Description=Poll once and transmit to mqtt Documentation=none Wants=network-online.target After=network-online.target From 1a14a741ac8128e445d52ee12b3f7e4900d1ed9a Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:39:03 +0200 Subject: [PATCH 054/109] Update modbus4mqttpro@.service --- modbus4mqttpro@.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqttpro@.service b/modbus4mqttpro@.service index 49b9c61..945b186 100644 --- a/modbus4mqttpro@.service +++ b/modbus4mqttpro@.service @@ -1,6 +1,6 @@ #/etc/systemd/system/modbus4mqtt@.service [Unit] -Description=check easywind online +Description=poll once and transmit to mqtt Documentation=none Wants=network-online.target After=network-online.target From 4a0be50a041dec381a28d399aca5ff392907194a Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:40:28 +0200 Subject: [PATCH 055/109] Create modbus4mqttloop@.service --- modbus4mqttloop@.service | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 modbus4mqttloop@.service diff --git a/modbus4mqttloop@.service b/modbus4mqttloop@.service new file mode 100644 index 0000000..84e56f8 --- /dev/null +++ b/modbus4mqttloop@.service @@ -0,0 +1,14 @@ +#/etc/systemd/system/modbus4mqtt@.service +[Unit] +Description=Loop the poll and transmit to mqtt +Documentation=none +Wants=network-online.target +After=network-online.target + +[Service] +ExecStart=/usr/local/bin/modbus4mqtt --lopp True --mqtt_topic_prefix %H --config /etc/modbus4mqtt/config/%I +Restart=always +RestartSec=30 + +[Install] +WantedBy=default.target From 2782f3b9249c9f79ed9a715a596c6e95065df05f Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 01:11:00 +0200 Subject: [PATCH 056/109] Update README.md --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 25235bb..634d471 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # Modbus4MQTT this is my rebuild of my fork from a fork from a fork from modbus4mqtt by tjhowse. +I have added Double (float64), multiple servicefiles, singlerun option (default) , +cleaned up wrong templates. updated readme. + + +thanks to tjhowse for the main project +thanks to JPSteindlberger for float support +thanks to a-s-z-home for float support and modbus_interface with units and serial support + https://github.com/tjhowse/modbus4mqtt @@ -17,9 +25,9 @@ git clone https://github.com/Pubaluba/modbus4mqtt_rebuild cd modbus4mqtt_rebuild #move the service file to systemd -mv modbus4mqtt@.service /etc/systemd/system +mv *.service /etc/systemd/system/ systemctl daemon-reload -#move the python file to replace the original +#move the python file to replace the original installed by pip mv *.py /usr/local/lib/python3.6/dist-packages/modbus4mqtt #if there's an error mv *.py /usr/local/lib/python3.10/dist-packages/modbus4mqtt @@ -31,22 +39,37 @@ modbus4mqtt --help test your config (example:) modbus4mqtt --mqtt_topic_prefix "***" --hostname "***" --config /etc/modbus4mqtt/TCPRTU1.yaml + +if your want to run in loop, use "--loop True" else a singlerun will be performed to preserve correct timestamps +unse ``` +## use cron for singlerun: + +you can use cron every 5 min for yiled data: +*/5 * * * * /etc/modbus4mqtt/autorunconfig + +you can use cron every 10 sec for power data (one start more !) +*/10 * * * * * /etc/modbus4mqtt/autorunpro + +both scripts use the servicefiles modbus4mqttconfig@ / modbusmqttpro@.service + -## to use a service: -copy a ./template/.yaml file to /etc/modbus4mqtt +## to use a service with "updaterate" setting: +please be awere of inconsistent timestamp. running multiple services @ different devices will loop different because a simple sllep command is used after polling. + +copy a ./template/.yaml file to /etc/modbus4mqtt/config/ ```bash -systemctl start modbusmqtt@"yourfile(include!.yaml)" +systemctl start modbusmqttloop@"yourfile(include!.yaml)" -systemctl status modbusmqtt@"yourfile(include!.yaml)" +systemctl status modbusmqttloop@"yourfile(include!.yaml)" -systemctl enable modbusmqtt@"yourfile(include!.yaml)" +systemctl enable modbusmqttloop@"yourfile(include!.yaml)" ``` the service uses the hostname as prefix u can change this by editing the service file in /etc/systemd/system/ the autostart file can be used instead: -every .yaml in /etc/modbus4mqtt/ will be started be this script. use cron or similar +every .yaml in /etc/modbus4mqtt/config will be started be this script. use cron or similar # below to be done !! : @@ -57,16 +80,16 @@ every .yaml in /etc/modbus4mqtt/ will be started be this script. use cron or sim ### Modbus device settings ```yaml -ip: 192.168.1.89 -port: 502 -update_rate: 30 +url: tcp://192.168.1.89:502 +#url: serial:///dev/ttyUSB1?comset=8E1 +update_rate: 30 # only used for "--loop True" address_offset: 0 scan_batching: 100 word_order: highlow ``` | Field name | Required | Default | Description | | ---------- | -------- | ------- | ----------- | -|url | Required | N/A | The IP address of the modbus device to be polled. Presently only modbus TCP/IP is supported. | +|url | Required | N/A | The address of the modbus device to be polled. modbus TCP/IP and serial is supported. I suggest using mbusd https://github.com/3cky/mbusd for serial devices if you want to pull multiple data with different rates. Serial does not support multiple clients when opend | | port | Optional | 502 | The port on the modbus device to connect to. | | update_rate | Optional | 5 | The number of seconds between polls of the modbus device. | | address_offset | Optional | 0 | This offset is applied to every register address to accommodate different Modbus addressing systems. In many Modbus devices the first register is enumerated as 1, other times 0. See section 4.4 of the Modbus spec. | @@ -76,6 +99,7 @@ word_order: highlow ### Register settings ```yaml + registers: - pub_topic: "forced_charge/mode" set_topic: "forced_charge/mode/set" @@ -86,6 +110,7 @@ registers: value_map: enabled: 170 disabled: 85 + unit: 8 ## defaults to 1 - pub_topic: "forced_charge/period_1/start_hours" set_topic: "forced_charge/period_1/start_hours/set" pub_only_on_change: true @@ -109,6 +134,22 @@ registers: - pub_topic: "minutes_online" address: 13016 type: uint32 + + - pub_topic: "Yield" + json_key: "PV inverter" # use "" for spaces + address: 53 + unit: 8 + type: float + + - pub_topic: "Yield" + json_key: Windturbine + address: 210 + unit: 255 + type: double + + - + + ``` This section of the YAML lists all the modbus registers that you consider interesting. @@ -123,6 +164,6 @@ This section of the YAML lists all the modbus registers that you consider intere | table | Optional | holding | The Modbus table to read from the device. Must be 'holding' or 'input'. | | value_map | Optional | N/A | A series of human-readable and raw values for the setting. This will be used to translate between human-readable values via MQTT to raw values via Modbus. If a value_map is set for a register the interface will reject raw values sent via MQTT. If value_map is not set the interface will try to set the Modbus register to that value. Note that the scale is applied after the value is read from Modbus and before it is written to Modbus. | | scale | Optional | 1 | After reading a value from the Modbus register it will be multiplied by this scalar before being published to MQTT. Values published on this register's `set_topic` will be divided by this scalar before being written to Modbus. | -| mask | Optional | 0xFFFF | This is a 16-bit number that can be used to select a part of a Modbus register to be referenced by this register. For example a mask of `0xFF00` will map to the most significant byte of the 16-bit Modbus register at `address`. A mask of `0x0001` will reference only the least significant bit of this register. | -| json_key | Optional | N/A | The value of this register will be published to its pub_topic in JSON format. E.G. `{ key: value }` Registers with a json_key specified can share a pub_topic. All registers with shared pub_topics must have a json_key specified. In this way, multiple registers can be published to the same topic in a single JSON message. If any of the registers that share a pub_topic have the retain field set that will affect the published JSON message. Conflicting retain settings are invalid. The keys will be alphabetically sorted. | -| type | Optional | uint16 | The type of the value stored at the modbus address provided. Only uint16 (unsigned 16-bit integer), int16 (signed 16-bit integer), uint32, int32, uint64 and int64 are currently supported. | +| mask | Optional | 0xFFFF | This is a 16-bit number that can be used to select a part of a Modbus register to be referenced by this register. For example a mask of `0xFF00` will map to the most significant byte of the 16-bit Modbus register at `address`. A mask of `0x0001` will reference only the least significant bit of this register. only to be used @ unsigned integer 16/32/64 bit | +| json_key | Optional | N/A | The value of this register will be published to its pub_topic in JSON format. E.G. `{ key: value }` Registers with a json_key specified can share a pub_topic. All registers with shared pub_topics must have a json_key specified. In this way, multiple registers can be published to the same topic in a single JSON message. If any of the registers that share a pub_topic have the retain field set that will affect the published JSON message. Conflicting retain settings are invalid. The keys will be alphabetically sorted. Keys with spaces must use ".. .." | +| type | Optional | uint16 | The type of the value stored at the modbus address provided. uint16 (unsigned 16-bit integer), int16 (signed 16-bit integer), uint32, int32, uint64 and int64 , float , float_be (big Endian), float_le (little Endian) double, double_be, double_le are supported. | From 9c873dabee7ef5e4bf0598cdd1e3cb0ee7d2ff68 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 01:11:23 +0200 Subject: [PATCH 057/109] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 634d471..92531cf 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,6 @@ u can change this by editing the service file in /etc/systemd/system/ the autostart file can be used instead: every .yaml in /etc/modbus4mqtt/config will be started be this script. use cron or similar -# below to be done !! : - - # Yaml Configuration From f0e4fd4c7075b9c750ecb8537e1a922ce3256b09 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 01:12:22 +0200 Subject: [PATCH 058/109] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e09586c..5567a87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ ruamel.yaml>=0.16.12 click paho-mqtt pymodbus>=2.3.0,<3.0.0 -SungrowModbusTcpClient>=0.1.6 \ No newline at end of file +SungrowModbusTcpClient>=0.1.6 +ccorp-yaml-include-relative-path From c5be716f5cc51d33ac7726608fdde5473df1ed49 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 01:38:51 +0200 Subject: [PATCH 059/109] Update README.md completed installation instruction --- README.md | 53 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 92531cf..84d5a31 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ I have added Double (float64), multiple servicefiles, singlerun option (default) cleaned up wrong templates. updated readme. -thanks to tjhowse for the main project -thanks to JPSteindlberger for float support -thanks to a-s-z-home for float support and modbus_interface with units and serial support - +##### thanks to tjhowse for the main project +##### thanks to JPSteindlberger for float support +##### thanks to a-s-z-home for float support and modbus_interface with units and serial support +# https://github.com/tjhowse/modbus4mqtt https://pypi.org/project/modbus4mqtt/ @@ -17,21 +17,35 @@ https://pypi.org/project/modbus4mqtt/ ## Installation ```bash -apt install python3-pip -pip3 install modbus4mqtt -pip3 install ccorp-yaml-include-relative-path -mkdir /etc/modbus4mqtt git clone https://github.com/Pubaluba/modbus4mqtt_rebuild - cd modbus4mqtt_rebuild + +apt install python3-pip +pip3 install -r requirements.txt + + #move the service file to systemd mv *.service /etc/systemd/system/ systemctl daemon-reload -#move the python file to replace the original installed by pip + +#install the python file to dist +python3 --version + +if 3.6.* +mkdir /usr/local/lib/python3.6/dist-packages/modbus4mqtt mv *.py /usr/local/lib/python3.6/dist-packages/modbus4mqtt -#if there's an error + +if 3.10.* +mkdir /usr/local/lib/python3.10/dist-packages/modbus4mqtt mv *.py /usr/local/lib/python3.10/dist-packages/modbus4mqtt + + +#move the binary +mv modbus4mqtt /usr/local/bin/ +chmod 777 /usr/local/bin/modbus4mqtt + #move the remaining to configdir +mkdir /etc/modbus4mqtt mv * /etc/modbus4mqtt cd /etc/modbus4mqtt/ @@ -44,12 +58,14 @@ if your want to run in loop, use "--loop True" else a singlerun will be performe unse ``` ## use cron for singlerun: - -you can use cron every 5 min for yiled data: -*/5 * * * * /etc/modbus4mqtt/autorunconfig +```bash +crontab -e +``` +you can use cron every 5 min for yield data: +##### */5 * * * * /etc/modbus4mqtt/autorunconfig you can use cron every 10 sec for power data (one start more !) -*/10 * * * * * /etc/modbus4mqtt/autorunpro +##### */10 * * * * * /etc/modbus4mqtt/autorunpro both scripts use the servicefiles modbus4mqttconfig@ / modbusmqttpro@.service @@ -72,6 +88,12 @@ the autostart file can be used instead: every .yaml in /etc/modbus4mqtt/config will be started be this script. use cron or similar +## use the commandline +```bash +modbus4mqtt --help +``` +make you own command, script, servicefile, etc + # Yaml Configuration ### Modbus device settings @@ -87,7 +109,6 @@ word_order: highlow | Field name | Required | Default | Description | | ---------- | -------- | ------- | ----------- | |url | Required | N/A | The address of the modbus device to be polled. modbus TCP/IP and serial is supported. I suggest using mbusd https://github.com/3cky/mbusd for serial devices if you want to pull multiple data with different rates. Serial does not support multiple clients when opend | -| port | Optional | 502 | The port on the modbus device to connect to. | | update_rate | Optional | 5 | The number of seconds between polls of the modbus device. | | address_offset | Optional | 0 | This offset is applied to every register address to accommodate different Modbus addressing systems. In many Modbus devices the first register is enumerated as 1, other times 0. See section 4.4 of the Modbus spec. | | variant | Optional | N/A | Allows variants of the ModbusTcpClient library to be used. Setting this to 'sungrow' enables support of SungrowModbusTcpClient. This library transparently decrypts the modbus comms with sungrow SH inverters running newer firmware versions. | From 558b00aecd10bf6c0952e27469486b72224d81e0 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 07:43:43 +0200 Subject: [PATCH 060/109] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84d5a31..a947131 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ crontab -e you can use cron every 5 min for yield data: ##### */5 * * * * /etc/modbus4mqtt/autorunconfig -you can use cron every 10 sec for power data (one start more !) +you can use cron every 10 sec for power data (one start more/ not every Version) ##### */10 * * * * * /etc/modbus4mqtt/autorunpro both scripts use the servicefiles modbus4mqttconfig@ / modbusmqttpro@.service From d4a2ad0c98fe4015b0f2b61ac9e0486607c5cba9 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 07:44:50 +0200 Subject: [PATCH 061/109] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a947131..f6523e3 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,11 @@ please be awere of inconsistent timestamp. running multiple services @ different copy a ./template/.yaml file to /etc/modbus4mqtt/config/ ```bash -systemctl start modbusmqttloop@"yourfile(include!.yaml)" +systemctl start modbus4mqttloop@"yourfile(include!.yaml)" -systemctl status modbusmqttloop@"yourfile(include!.yaml)" +systemctl status modbus4mqttloop@"yourfile(include!.yaml)" -systemctl enable modbusmqttloop@"yourfile(include!.yaml)" +systemctl enable modbus4mqttloop@"yourfile(include!.yaml)" ``` the service uses the hostname as prefix From 0821e9a0306e02993f72b2bebaf228d976264b8b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Sat, 12 Aug 2023 07:50:06 +0200 Subject: [PATCH 062/109] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index f6523e3..7d8de7d 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,17 @@ you can use cron every 5 min for yield data: you can use cron every 10 sec for power data (one start more/ not every Version) ##### */10 * * * * * /etc/modbus4mqtt/autorunpro +alternativ for 10 sec interval: +```bash +* * * * * /etc/modbus4mqtt/autorunpro +* * * * * (sleep 10 ; /etc/modbus4mqtt/autorunpro) +* * * * * (sleep 20 ; /etc/modbus4mqtt/autorunpro) +* * * * * (sleep 30 ; /etc/modbus4mqtt/autorunpro) +* * * * * (sleep 40 ; /etc/modbus4mqtt/autorunpro) +* * * * * (sleep 50 ; /etc/modbus4mqtt/autorunpro) +``` + + both scripts use the servicefiles modbus4mqttconfig@ / modbusmqttpro@.service From fc2eaf2d737009fff08d55a627327266b1eb3a93 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:24:00 +0200 Subject: [PATCH 063/109] Delete config directory --- config/STPx.yield.yaml | 18 ----- config/TCPRTU1.yield.yaml | 136 -------------------------------------- 2 files changed, 154 deletions(-) delete mode 100644 config/STPx.yield.yaml delete mode 100644 config/TCPRTU1.yield.yaml diff --git a/config/STPx.yield.yaml b/config/STPx.yield.yaml deleted file mode 100644 index cc4f5d8..0000000 --- a/config/STPx.yield.yaml +++ /dev/null @@ -1,18 +0,0 @@ -url: tcp://172.31.12.202:502 -#url: serial:///dev/ttysWK0 -#unit: '0x13' wrong position ! -update_rate: 60 -address_offset: 0 -scan_batching: 2 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: "Erzeugung" - json_key: 'PV' - unit: 0x03 - address: 529 - type: uint32 - table: 'holding' - pub_only_on_change: false - scale: 0,001 - \ No newline at end of file diff --git a/config/TCPRTU1.yield.yaml b/config/TCPRTU1.yield.yaml deleted file mode 100644 index 944f753..0000000 --- a/config/TCPRTU1.yield.yaml +++ /dev/null @@ -1,136 +0,0 @@ -url: tcp://127.0.0.1:500 -#url: serial:///dev/ttysWK0 -update_rate: 30 -address_offset: 1 -scan_batching: 4 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: Verbrauch - json_key: "Zaehler 1" - unit: 0x01 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 1" - unit: 0x01 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 2" - unit: 0x02 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 2" - unit: 0x02 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 3" - unit: 0x03 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 3" - unit: 0x03 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 4" - unit: 0x04 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 4" - unit: 0x04 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 5" - unit: 0x05 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 5" - unit: 0x05 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 6" - unit: 0x06 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 6" - unit: 0x06 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 7" - unit: 0x07 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 7" - unit: 0x07 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 8" - unit: 0x08 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 8" - unit: 0x08 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 \ No newline at end of file From 9f93153540c3954535e98e8f79aa2d125201df95 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:24:35 +0200 Subject: [PATCH 064/109] Delete pro directory --- pro/STPx.power.yaml | 17 ---------- pro/TCPRTU1.power.yaml | 73 ------------------------------------------ 2 files changed, 90 deletions(-) delete mode 100644 pro/STPx.power.yaml delete mode 100644 pro/TCPRTU1.power.yaml diff --git a/pro/STPx.power.yaml b/pro/STPx.power.yaml deleted file mode 100644 index 29c3780..0000000 --- a/pro/STPx.power.yaml +++ /dev/null @@ -1,17 +0,0 @@ -url: tcp://172.31.12.202:502 -#url: serial:///dev/ttysWK0 -update_rate: 60 -address_offset: 0 -scan_batching: 2 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: "Leistungen" - json_key: 'SMA_STP' - unit: 0x03 - address: 30775 - type: int32 - table: 'holding' - pub_only_on_change: true - scale: 1 - \ No newline at end of file diff --git a/pro/TCPRTU1.power.yaml b/pro/TCPRTU1.power.yaml deleted file mode 100644 index 2a90c74..0000000 --- a/pro/TCPRTU1.power.yaml +++ /dev/null @@ -1,73 +0,0 @@ -url: tcp://127.0.0.1:500 -#url: serial:///dev/ttysWK0 -update_rate: 30 -address_offset: 1 -scan_batching: 4 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: Leistungen - json_key: "Leistung 1" - unit: 0x01 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: "Leistung 2" - unit: 0x02 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: "Leistung 3" - unit: 0x03 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: "Leistung 4" - unit: 0x04 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: "Leistung 5" - unit: 0x05 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: "Leistung 6" - unit: 0x06 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: "Leistung 7" - unit: 0x07 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: "Leistung 8" - unit: 0x08 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - \ No newline at end of file From ffd9b0bec28a363d4aed39b38da0eb36b2e1eba9 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:25:02 +0200 Subject: [PATCH 065/109] Delete autorunconfig --- autorunconfig | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 autorunconfig diff --git a/autorunconfig b/autorunconfig deleted file mode 100644 index 13a4135..0000000 --- a/autorunconfig +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# add -# # */5 * * * * /etc/modbus4mqtt/autorunconfig to your crontab (every 5 min) -# every config.yaml in directory will be launched as own instance of the service -# for singlerun transmission -# adjust path to your destination -cd /etc/modbus4mqtt/config/ -for f in $( ls *.yaml); do - systemctl start modbus4mqtt@$f.service - echo $f -done From 5397e479e3e09c60c9ea5fa65354f8655147eb2b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:25:13 +0200 Subject: [PATCH 066/109] Delete autorunpro --- autorunpro | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 autorunpro diff --git a/autorunpro b/autorunpro deleted file mode 100644 index f0192ac..0000000 --- a/autorunpro +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# add -# # */10 * * * * * /etc/modbus4mqtt/autorunpro to your crontab (every 10 sec) -# every config.yaml in directory will be launched as own instance of the service -# for singlerun transmission -# adjust path to your destination -cd /etc/modbus4mqtt/pro/ -for f in $( ls *.yaml); do - systemctl start modbus4mqttpro@$f.service - echo $f -done From 280009d7d32cf81596c15e7da6f3a35e3249646b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:25:29 +0200 Subject: [PATCH 067/109] Delete modbus4mqttpro@.service --- modbus4mqttpro@.service | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 modbus4mqttpro@.service diff --git a/modbus4mqttpro@.service b/modbus4mqttpro@.service deleted file mode 100644 index 945b186..0000000 --- a/modbus4mqttpro@.service +++ /dev/null @@ -1,14 +0,0 @@ -#/etc/systemd/system/modbus4mqtt@.service -[Unit] -Description=poll once and transmit to mqtt -Documentation=none -Wants=network-online.target -After=network-online.target - -[Service] -ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --config /etc/modbus4mqtt/pro/%I -#Restart=always -#RestartSec=30 - -[Install] -WantedBy=default.target From 67039fe530277e309d36255c41991ac56f3a6403 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:25:45 +0200 Subject: [PATCH 068/109] Delete autostart --- autostart | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 autostart diff --git a/autostart b/autostart deleted file mode 100644 index cca330b..0000000 --- a/autostart +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# add -# @reboot /etc/modbus4mqtt/autostart to your crontab -# -# every config.yaml in directory will be launched as own instance of the service -# adjust path to your destination -sleep 10 -cd /etc/modbus4mqtt/config/ -for f in $( ls *.yaml); do - systemctl start modbus4mqttloop@$f.service - echo $f -done From 65f004ee27f5a6ac26a9734e6231579bba996c66 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:25:58 +0200 Subject: [PATCH 069/109] Delete modbus4mqtt@.service --- modbus4mqtt@.service | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 modbus4mqtt@.service diff --git a/modbus4mqtt@.service b/modbus4mqtt@.service deleted file mode 100644 index 6f6f84b..0000000 --- a/modbus4mqtt@.service +++ /dev/null @@ -1,14 +0,0 @@ -#/etc/systemd/system/modbus4mqtt@.service -[Unit] -Description=Poll once and transmit to mqtt -Documentation=none -Wants=network-online.target -After=network-online.target - -[Service] -ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --config /etc/modbus4mqtt/config/%I -#Restart=always -#RestartSec=30 - -[Install] -WantedBy=default.target From a8c15946fd37290db5c5bfb63b5394e8304ff2e0 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 15 Aug 2023 23:59:45 +0200 Subject: [PATCH 070/109] Update README.md --- README.md | 83 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 7d8de7d..9698385 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Modbus4MQTT this is my rebuild of my fork from a fork from a fork from modbus4mqtt by tjhowse. -I have added Double (float64), multiple servicefiles, singlerun option (default) , +I have added Double (float64), multiple servicefiles, singlerun option (--loop False), cleaned up wrong templates. updated readme. @@ -17,19 +17,25 @@ https://pypi.org/project/modbus4mqtt/ ## Installation ```bash +# if not running as root + sudo -s + +#clone the repo and move to destination git clone https://github.com/Pubaluba/modbus4mqtt_rebuild -cd modbus4mqtt_rebuild +mv modbus4mqtt_rebuild /etc/modbus4mqtt +cd /etc/modbus4mqtt +#install phyton and requirements apt install python3-pip pip3 install -r requirements.txt - -#move the service file to systemd +#move the service file to systemd and reload systemd mv *.service /etc/systemd/system/ systemctl daemon-reload -#install the python file to dist +#get your phyton version python3 --version +#install the python file to dist if 3.6.* mkdir /usr/local/lib/python3.6/dist-packages/modbus4mqtt @@ -39,52 +45,47 @@ if 3.10.* mkdir /usr/local/lib/python3.10/dist-packages/modbus4mqtt mv *.py /usr/local/lib/python3.10/dist-packages/modbus4mqtt +#make executeable +chmod 777 /etc/modbus4mqtt/* #move the binary mv modbus4mqtt /usr/local/bin/ -chmod 777 /usr/local/bin/modbus4mqtt -#move the remaining to configdir -mkdir /etc/modbus4mqtt -mv * /etc/modbus4mqtt - -cd /etc/modbus4mqtt/ +#first run modbus4mqtt --help -test your config (example:) +#test your config (example:) modbus4mqtt --mqtt_topic_prefix "***" --hostname "***" --config /etc/modbus4mqtt/TCPRTU1.yaml -if your want to run in loop, use "--loop True" else a singlerun will be performed to preserve correct timestamps -unse +if your want to run a single run, use "--loop False" ``` + ## use cron for singlerun: +I have provided unix service files. the +#### autopower and the autostatus scripts +do not loop ! they need to be called every time. the servicefiles dont have restart settings. both use the corresponding folder + ```bash crontab -e ``` you can use cron every 5 min for yield data: -##### */5 * * * * /etc/modbus4mqtt/autorunconfig - -you can use cron every 10 sec for power data (one start more/ not every Version) -##### */10 * * * * * /etc/modbus4mqtt/autorunpro - -alternativ for 10 sec interval: ```bash -* * * * * /etc/modbus4mqtt/autorunpro -* * * * * (sleep 10 ; /etc/modbus4mqtt/autorunpro) -* * * * * (sleep 20 ; /etc/modbus4mqtt/autorunpro) -* * * * * (sleep 30 ; /etc/modbus4mqtt/autorunpro) -* * * * * (sleep 40 ; /etc/modbus4mqtt/autorunpro) -* * * * * (sleep 50 ; /etc/modbus4mqtt/autorunpro) +*/5 * * * * /etc/modbus4mqtt/autostatus +``` +you can use cron every 10 sec for power data +```bash +* * * * * /etc/modbus4mqtt/autopower +* * * * * (sleep 10 ; /etc/modbus4mqtt/autopower) +* * * * * (sleep 20 ; /etc/modbus4mqtt/autopower) +* * * * * (sleep 30 ; /etc/modbus4mqtt/autopower) +* * * * * (sleep 40 ; /etc/modbus4mqtt/autopower) +* * * * * (sleep 50 ; /etc/modbus4mqtt/autopower) ``` - - -both scripts use the servicefiles modbus4mqttconfig@ / modbusmqttpro@.service - ## to use a service with "updaterate" setting: -please be awere of inconsistent timestamp. running multiple services @ different devices will loop different because a simple sllep command is used after polling. +please be awere of inconsistent timestamp. running multiple services @ different devices will loop different because a simple sleep command is used after polling. if a read request of 8 rtu devices has a duration of 2.6 seconds, the update_rate rate will be 32.6 if you set 30. -copy a ./template/.yaml file to /etc/modbus4mqtt/config/ +copy a ./template/.yaml file to /etc/modbus4mqtt/loop/ ```bash systemctl start modbus4mqttloop@"yourfile(include!.yaml)" @@ -92,12 +93,16 @@ systemctl status modbus4mqttloop@"yourfile(include!.yaml)" systemctl enable modbus4mqttloop@"yourfile(include!.yaml)" ``` -the service uses the hostname as prefix +the service's use the hostname as prefix -u can change this by editing the service file in /etc/systemd/system/ -the autostart file can be used instead: -every .yaml in /etc/modbus4mqtt/config will be started be this script. use cron or similar +u can change this by editing the service files in /etc/systemd/system/ +the autoloop file can be used instead: +every .yaml in /etc/modbus4mqtt/loop will be started be this script. use cron or similar +```bash +crontab -e +@reboot /etc/modbus4mqtt/autoloop +``` ## use the commandline ```bash @@ -120,7 +125,7 @@ word_order: highlow | Field name | Required | Default | Description | | ---------- | -------- | ------- | ----------- | |url | Required | N/A | The address of the modbus device to be polled. modbus TCP/IP and serial is supported. I suggest using mbusd https://github.com/3cky/mbusd for serial devices if you want to pull multiple data with different rates. Serial does not support multiple clients when opend | -| update_rate | Optional | 5 | The number of seconds between polls of the modbus device. | +| update_rate | Optional | 5 | The number of seconds between polls of the modbus device. only used in loop modus. | | address_offset | Optional | 0 | This offset is applied to every register address to accommodate different Modbus addressing systems. In many Modbus devices the first register is enumerated as 1, other times 0. See section 4.4 of the Modbus spec. | | variant | Optional | N/A | Allows variants of the ModbusTcpClient library to be used. Setting this to 'sungrow' enables support of SungrowModbusTcpClient. This library transparently decrypts the modbus comms with sungrow SH inverters running newer firmware versions. | | scan_batching | Optional | 100 | Must be between 1 and 100 inclusive. Modbus read operations are more efficient in bigger batches of contiguous registers, but different devices have different limits on the size of the batched reads. This setting can also be helpful when building a modbus register map for an uncharted device. In some modbus devices a single invalid register in a read range will fail the entire read operation. By setting `scan_batching` to `1` each register will be scanned individually. This will be very inefficient and should not be used in production as it will saturate the link with many read operations. | @@ -188,8 +193,8 @@ This section of the YAML lists all the modbus registers that you consider intere | address | Required | N/A | The decimal address of the register to read from the device, starting at 0. Many modbus devices enumerate registers beginning at 1, so beware. | | pub_topic | Optional | N/A | This is the topic to which the value of this register will be published. | | set_topic | Optional | N/A | Values published to this topic will be written to the Modbus device. Cannot yet be combined with json_key. See https://github.com/tjhowse/modbus4mqtt/issues/23 for details. | -| retain | Optional | false | Controls whether the value of this register will be published with the retain bit set. | -| pub_only_on_change | Optional | true | Controls whether this register will only be published if its value changed from the previous poll. | +| retain | Optional | false | Controls whether the value of this register will be published with the retain bit set. if so, the message will be stored until deleted. if stored, other clients that subscribe same topic will always get this message/value | +| pub_only_on_change | Optional | true | Controls whether this register will only be published if its value changed from the previous poll. useless for singlerun. | | table | Optional | holding | The Modbus table to read from the device. Must be 'holding' or 'input'. | | value_map | Optional | N/A | A series of human-readable and raw values for the setting. This will be used to translate between human-readable values via MQTT to raw values via Modbus. If a value_map is set for a register the interface will reject raw values sent via MQTT. If value_map is not set the interface will try to set the Modbus register to that value. Note that the scale is applied after the value is read from Modbus and before it is written to Modbus. | | scale | Optional | 1 | After reading a value from the Modbus register it will be multiplied by this scalar before being published to MQTT. Values published on this register's `set_topic` will be divided by this scalar before being written to Modbus. | From eb856ba85e7ecf04d46f3a3230f5d2c80e4720a3 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:00:37 +0200 Subject: [PATCH 071/109] Add files via upload --- autoloop | 8 ++++++++ autopower | 8 ++++++++ autostatus | 8 ++++++++ modbus4mqtt | 11 +++++++++++ modbus4mqtt.py | 2 +- modbus4mqttloop@.service | 8 ++++---- modbus4mqttpower@.service | 12 ++++++++++++ modbus4mqttstatus@.service | 12 ++++++++++++ 8 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 autoloop create mode 100644 autopower create mode 100644 autostatus create mode 100644 modbus4mqtt create mode 100644 modbus4mqttpower@.service create mode 100644 modbus4mqttstatus@.service diff --git a/autoloop b/autoloop new file mode 100644 index 0000000..538ca22 --- /dev/null +++ b/autoloop @@ -0,0 +1,8 @@ +#!/bin/bash +#add this with "@reboot" to your crontab +#every config.yaml in directory will be launched as own instance +cd /etc/modbus4mqtt/loop/ +for f in $( ls *.yaml); do + systemctl start modbus4mqttloop@$f.service + echo $f #for debug in bash +done diff --git a/autopower b/autopower new file mode 100644 index 0000000..f8f4d34 --- /dev/null +++ b/autopower @@ -0,0 +1,8 @@ +#!/bin/bash +#add this with "@reboot" to your crontab +#every config.yaml in directory will be launched as own instance +cd /etc/modbus4mqtt/power/ +for f in $( ls *.yaml); do + systemctl start modbus4mqttpower@$f.service + echo $f #for debug in bash +done diff --git a/autostatus b/autostatus new file mode 100644 index 0000000..098f10b --- /dev/null +++ b/autostatus @@ -0,0 +1,8 @@ +#!/bin/bash +#add this with "@reboot" to your crontab +#every config.yaml in directory will be launched as own instance +cd /etc/modbus4mqtt/status/ +for f in $( ls *.yaml); do + systemctl start modbus4mqttstatus@$f.service + echo $f #for debug in bash +done diff --git a/modbus4mqtt b/modbus4mqtt new file mode 100644 index 0000000..ba5afd5 --- /dev/null +++ b/modbus4mqtt @@ -0,0 +1,11 @@ +#!/usr/bin/python3 + +# -*- coding: utf-8 -*- +import re +import sys + +from modbus4mqtt.modbus4mqtt import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index e6e4949..ff1950f 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -409,7 +409,7 @@ def singlerun(self): help='Client certificate for authentication, if required by server.', show_default=True) @click.option('--key', default=None, help='Client private key for authentication, if required by server.', show_default=True) -@click.option('--loop', default='False', +@click.option('--loop', default='True', help='use True if you want to disable oneshot and use update_rate in loop.', show_default=True) def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key, loop): diff --git a/modbus4mqttloop@.service b/modbus4mqttloop@.service index 84e56f8..77cff65 100644 --- a/modbus4mqttloop@.service +++ b/modbus4mqttloop@.service @@ -1,14 +1,14 @@ -#/etc/systemd/system/modbus4mqtt@.service +#/etc/systemd/system/modbus4mqttloop@.service [Unit] -Description=Loop the poll and transmit to mqtt +Description=Modbus4MQTT loop service Documentation=none Wants=network-online.target After=network-online.target [Service] -ExecStart=/usr/local/bin/modbus4mqtt --lopp True --mqtt_topic_prefix %H --config /etc/modbus4mqtt/config/%I +ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --config /etc/modbus4mqtt/loop/%I Restart=always -RestartSec=30 +RestartSec=5 [Install] WantedBy=default.target diff --git a/modbus4mqttpower@.service b/modbus4mqttpower@.service new file mode 100644 index 0000000..de626b5 --- /dev/null +++ b/modbus4mqttpower@.service @@ -0,0 +1,12 @@ +#/etc/systemd/system/modbus4mqttpower@.service +[Unit] +Description=Modbus4MQTT power service +Documentation=none +Wants=network-online.target +After=network-online.target + +[Service] +ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --loop False --config /etc/modbus4mqtt/power/%I + +[Install] +WantedBy=default.target diff --git a/modbus4mqttstatus@.service b/modbus4mqttstatus@.service new file mode 100644 index 0000000..5875407 --- /dev/null +++ b/modbus4mqttstatus@.service @@ -0,0 +1,12 @@ +#/etc/systemd/system/modbus4mqttstatus@.service +[Unit] +Description=Modbus4MQTT status service +Documentation=none +Wants=network-online.target +After=network-online.target + +[Service] +ExecStart=/usr/local/bin/modbus4mqtt --mqtt_topic_prefix %H --loop False --config /etc/modbus4mqtt/status/%I + +[Install] +WantedBy=default.target From 663fa74a8f0f86f51444eeb3de836c12d121dfac Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:01:59 +0200 Subject: [PATCH 072/109] Create d --- power/d | 1 + 1 file changed, 1 insertion(+) create mode 100644 power/d diff --git a/power/d b/power/d new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/power/d @@ -0,0 +1 @@ + From a70ddf92f5e9baca59eb7467e208260cddc26391 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:02:20 +0200 Subject: [PATCH 073/109] Create d --- loop/d | 1 + 1 file changed, 1 insertion(+) create mode 100644 loop/d diff --git a/loop/d b/loop/d new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/loop/d @@ -0,0 +1 @@ + From 94ac4357d558939ea8ce62d809b43c1fa813ed11 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:02:39 +0200 Subject: [PATCH 074/109] Create d --- status/d | 1 + 1 file changed, 1 insertion(+) create mode 100644 status/d diff --git a/status/d b/status/d new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/status/d @@ -0,0 +1 @@ + From 3ff3768d94bd697d070e85abfd755546b4f2883d Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:04:19 +0200 Subject: [PATCH 075/109] Add files via upload --- status/EastronRTU.yield.yaml | 136 +++++++++++++++++++++++++++++++++++ status/SMASIx.status.yaml | 32 +++++++++ status/SMASTPx.yield.yaml | 18 +++++ 3 files changed, 186 insertions(+) create mode 100644 status/EastronRTU.yield.yaml create mode 100644 status/SMASIx.status.yaml create mode 100644 status/SMASTPx.yield.yaml diff --git a/status/EastronRTU.yield.yaml b/status/EastronRTU.yield.yaml new file mode 100644 index 0000000..6a6c639 --- /dev/null +++ b/status/EastronRTU.yield.yaml @@ -0,0 +1,136 @@ +url: tcp://127.0.0.1:500 +#url: serial:///dev/ttysWK0 +update_rate: 30 +address_offset: 1 +scan_batching: 4 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: Verbrauch + json_key: "Zaehler 1" + unit: 1 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 1" + unit: 0x01 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 2" + unit: 0x02 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 2" + unit: 0x02 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 3" + unit: 0x03 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 3" + unit: 0x03 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 4" + unit: 0x04 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 4" + unit: 0x04 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 5" + unit: 0x05 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 5" + unit: 0x05 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 6" + unit: 0x06 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 6" + unit: 0x06 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 7" + unit: 0x07 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 7" + unit: 0x07 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 8" + unit: 0x08 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 8" + unit: 0x08 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 diff --git a/status/SMASIx.status.yaml b/status/SMASIx.status.yaml new file mode 100644 index 0000000..47af8cd --- /dev/null +++ b/status/SMASIx.status.yaml @@ -0,0 +1,32 @@ +url: tcp://172.31.12.203:502 +#url: serial:///dev/ttysWK0 +update_rate: 600 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Speicher" + json_key: 'Entladung' + unit: 0x03 + address: 30597 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 0.001 + - pub_topic: "Speicher" + json_key: 'Ladung' + unit: 0x03 + address: 30595 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 0.001 + - pub_topic: "Speicher" + json_key: 'Ladezustand' + unit: 0x03 + address: 30845 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 1 diff --git a/status/SMASTPx.yield.yaml b/status/SMASTPx.yield.yaml new file mode 100644 index 0000000..349a9d6 --- /dev/null +++ b/status/SMASTPx.yield.yaml @@ -0,0 +1,18 @@ +url: tcp://172.31.12.202:502 +#url: serial:///dev/ttysWK0 +#unit: '0x13' wrong position ! +update_rate: 60 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Erzeugung" + json_key: 'PV' + unit: 0x03 + address: 30529 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 0.001 + From 8977a153cf863949d7369851453fb67cfbd2a945 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:05:51 +0200 Subject: [PATCH 076/109] Add files via upload --- power/EastronRTU.power.yaml | 73 +++++++++++++++++++++++++++++++++++++ power/SMASIx.power.yaml | 17 +++++++++ power/SMASTPx.power.yaml | 17 +++++++++ 3 files changed, 107 insertions(+) create mode 100644 power/EastronRTU.power.yaml create mode 100644 power/SMASIx.power.yaml create mode 100644 power/SMASTPx.power.yaml diff --git a/power/EastronRTU.power.yaml b/power/EastronRTU.power.yaml new file mode 100644 index 0000000..0846ae4 --- /dev/null +++ b/power/EastronRTU.power.yaml @@ -0,0 +1,73 @@ +url: tcp://127.0.0.1:500 +#url: serial:///dev/ttysWK0 +update_rate: 30 +address_offset: 1 +scan_batching: 4 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: Leistungen + json_key: Leistung_1 + unit: 0x01 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_2 + unit: 0x02 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_3 + unit: 0x03 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_4 + unit: 0x04 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_5 + unit: 0x05 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_6 + unit: 0x06 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_7 + unit: 0x07 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_8 + unit: 0x08 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + \ No newline at end of file diff --git a/power/SMASIx.power.yaml b/power/SMASIx.power.yaml new file mode 100644 index 0000000..902e9d4 --- /dev/null +++ b/power/SMASIx.power.yaml @@ -0,0 +1,17 @@ +url: tcp://172.31.12.203:502 +#url: serial:///dev/ttysWK0 +update_rate: 60 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Leistungen" + json_key: 'Speicher' + unit: 0x03 + address: 30775 + type: int32 + table: 'holding' + pub_only_on_change: true + scale: 1 + diff --git a/power/SMASTPx.power.yaml b/power/SMASTPx.power.yaml new file mode 100644 index 0000000..29c3780 --- /dev/null +++ b/power/SMASTPx.power.yaml @@ -0,0 +1,17 @@ +url: tcp://172.31.12.202:502 +#url: serial:///dev/ttysWK0 +update_rate: 60 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Leistungen" + json_key: 'SMA_STP' + unit: 0x03 + address: 30775 + type: int32 + table: 'holding' + pub_only_on_change: true + scale: 1 + \ No newline at end of file From 259827272de6d0d4aee725bcbe6967e79ff77e59 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:06:19 +0200 Subject: [PATCH 077/109] Delete d --- power/d | 1 - 1 file changed, 1 deletion(-) delete mode 100644 power/d diff --git a/power/d b/power/d deleted file mode 100644 index 8b13789..0000000 --- a/power/d +++ /dev/null @@ -1 +0,0 @@ - From 68d589533d6095d0726390dbfd779c09c64b3963 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:06:36 +0200 Subject: [PATCH 078/109] Delete d --- loop/d | 1 - 1 file changed, 1 deletion(-) delete mode 100644 loop/d diff --git a/loop/d b/loop/d deleted file mode 100644 index 8b13789..0000000 --- a/loop/d +++ /dev/null @@ -1 +0,0 @@ - From ece35e74af348bedaef4c58eadbde2536d2524bd Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:07:11 +0200 Subject: [PATCH 079/109] Create dummy.yaml --- loop/dummy.yaml | 1 + 1 file changed, 1 insertion(+) create mode 100644 loop/dummy.yaml diff --git a/loop/dummy.yaml b/loop/dummy.yaml new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/loop/dummy.yaml @@ -0,0 +1 @@ + From 45e47095877d8b082d905154f22933cd0b19fba7 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:07:33 +0200 Subject: [PATCH 080/109] Delete d --- status/d | 1 - 1 file changed, 1 deletion(-) delete mode 100644 status/d diff --git a/status/d b/status/d deleted file mode 100644 index 8b13789..0000000 --- a/status/d +++ /dev/null @@ -1 +0,0 @@ - From 0a32ccba3641a4478b822a4ba86c3ca71f36c03b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:10:25 +0200 Subject: [PATCH 081/109] Add files via upload --- templates/EastronRTU.power.yaml | 73 +++++++++++++++++ templates/EastronRTU.yield.yaml | 136 ++++++++++++++++++++++++++++++++ templates/SMASIx.power.yaml | 17 ++++ templates/SMASIx.status.yaml | 32 ++++++++ templates/SMASTPx.power.yaml | 17 ++++ templates/SMASTPx.yield.yaml | 18 +++++ 6 files changed, 293 insertions(+) create mode 100644 templates/EastronRTU.power.yaml create mode 100644 templates/EastronRTU.yield.yaml create mode 100644 templates/SMASIx.power.yaml create mode 100644 templates/SMASIx.status.yaml create mode 100644 templates/SMASTPx.power.yaml create mode 100644 templates/SMASTPx.yield.yaml diff --git a/templates/EastronRTU.power.yaml b/templates/EastronRTU.power.yaml new file mode 100644 index 0000000..0846ae4 --- /dev/null +++ b/templates/EastronRTU.power.yaml @@ -0,0 +1,73 @@ +url: tcp://127.0.0.1:500 +#url: serial:///dev/ttysWK0 +update_rate: 30 +address_offset: 1 +scan_batching: 4 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: Leistungen + json_key: Leistung_1 + unit: 0x01 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_2 + unit: 0x02 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_3 + unit: 0x03 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_4 + unit: 0x04 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_5 + unit: 0x05 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_6 + unit: 0x06 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_7 + unit: 0x07 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + - pub_topic: Leistungen + json_key: Leistung_8 + unit: 0x08 + address: 53 + type: float + table: 'input' + pub_only_on_change: false + scale: 1 + \ No newline at end of file diff --git a/templates/EastronRTU.yield.yaml b/templates/EastronRTU.yield.yaml new file mode 100644 index 0000000..6a6c639 --- /dev/null +++ b/templates/EastronRTU.yield.yaml @@ -0,0 +1,136 @@ +url: tcp://127.0.0.1:500 +#url: serial:///dev/ttysWK0 +update_rate: 30 +address_offset: 1 +scan_batching: 4 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: Verbrauch + json_key: "Zaehler 1" + unit: 1 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 1" + unit: 0x01 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 2" + unit: 0x02 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 2" + unit: 0x02 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 3" + unit: 0x03 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 3" + unit: 0x03 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 4" + unit: 0x04 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 4" + unit: 0x04 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 5" + unit: 0x05 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 5" + unit: 0x05 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 6" + unit: 0x06 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 6" + unit: 0x06 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 7" + unit: 0x07 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 7" + unit: 0x07 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Verbrauch + json_key: "Zaehler 8" + unit: 0x08 + address: 73 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 + - pub_topic: Erzeugung + json_key: "Zaehler 8" + unit: 0x08 + address: 75 + type: float + table: 'input' + pub_only_on_change: true + scale: 1 diff --git a/templates/SMASIx.power.yaml b/templates/SMASIx.power.yaml new file mode 100644 index 0000000..902e9d4 --- /dev/null +++ b/templates/SMASIx.power.yaml @@ -0,0 +1,17 @@ +url: tcp://172.31.12.203:502 +#url: serial:///dev/ttysWK0 +update_rate: 60 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Leistungen" + json_key: 'Speicher' + unit: 0x03 + address: 30775 + type: int32 + table: 'holding' + pub_only_on_change: true + scale: 1 + diff --git a/templates/SMASIx.status.yaml b/templates/SMASIx.status.yaml new file mode 100644 index 0000000..47af8cd --- /dev/null +++ b/templates/SMASIx.status.yaml @@ -0,0 +1,32 @@ +url: tcp://172.31.12.203:502 +#url: serial:///dev/ttysWK0 +update_rate: 600 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Speicher" + json_key: 'Entladung' + unit: 0x03 + address: 30597 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 0.001 + - pub_topic: "Speicher" + json_key: 'Ladung' + unit: 0x03 + address: 30595 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 0.001 + - pub_topic: "Speicher" + json_key: 'Ladezustand' + unit: 0x03 + address: 30845 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 1 diff --git a/templates/SMASTPx.power.yaml b/templates/SMASTPx.power.yaml new file mode 100644 index 0000000..29c3780 --- /dev/null +++ b/templates/SMASTPx.power.yaml @@ -0,0 +1,17 @@ +url: tcp://172.31.12.202:502 +#url: serial:///dev/ttysWK0 +update_rate: 60 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Leistungen" + json_key: 'SMA_STP' + unit: 0x03 + address: 30775 + type: int32 + table: 'holding' + pub_only_on_change: true + scale: 1 + \ No newline at end of file diff --git a/templates/SMASTPx.yield.yaml b/templates/SMASTPx.yield.yaml new file mode 100644 index 0000000..349a9d6 --- /dev/null +++ b/templates/SMASTPx.yield.yaml @@ -0,0 +1,18 @@ +url: tcp://172.31.12.202:502 +#url: serial:///dev/ttysWK0 +#unit: '0x13' wrong position ! +update_rate: 60 +address_offset: 0 +scan_batching: 2 +word_order: highlow +sort_json_keys: False +registers: + - pub_topic: "Erzeugung" + json_key: 'PV' + unit: 0x03 + address: 30529 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 0.001 + From 8421c64b8c07b448863084e96f3e604d96182698 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:10:36 +0200 Subject: [PATCH 082/109] Delete loop directory --- loop/dummy.yaml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 loop/dummy.yaml diff --git a/loop/dummy.yaml b/loop/dummy.yaml deleted file mode 100644 index 8b13789..0000000 --- a/loop/dummy.yaml +++ /dev/null @@ -1 +0,0 @@ - From 9a633a2f58d28fc33eb905e56d908c93fb016854 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:10:51 +0200 Subject: [PATCH 083/109] Delete power directory --- power/EastronRTU.power.yaml | 73 ------------------------------------- power/SMASIx.power.yaml | 17 --------- power/SMASTPx.power.yaml | 17 --------- 3 files changed, 107 deletions(-) delete mode 100644 power/EastronRTU.power.yaml delete mode 100644 power/SMASIx.power.yaml delete mode 100644 power/SMASTPx.power.yaml diff --git a/power/EastronRTU.power.yaml b/power/EastronRTU.power.yaml deleted file mode 100644 index 0846ae4..0000000 --- a/power/EastronRTU.power.yaml +++ /dev/null @@ -1,73 +0,0 @@ -url: tcp://127.0.0.1:500 -#url: serial:///dev/ttysWK0 -update_rate: 30 -address_offset: 1 -scan_batching: 4 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: Leistungen - json_key: Leistung_1 - unit: 0x01 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: Leistung_2 - unit: 0x02 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: Leistung_3 - unit: 0x03 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: Leistung_4 - unit: 0x04 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: Leistung_5 - unit: 0x05 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: Leistung_6 - unit: 0x06 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: Leistung_7 - unit: 0x07 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - - pub_topic: Leistungen - json_key: Leistung_8 - unit: 0x08 - address: 53 - type: float - table: 'input' - pub_only_on_change: false - scale: 1 - \ No newline at end of file diff --git a/power/SMASIx.power.yaml b/power/SMASIx.power.yaml deleted file mode 100644 index 902e9d4..0000000 --- a/power/SMASIx.power.yaml +++ /dev/null @@ -1,17 +0,0 @@ -url: tcp://172.31.12.203:502 -#url: serial:///dev/ttysWK0 -update_rate: 60 -address_offset: 0 -scan_batching: 2 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: "Leistungen" - json_key: 'Speicher' - unit: 0x03 - address: 30775 - type: int32 - table: 'holding' - pub_only_on_change: true - scale: 1 - diff --git a/power/SMASTPx.power.yaml b/power/SMASTPx.power.yaml deleted file mode 100644 index 29c3780..0000000 --- a/power/SMASTPx.power.yaml +++ /dev/null @@ -1,17 +0,0 @@ -url: tcp://172.31.12.202:502 -#url: serial:///dev/ttysWK0 -update_rate: 60 -address_offset: 0 -scan_batching: 2 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: "Leistungen" - json_key: 'SMA_STP' - unit: 0x03 - address: 30775 - type: int32 - table: 'holding' - pub_only_on_change: true - scale: 1 - \ No newline at end of file From 4c277db75c8e23b41029a463fbf17257cc350b79 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:11:08 +0200 Subject: [PATCH 084/109] Delete status directory --- status/EastronRTU.yield.yaml | 136 ----------------------------------- status/SMASIx.status.yaml | 32 --------- status/SMASTPx.yield.yaml | 18 ----- 3 files changed, 186 deletions(-) delete mode 100644 status/EastronRTU.yield.yaml delete mode 100644 status/SMASIx.status.yaml delete mode 100644 status/SMASTPx.yield.yaml diff --git a/status/EastronRTU.yield.yaml b/status/EastronRTU.yield.yaml deleted file mode 100644 index 6a6c639..0000000 --- a/status/EastronRTU.yield.yaml +++ /dev/null @@ -1,136 +0,0 @@ -url: tcp://127.0.0.1:500 -#url: serial:///dev/ttysWK0 -update_rate: 30 -address_offset: 1 -scan_batching: 4 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: Verbrauch - json_key: "Zaehler 1" - unit: 1 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 1" - unit: 0x01 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 2" - unit: 0x02 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 2" - unit: 0x02 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 3" - unit: 0x03 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 3" - unit: 0x03 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 4" - unit: 0x04 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 4" - unit: 0x04 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 5" - unit: 0x05 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 5" - unit: 0x05 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 6" - unit: 0x06 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 6" - unit: 0x06 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 7" - unit: 0x07 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 7" - unit: 0x07 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 8" - unit: 0x08 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 8" - unit: 0x08 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 diff --git a/status/SMASIx.status.yaml b/status/SMASIx.status.yaml deleted file mode 100644 index 47af8cd..0000000 --- a/status/SMASIx.status.yaml +++ /dev/null @@ -1,32 +0,0 @@ -url: tcp://172.31.12.203:502 -#url: serial:///dev/ttysWK0 -update_rate: 600 -address_offset: 0 -scan_batching: 2 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: "Speicher" - json_key: 'Entladung' - unit: 0x03 - address: 30597 - type: uint32 - table: 'holding' - pub_only_on_change: false - scale: 0.001 - - pub_topic: "Speicher" - json_key: 'Ladung' - unit: 0x03 - address: 30595 - type: uint32 - table: 'holding' - pub_only_on_change: false - scale: 0.001 - - pub_topic: "Speicher" - json_key: 'Ladezustand' - unit: 0x03 - address: 30845 - type: uint32 - table: 'holding' - pub_only_on_change: false - scale: 1 diff --git a/status/SMASTPx.yield.yaml b/status/SMASTPx.yield.yaml deleted file mode 100644 index 349a9d6..0000000 --- a/status/SMASTPx.yield.yaml +++ /dev/null @@ -1,18 +0,0 @@ -url: tcp://172.31.12.202:502 -#url: serial:///dev/ttysWK0 -#unit: '0x13' wrong position ! -update_rate: 60 -address_offset: 0 -scan_batching: 2 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: "Erzeugung" - json_key: 'PV' - unit: 0x03 - address: 30529 - type: uint32 - table: 'holding' - pub_only_on_change: false - scale: 0.001 - From 39ddbb5a19d79deef426d7a93129b1c2c703233f Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:12:21 +0200 Subject: [PATCH 085/109] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9698385..2181415 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,16 @@ chmod 777 /etc/modbus4mqtt/* #move the binary mv modbus4mqtt /usr/local/bin/ +#create config folders +mkdir status +mkdir loop +mkdir power + #first run modbus4mqtt --help #test your config (example:) -modbus4mqtt --mqtt_topic_prefix "***" --hostname "***" --config /etc/modbus4mqtt/TCPRTU1.yaml +modbus4mqtt --mqtt_topic_prefix "***" --hostname "***" --config /etc/modbus4mqtt/****.yaml if your want to run a single run, use "--loop False" ``` From 76eb28d09dc0ad7e70f71799437791757ee204bd Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:13:11 +0200 Subject: [PATCH 086/109] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2181415..0cbc005 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Modbus4MQTT this is my rebuild of my fork from a fork from a fork from modbus4mqtt by tjhowse. I have added Double (float64), multiple servicefiles, singlerun option (--loop False), -cleaned up wrong templates. updated readme. +cleaned up wrong templates. updated readme and installation. ##### thanks to tjhowse for the main project From a2b28c412efde0abc42e756621afc7d77efdc117 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:15:17 +0200 Subject: [PATCH 087/109] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0cbc005..5ac17ae 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Modbus4MQTT this is my rebuild of my fork from a fork from a fork from modbus4mqtt by tjhowse. -I have added Double (float64), multiple servicefiles, singlerun option (--loop False), -cleaned up wrong templates. updated readme and installation. +I have added Double (float64), multiple servicefiles, singlerun option (--loop False), +cleaned up wrong templates. Removed docker as i use service files. updated readme and installation. ##### thanks to tjhowse for the main project @@ -35,7 +35,7 @@ systemctl daemon-reload #get your phyton version python3 --version -#install the python file to dist +#install the python-file to dist if 3.6.* mkdir /usr/local/lib/python3.6/dist-packages/modbus4mqtt From ca59120a47fad89a2358792f6378ff94232716dc Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:20:35 +0200 Subject: [PATCH 088/109] Add files via upload --- templates/SMASIx.status.yaml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/templates/SMASIx.status.yaml b/templates/SMASIx.status.yaml index 47af8cd..1eee40e 100644 --- a/templates/SMASIx.status.yaml +++ b/templates/SMASIx.status.yaml @@ -8,7 +8,7 @@ sort_json_keys: False registers: - pub_topic: "Speicher" json_key: 'Entladung' - unit: 0x03 + unit: 3 address: 30597 type: uint32 table: 'holding' @@ -16,7 +16,7 @@ registers: scale: 0.001 - pub_topic: "Speicher" json_key: 'Ladung' - unit: 0x03 + unit: 3 address: 30595 type: uint32 table: 'holding' @@ -24,9 +24,25 @@ registers: scale: 0.001 - pub_topic: "Speicher" json_key: 'Ladezustand' - unit: 0x03 + unit: 3 address: 30845 type: uint32 table: 'holding' pub_only_on_change: false scale: 1 + - pub_topic: "Speicher" + json_key: "SoH" + unit: 3 + address: 847 + type: uint32 + table: 'holding' + pub_only_on_change: false + scale: 1 + - pub_topic: "Speicher" + json_key: "SoC" + unit: 3 + address: 845 + type: int32 + table: 'holding' + pub_only_on_change: false + scale: 1 \ No newline at end of file From b6bbb8e05bbb04079e2e90ba136454ca04c2dc7b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:27:54 +0200 Subject: [PATCH 089/109] Create test.sh --- test.sh | 1 + 1 file changed, 1 insertion(+) create mode 100644 test.sh diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..8827fb9 --- /dev/null +++ b/test.sh @@ -0,0 +1 @@ +modbus4mqtt --loop False --mqtt_topic_prefix Testrun --config $1 From dd8c0f6403af40fa836402a580236c994ad64030 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:29:03 +0200 Subject: [PATCH 090/109] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ac17ae..2c0e071 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,9 @@ modbus4mqtt --help #test your config (example:) modbus4mqtt --mqtt_topic_prefix "***" --hostname "***" --config /etc/modbus4mqtt/****.yaml +test.sh /complete/path/to/config -if your want to run a single run, use "--loop False" +if your want to run a single run, add "--loop False" ``` ## use cron for singlerun: From fd9ffee33ae98796cc78b96cb8d0d24e421d9220 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:30:26 +0200 Subject: [PATCH 091/109] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2c0e071..da6679c 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,13 @@ systemctl daemon-reload python3 --version #install the python-file to dist -if 3.6.* -mkdir /usr/local/lib/python3.6/dist-packages/modbus4mqtt -mv *.py /usr/local/lib/python3.6/dist-packages/modbus4mqtt +#if 3.6.* +#mkdir /usr/local/lib/python3.6/dist-packages/modbus4mqtt +#mv *.py /usr/local/lib/python3.6/dist-packages/modbus4mqtt -if 3.10.* -mkdir /usr/local/lib/python3.10/dist-packages/modbus4mqtt -mv *.py /usr/local/lib/python3.10/dist-packages/modbus4mqtt +#if 3.10.* +#mkdir /usr/local/lib/python3.10/dist-packages/modbus4mqtt +#mv *.py /usr/local/lib/python3.10/dist-packages/modbus4mqtt #make executeable chmod 777 /etc/modbus4mqtt/* From 0593f6cb582e477e4eba0ffefe8e1591f0547460 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 00:42:23 +0200 Subject: [PATCH 092/109] Update modbus4mqtt.py --- modbus4mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index ff1950f..2adb5d0 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -384,7 +384,7 @@ def loop_forever(self): def singlerun(self): self.poll() - sleep(5) + sleep(10) @click.command() @click.option('--hostname', default='localhost', From d9013bd19a447cea4fe50993639a997edfae2964 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:11:24 +0200 Subject: [PATCH 093/109] Update EastronRTU.power.yaml --- templates/EastronRTU.power.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/EastronRTU.power.yaml b/templates/EastronRTU.power.yaml index 0846ae4..32cea15 100644 --- a/templates/EastronRTU.power.yaml +++ b/templates/EastronRTU.power.yaml @@ -1,7 +1,7 @@ url: tcp://127.0.0.1:500 #url: serial:///dev/ttysWK0 update_rate: 30 -address_offset: 1 +address_offset: 0 scan_batching: 4 word_order: highlow sort_json_keys: False @@ -9,7 +9,7 @@ registers: - pub_topic: Leistungen json_key: Leistung_1 unit: 0x01 - address: 53 + address: 52 type: float table: 'input' pub_only_on_change: false @@ -17,7 +17,7 @@ registers: - pub_topic: Leistungen json_key: Leistung_2 unit: 0x02 - address: 53 + address: 52 type: float table: 'input' pub_only_on_change: false @@ -25,7 +25,7 @@ registers: - pub_topic: Leistungen json_key: Leistung_3 unit: 0x03 - address: 53 + address: 52 type: float table: 'input' pub_only_on_change: false @@ -33,7 +33,7 @@ registers: - pub_topic: Leistungen json_key: Leistung_4 unit: 0x04 - address: 53 + address: 52 type: float table: 'input' pub_only_on_change: false @@ -41,7 +41,7 @@ registers: - pub_topic: Leistungen json_key: Leistung_5 unit: 0x05 - address: 53 + address: 52 type: float table: 'input' pub_only_on_change: false @@ -49,7 +49,7 @@ registers: - pub_topic: Leistungen json_key: Leistung_6 unit: 0x06 - address: 53 + address: 52 type: float table: 'input' pub_only_on_change: false @@ -57,7 +57,7 @@ registers: - pub_topic: Leistungen json_key: Leistung_7 unit: 0x07 - address: 53 + address: 52 type: float table: 'input' pub_only_on_change: false @@ -65,9 +65,9 @@ registers: - pub_topic: Leistungen json_key: Leistung_8 unit: 0x08 - address: 53 + address: 52 type: float table: 'input' pub_only_on_change: false scale: 1 - \ No newline at end of file + From 53b42b1577cc8302e3fa9d9d3a503b157b66937b Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:12:03 +0200 Subject: [PATCH 094/109] Update EastronRTU.yield.yaml --- templates/EastronRTU.yield.yaml | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/templates/EastronRTU.yield.yaml b/templates/EastronRTU.yield.yaml index 6a6c639..a3f9657 100644 --- a/templates/EastronRTU.yield.yaml +++ b/templates/EastronRTU.yield.yaml @@ -1,7 +1,7 @@ url: tcp://127.0.0.1:500 #url: serial:///dev/ttysWK0 update_rate: 30 -address_offset: 1 +address_offset: 0 scan_batching: 4 word_order: highlow sort_json_keys: False @@ -9,7 +9,7 @@ registers: - pub_topic: Verbrauch json_key: "Zaehler 1" unit: 1 - address: 73 + address: 72 type: float table: 'input' pub_only_on_change: true @@ -17,7 +17,7 @@ registers: - pub_topic: Erzeugung json_key: "Zaehler 1" unit: 0x01 - address: 75 + address: 74 type: float table: 'input' pub_only_on_change: true @@ -25,7 +25,7 @@ registers: - pub_topic: Verbrauch json_key: "Zaehler 2" unit: 0x02 - address: 73 + address: 72 type: float table: 'input' pub_only_on_change: true @@ -33,7 +33,7 @@ registers: - pub_topic: Erzeugung json_key: "Zaehler 2" unit: 0x02 - address: 75 + address: 74 type: float table: 'input' pub_only_on_change: true @@ -41,7 +41,7 @@ registers: - pub_topic: Verbrauch json_key: "Zaehler 3" unit: 0x03 - address: 73 + address: 72 type: float table: 'input' pub_only_on_change: true @@ -49,7 +49,7 @@ registers: - pub_topic: Erzeugung json_key: "Zaehler 3" unit: 0x03 - address: 75 + address: 74 type: float table: 'input' pub_only_on_change: true @@ -57,7 +57,7 @@ registers: - pub_topic: Verbrauch json_key: "Zaehler 4" unit: 0x04 - address: 73 + address: 72 type: float table: 'input' pub_only_on_change: true @@ -65,7 +65,7 @@ registers: - pub_topic: Erzeugung json_key: "Zaehler 4" unit: 0x04 - address: 75 + address: 74 type: float table: 'input' pub_only_on_change: true @@ -73,7 +73,7 @@ registers: - pub_topic: Verbrauch json_key: "Zaehler 5" unit: 0x05 - address: 73 + address: 72 type: float table: 'input' pub_only_on_change: true @@ -81,7 +81,7 @@ registers: - pub_topic: Erzeugung json_key: "Zaehler 5" unit: 0x05 - address: 75 + address: 74 type: float table: 'input' pub_only_on_change: true @@ -89,7 +89,7 @@ registers: - pub_topic: Verbrauch json_key: "Zaehler 6" unit: 0x06 - address: 73 + address: 72 type: float table: 'input' pub_only_on_change: true @@ -97,7 +97,7 @@ registers: - pub_topic: Erzeugung json_key: "Zaehler 6" unit: 0x06 - address: 75 + address: 74 type: float table: 'input' pub_only_on_change: true @@ -105,7 +105,7 @@ registers: - pub_topic: Verbrauch json_key: "Zaehler 7" unit: 0x07 - address: 73 + address: 72 type: float table: 'input' pub_only_on_change: true @@ -113,7 +113,7 @@ registers: - pub_topic: Erzeugung json_key: "Zaehler 7" unit: 0x07 - address: 75 + address: 74 type: float table: 'input' pub_only_on_change: true @@ -121,7 +121,7 @@ registers: - pub_topic: Verbrauch json_key: "Zaehler 8" unit: 0x08 - address: 73 + address: 72 type: float table: 'input' pub_only_on_change: true @@ -129,7 +129,7 @@ registers: - pub_topic: Erzeugung json_key: "Zaehler 8" unit: 0x08 - address: 75 + address: 74 type: float table: 'input' pub_only_on_change: true From 291cbdbf2f8c80d2e33304a666dcecb25c1c4fa3 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:12:26 +0200 Subject: [PATCH 095/109] Update SMASTPx.yield.yaml --- templates/SMASTPx.yield.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/SMASTPx.yield.yaml b/templates/SMASTPx.yield.yaml index 349a9d6..cb58337 100644 --- a/templates/SMASTPx.yield.yaml +++ b/templates/SMASTPx.yield.yaml @@ -1,7 +1,6 @@ url: tcp://172.31.12.202:502 #url: serial:///dev/ttysWK0 -#unit: '0x13' wrong position ! -update_rate: 60 +update_rate: 600 address_offset: 0 scan_batching: 2 word_order: highlow From 9f01be45cfca765a314a80ef4a06d1bdc094ffde Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:12:41 +0200 Subject: [PATCH 096/109] Delete TCPRTU1.yaml --- templates/TCPRTU1.yaml | 136 ----------------------------------------- 1 file changed, 136 deletions(-) delete mode 100644 templates/TCPRTU1.yaml diff --git a/templates/TCPRTU1.yaml b/templates/TCPRTU1.yaml deleted file mode 100644 index 623b56d..0000000 --- a/templates/TCPRTU1.yaml +++ /dev/null @@ -1,136 +0,0 @@ -url: tcp://127.0.0.1:500 -#url: serial:///dev/ttysWK0 -update_rate: 30 -address_offset: 1 -scan_batching: 4 -word_order: highlow -sort_json_keys: False -registers: - - pub_topic: Netzanschluss - json_key: Bezug - unit: 0x01 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Netzanschluss - json_key: Einspeisung - unit: 0x01 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 2" - unit: 0x02 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 2" - unit: 0x02 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 3" - unit: 0x03 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 3" - unit: 0x03 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 4" - unit: 0x04 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 4" - unit: 0x04 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 5" - unit: 0x05 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 5" - unit: 0x05 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 6" - unit: 0x06 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 6" - unit: 0x06 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 7" - unit: 0x07 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 7" - unit: 0x07 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Verbrauch - json_key: "Zaehler 8" - unit: 0x08 - address: 73 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 - - pub_topic: Erzeugung - json_key: "Zaehler 8" - unit: 0x08 - address: 75 - type: float - table: 'input' - pub_only_on_change: true - scale: 1 \ No newline at end of file From dcddfd52b0cbe155589190e9fb22c7a10012fd76 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Thu, 17 Aug 2023 16:14:11 +0200 Subject: [PATCH 097/109] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da6679c..cff3c9c 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ if your want to run a single run, add "--loop False" ## use cron for singlerun: I have provided unix service files. the #### autopower and the autostatus scripts -do not loop ! they need to be called every time. the servicefiles dont have restart settings. both use the corresponding folder +do not loop ! they need to be called every time. the servicefiles dont have restart settings. both use the corresponding folder. if you us LTE/Mobile data, you can save data with defined intervall. ```bash crontab -e From c1a5b32373bd5fe9080fd2973554042130f159d0 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:17:55 +0200 Subject: [PATCH 098/109] Update README.md Automatik phyton Version for Installation --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cff3c9c..65b46a8 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,11 @@ mv *.service /etc/systemd/system/ systemctl daemon-reload #get your phyton version -python3 --version +version=$(python3 --version | cut -c 8- | cut -f1,2 -d'.') #install the python-file to dist +mkdir /usr/local/lib/python$version/dist-packages/modbus4mqtt +mv *.py /usr/local/lib/python$version/dist-packages/modbus4mqtt -#if 3.6.* -#mkdir /usr/local/lib/python3.6/dist-packages/modbus4mqtt -#mv *.py /usr/local/lib/python3.6/dist-packages/modbus4mqtt - -#if 3.10.* -#mkdir /usr/local/lib/python3.10/dist-packages/modbus4mqtt -#mv *.py /usr/local/lib/python3.10/dist-packages/modbus4mqtt #make executeable chmod 777 /etc/modbus4mqtt/* From eb877d2419d0949c737da99d4055705e89a5eff2 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:21:41 +0200 Subject: [PATCH 099/109] Update README.md Removed some typo --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 65b46a8..a03244f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ git clone https://github.com/Pubaluba/modbus4mqtt_rebuild mv modbus4mqtt_rebuild /etc/modbus4mqtt cd /etc/modbus4mqtt -#install phyton and requirements +#install python and requirements apt install python3-pip pip3 install -r requirements.txt @@ -33,7 +33,7 @@ pip3 install -r requirements.txt mv *.service /etc/systemd/system/ systemctl daemon-reload -#get your phyton version +#get your python version version=$(python3 --version | cut -c 8- | cut -f1,2 -d'.') #install the python-file to dist mkdir /usr/local/lib/python$version/dist-packages/modbus4mqtt @@ -64,7 +64,11 @@ if your want to run a single run, add "--loop False" ## use cron for singlerun: I have provided unix service files. the #### autopower and the autostatus scripts -do not loop ! they need to be called every time. the servicefiles dont have restart settings. both use the corresponding folder. if you us LTE/Mobile data, you can save data with defined intervall. +do not loop ! they need to be called every time. + +these script are starting every yaml in related folder as own Service. + +the servicefiles dont have restart settings. both use the corresponding folder. if you us LTE/Mobile data, you can save data with defined intervall. ```bash crontab -e From 4adf3e709ccb74978f23aca5fe2e6804a08e6ade Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:24:10 +0200 Subject: [PATCH 100/109] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a03244f..4688f87 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ do not loop ! they need to be called every time. these script are starting every yaml in related folder as own Service. -the servicefiles dont have restart settings. both use the corresponding folder. if you us LTE/Mobile data, you can save data with defined intervall. +the servicefiles dont have restart settings. both also use the related folder and are running without being delayed by other instances. if you us LTE/Mobile data, you can save data with defined intervall. ```bash crontab -e From ed7a7535ae869ac516fd36f3d8f98b8b68cdc557 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:29:36 +0200 Subject: [PATCH 101/109] Update modbus4mqtt.py Removed Typo in helptext --- modbus4mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index 2adb5d0..2518bc0 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -410,7 +410,7 @@ def singlerun(self): @click.option('--key', default=None, help='Client private key for authentication, if required by server.', show_default=True) @click.option('--loop', default='True', - help='use True if you want to disable oneshot and use update_rate in loop.', show_default=True) + help='use False if you want to disable looping with update_rate and only want to run run 1 poll.', show_default=True) def main(hostname, port, username, password, config, mqtt_topic_prefix, use_tls, insecure, cafile, cert, key, loop): logging.basicConfig( From 8a37a9a35de5553f1f1bf019f4d481c18d4bcd83 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:18:03 +0100 Subject: [PATCH 102/109] Update modbus4mqtt.py added 'last-data' wih unix timestamp into poll loop, the server can now display the last known transmision --- modbus4mqtt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index 2518bc0..d23bb19 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -219,7 +219,8 @@ def poll(self): elif changed: retain = register.get('retain', False) self._mqtt_client.publish(self.prefix+register['pub_topic'], value, retain=retain) - + # Transmit Data-Timestamp + self._mqtt_client.publish(self.prefix+'last-data', time.time() ) # Transmit the queued JSON messages. for topic, message in json_messages.items(): if json_messages_changed[topic]: From c6f6db30e138a028ce8717cac20bb983b12f266c Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:22:12 +0100 Subject: [PATCH 103/109] Update modbus4mqtt.py raised version a little bit --- modbus4mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index d23bb19..aa10076 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -10,7 +10,7 @@ import paho.mqtt.client as mqtt from . import modbus_interface -version = "EW.0.8" +version = "EW.0.81" MAX_DECIMAL_POINTS = 3 DEFAULT_SCAN_RATE_S = 5 From 176564de469a6741fbd455560c94b84cdf08a23e Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 10 Jan 2024 23:18:20 +0100 Subject: [PATCH 104/109] Update modbus4mqtt.py Added threading 4 polling --- modbus4mqtt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index aa10076..e3ec793 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -8,6 +8,7 @@ from ccorp.ruamel.yaml.include import YAML import click import paho.mqtt.client as mqtt +import threading from . import modbus_interface version = "EW.0.81" @@ -378,10 +379,12 @@ def _load_modbus_config(self, path): return result def loop_forever(self): + pollthread = threading.Thread(target = self.poll, args = ()) while True: # TODO this properly. - self.poll() + pollthread.start() sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) + pollthread.join() def singlerun(self): self.poll() From 35e773e7b650bf25b17463ccadb921e1bcaae689 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 10 Jan 2024 23:41:28 +0100 Subject: [PATCH 105/109] Update modbus4mqtt.py --- modbus4mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index e3ec793..ce10d4d 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -379,9 +379,9 @@ def _load_modbus_config(self, path): return result def loop_forever(self): - pollthread = threading.Thread(target = self.poll, args = ()) while True: # TODO this properly. + pollthread = threading.Thread(target = self.poll, args = ()) pollthread.start() sleep(self.config.get('update_rate', DEFAULT_SCAN_RATE_S)) pollthread.join() From 5a946d2500cd42374adc2bb7e8506c02a71faffd Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Wed, 10 Jan 2024 23:41:50 +0100 Subject: [PATCH 106/109] Update modbus4mqtt.py --- modbus4mqtt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modbus4mqtt.py b/modbus4mqtt.py index ce10d4d..f17aff5 100644 --- a/modbus4mqtt.py +++ b/modbus4mqtt.py @@ -11,7 +11,7 @@ import threading from . import modbus_interface -version = "EW.0.81" +version = "EW.0.9" MAX_DECIMAL_POINTS = 3 DEFAULT_SCAN_RATE_S = 5 From 246adad8c4815743a06319ee8838ec9eb7811d49 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Mon, 22 Jan 2024 21:47:34 +0100 Subject: [PATCH 107/109] Update README.md added set_topic only as an example --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4688f87..e635da5 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,14 @@ registers: unit: 255 type: double - - + - set_topic: Stop/set # u need to create to topic on your mqtt-server by yourself, + address: 4 # can be used to "control" multiple devices at once. + unit: 255 # the device-specific value is not pulished ! :-D + type: uint16 + pub_only_on_change: true + value_map: + "false": 0 + "true": 1 ``` From 3d900e65186a59d14722b675af83ec9c23744b9e Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:29:59 +0100 Subject: [PATCH 108/109] Update README.md added desciption for the Thread-Feature --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e635da5..ecdd6f4 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,8 @@ you can use cron every 10 sec for power data ``` ## to use a service with "updaterate" setting: -please be awere of inconsistent timestamp. running multiple services @ different devices will loop different because a simple sleep command is used after polling. if a read request of 8 rtu devices has a duration of 2.6 seconds, the update_rate rate will be 32.6 if you set 30. +with version 0.9, the poll of the devices is threaded. after the sleep (update_rate) is past, the thread is restartet. still, a little amount of time is +spend for the script, so a update_rate of "3" results in example 3.012345 seconds depending on CPU speed. if poll has to poll multiple devices and times-out with some of them to more then 3 seconds in our example, the poll finisches before a new thread gets started an the time raises above 3 seconds. copy a ./template/.yaml file to /etc/modbus4mqtt/loop/ ```bash From 46305998dc52b6f4528eea619536faa8ddc03ad3 Mon Sep 17 00:00:00 2001 From: Pubaluba <30649275+Pubaluba@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:56:48 +0200 Subject: [PATCH 109/109] Update requirements.txt limit paho-mqtt version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5567a87..6870dc1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ setuptools ruamel.yaml>=0.16.12 click -paho-mqtt +paho-mqtt>=1.5.0,<2 pymodbus>=2.3.0,<3.0.0 SungrowModbusTcpClient>=0.1.6 ccorp-yaml-include-relative-path