From a9fbbd62dc73ade9b49ab892540e5a8d117d93e2 Mon Sep 17 00:00:00 2001 From: Till Schulte-Coerne Date: Thu, 16 Jan 2025 14:03:37 +0100 Subject: [PATCH] Big restructuring --- .gitignore | 3 ++ __init__.py | 0 __main__.py | 8 +++ build/lib/can2mqtt/__init__.py | 0 build/lib/can2mqtt/app.py | 28 ++++++++++ build/lib/can2mqtt/can_listener.py | 38 ++++++++++++++ build/lib/can2mqtt/test/__init__.py | 0 build/lib/can2mqtt/test/mqtt_dummy.py | 6 +++ build/lib/can2mqtt/test/test_main.py | 24 +++++++++ can2mqtt.egg-info/PKG-INFO | 60 +++++++++++++++++++++ can2mqtt.egg-info/SOURCES.txt | 14 +++++ can2mqtt.egg-info/dependency_links.txt | 1 + can2mqtt.egg-info/entry_points.txt | 2 + can2mqtt.egg-info/requires.txt | 9 ++++ can2mqtt.egg-info/top_level.txt | 1 + can2mqtt/__init__.py | 0 can2mqtt/app.py | 28 ++++++++++ can2mqtt/can_listener.py | 38 ++++++++++++++ can2mqtt/test/__init__.py | 0 can2mqtt/test/mqtt_dummy.py | 6 +++ can2mqtt/test/simple.dbc | 2 + can2mqtt/test/test_main.py | 24 +++++++++ main.py | 73 -------------------------- setup.py | 4 +- 24 files changed, 294 insertions(+), 75 deletions(-) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100755 __main__.py create mode 100644 build/lib/can2mqtt/__init__.py create mode 100644 build/lib/can2mqtt/app.py create mode 100644 build/lib/can2mqtt/can_listener.py create mode 100644 build/lib/can2mqtt/test/__init__.py create mode 100644 build/lib/can2mqtt/test/mqtt_dummy.py create mode 100644 build/lib/can2mqtt/test/test_main.py create mode 100644 can2mqtt.egg-info/PKG-INFO create mode 100644 can2mqtt.egg-info/SOURCES.txt create mode 100644 can2mqtt.egg-info/dependency_links.txt create mode 100644 can2mqtt.egg-info/entry_points.txt create mode 100644 can2mqtt.egg-info/requires.txt create mode 100644 can2mqtt.egg-info/top_level.txt create mode 100644 can2mqtt/__init__.py create mode 100644 can2mqtt/app.py create mode 100644 can2mqtt/can_listener.py create mode 100644 can2mqtt/test/__init__.py create mode 100644 can2mqtt/test/mqtt_dummy.py create mode 100644 can2mqtt/test/simple.dbc create mode 100644 can2mqtt/test/test_main.py delete mode 100755 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a172f41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +.vscode +dist diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/__main__.py b/__main__.py new file mode 100755 index 0000000..88afe0a --- /dev/null +++ b/__main__.py @@ -0,0 +1,8 @@ +#!/usr/bin/python3 + +from can2mqtt.app import load_config, load_dbc_db, main_program + +if __name__ == '__main__': + config = load_config() + dbc_db = load_dbc_db(config['dbc_files']) + main_program(config, dbc_db) diff --git a/build/lib/can2mqtt/__init__.py b/build/lib/can2mqtt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build/lib/can2mqtt/app.py b/build/lib/can2mqtt/app.py new file mode 100644 index 0000000..8d89301 --- /dev/null +++ b/build/lib/can2mqtt/app.py @@ -0,0 +1,28 @@ +import can +import cantools +import paho.mqtt.client as mqtt +import yaml +import threading +from can2mqtt.can_listener import CanListener + +def load_config(): + with open('config.yaml', 'r') as stream: + return yaml.load(stream) + +def load_dbc_db(dbc_files): + db = cantools.database.Database() + for file in dbc_files: + with open (file, 'r') as fin: + db.add_dbc(fin) + return db + +def main_program(config, dbc_db): + mqtt_client = mqtt.Client('can2mqtt') + mqtt_client.username_pw_set(username=config['mqtt']['username'],password=config['mqtt']['password']) + mqtt_client.connect(config['mqtt']['host']) + + can_listener = CanListener(dbc_db, mqtt_client) + bus = can.interface.Bus(bustype='socketcan', channel=config['can']['interface'], bitrate=config['can']['bitrate']) + can.Notifier(bus, [can_listener]) + + threading.Event().wait() diff --git a/build/lib/can2mqtt/can_listener.py b/build/lib/can2mqtt/can_listener.py new file mode 100644 index 0000000..0d4debc --- /dev/null +++ b/build/lib/can2mqtt/can_listener.py @@ -0,0 +1,38 @@ +import can + +class CanListener(can.Listener): + + first_ts = 0 + last = {} + first_underscores_to_slash = False + prefix = False + + def __init__(self, db, mqtt_client, config): + if 'mqtt' in config: + if 'topic_names' in config['mqtt']: + if 'first_underscores_to_slash' in config['mqtt']['topic_names']: + self.first_underscores_to_slash = config['mqtt']['topic_names']['first_underscores_to_slash'] + if 'prefix' in config['mqtt']['topic_names']: + self.prefix = config['mqtt']['topic_names']['prefix'] + self.db = db + self.mqtt_client = mqtt_client + + def on_message_received(self, m): + if self.first_ts == 0: + self.first_ts = m.timestamp + + try: + msg = self.db.decode_message(m.arbitration_id, m.data) + for signal_id in msg: + topic = signal_id.lower() + if self.first_underscores_to_slash: + xxx + topic = topic.replace('_', '/', self.first_underscores_to_slash) + if self.prefix: + topic = self.prefix + topic + data = round(msg[signal_id], 5) + if topic not in self.last or self.last[topic]['data'] != data or m.timestamp - self.last[topic]['timestamp'] > 60: + self.last[topic] = {'data': data, 'timestamp': m.timestamp} + self.mqtt_client.publish(topic, data) + except KeyError: + pass diff --git a/build/lib/can2mqtt/test/__init__.py b/build/lib/can2mqtt/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build/lib/can2mqtt/test/mqtt_dummy.py b/build/lib/can2mqtt/test/mqtt_dummy.py new file mode 100644 index 0000000..b3da237 --- /dev/null +++ b/build/lib/can2mqtt/test/mqtt_dummy.py @@ -0,0 +1,6 @@ +class MqttDummy(): + + def publish(self, topic, data): + self.topic = topic + self.data = data + diff --git a/build/lib/can2mqtt/test/test_main.py b/build/lib/can2mqtt/test/test_main.py new file mode 100644 index 0000000..20b4d6d --- /dev/null +++ b/build/lib/can2mqtt/test/test_main.py @@ -0,0 +1,24 @@ +import unittest + +import can +import struct + +from can2mqtt.test.mqtt_dummy import MqttDummy +from can2mqtt.can_listener import CanListener +from can2mqtt.app import load_dbc_db + +class TestStringMethods(unittest.TestCase): + + def test_can_listener_converts_topic_names(self): + mqtt_dummy = MqttDummy() + + config = {} + can_listener = CanListener(load_dbc_db(['can2mqtt/test/simple.dbc']), mqtt_dummy, config) + + message = can.Message(arbitration_id=500, data=struct.pack('4b', 0, 0, 0, 50)) + can_listener.on_message_received(message) + self.assertEqual(mqtt_dummy.topic, 'io/debug/test_unsigned') + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/can2mqtt.egg-info/PKG-INFO b/can2mqtt.egg-info/PKG-INFO new file mode 100644 index 0000000..5a4b6d8 --- /dev/null +++ b/can2mqtt.egg-info/PKG-INFO @@ -0,0 +1,60 @@ +Metadata-Version: 2.2 +Name: can2mqtt +Version: 0.0.1 +Summary: A python can to mqtt bridge +Home-page: https://github.com/tillsc/can2mqtt +Author: Till Schulte-Coerne +Author-email: till.schulte-coerne@innoq.com +Project-URL: Bug Reports, https://github.com/tillsc/can2mqtt/issues +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Requires-Python: !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4 +Description-Content-Type: text/markdown +Requires-Dist: python-can +Requires-Dist: cantools +Requires-Dist: paho-mqtt +Requires-Dist: pyyaml +Provides-Extra: dev +Provides-Extra: test +Requires-Dist: pytest; extra == "test" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: keywords +Dynamic: project-url +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary + +[DBC](http://socialledge.com/sjsu/index.php/DBC_Format) based CAN to MQTT brigde +=== + +This is a generic CAN 2 MQTT bridge build with Python 3 + +### Setup + +This package is based upon the following dependencies: + +* [python-can](https://python-can.readthedocs.io/en/master/) +* [cantools](https://github.com/eerimoq/cantools) +* [phao](http://www.eclipse.org/paho/) + +Install all required Python 3 dependencies on a raspberry: + + sudo apt-get install python3-pip python3-can + pip3 install paho-mqtt cantools + +### Usage + +Copy your DBC files into the root directory. + +Copy config.example.yaml to config.yaml and modify it. + +Run `./main.py` or `python3 main.py` diff --git a/can2mqtt.egg-info/SOURCES.txt b/can2mqtt.egg-info/SOURCES.txt new file mode 100644 index 0000000..a162a5c --- /dev/null +++ b/can2mqtt.egg-info/SOURCES.txt @@ -0,0 +1,14 @@ +README.md +setup.py +can2mqtt/__init__.py +can2mqtt/app.py +can2mqtt/can_listener.py +can2mqtt.egg-info/PKG-INFO +can2mqtt.egg-info/SOURCES.txt +can2mqtt.egg-info/dependency_links.txt +can2mqtt.egg-info/entry_points.txt +can2mqtt.egg-info/requires.txt +can2mqtt.egg-info/top_level.txt +can2mqtt/test/__init__.py +can2mqtt/test/mqtt_dummy.py +can2mqtt/test/test_main.py \ No newline at end of file diff --git a/can2mqtt.egg-info/dependency_links.txt b/can2mqtt.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/can2mqtt.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/can2mqtt.egg-info/entry_points.txt b/can2mqtt.egg-info/entry_points.txt new file mode 100644 index 0000000..8d5f69d --- /dev/null +++ b/can2mqtt.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +run = run:main diff --git a/can2mqtt.egg-info/requires.txt b/can2mqtt.egg-info/requires.txt new file mode 100644 index 0000000..b0d799c --- /dev/null +++ b/can2mqtt.egg-info/requires.txt @@ -0,0 +1,9 @@ +python-can +cantools +paho-mqtt +pyyaml + +[dev] + +[test] +pytest diff --git a/can2mqtt.egg-info/top_level.txt b/can2mqtt.egg-info/top_level.txt new file mode 100644 index 0000000..4344535 --- /dev/null +++ b/can2mqtt.egg-info/top_level.txt @@ -0,0 +1 @@ +can2mqtt diff --git a/can2mqtt/__init__.py b/can2mqtt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/can2mqtt/app.py b/can2mqtt/app.py new file mode 100644 index 0000000..8d89301 --- /dev/null +++ b/can2mqtt/app.py @@ -0,0 +1,28 @@ +import can +import cantools +import paho.mqtt.client as mqtt +import yaml +import threading +from can2mqtt.can_listener import CanListener + +def load_config(): + with open('config.yaml', 'r') as stream: + return yaml.load(stream) + +def load_dbc_db(dbc_files): + db = cantools.database.Database() + for file in dbc_files: + with open (file, 'r') as fin: + db.add_dbc(fin) + return db + +def main_program(config, dbc_db): + mqtt_client = mqtt.Client('can2mqtt') + mqtt_client.username_pw_set(username=config['mqtt']['username'],password=config['mqtt']['password']) + mqtt_client.connect(config['mqtt']['host']) + + can_listener = CanListener(dbc_db, mqtt_client) + bus = can.interface.Bus(bustype='socketcan', channel=config['can']['interface'], bitrate=config['can']['bitrate']) + can.Notifier(bus, [can_listener]) + + threading.Event().wait() diff --git a/can2mqtt/can_listener.py b/can2mqtt/can_listener.py new file mode 100644 index 0000000..0d4debc --- /dev/null +++ b/can2mqtt/can_listener.py @@ -0,0 +1,38 @@ +import can + +class CanListener(can.Listener): + + first_ts = 0 + last = {} + first_underscores_to_slash = False + prefix = False + + def __init__(self, db, mqtt_client, config): + if 'mqtt' in config: + if 'topic_names' in config['mqtt']: + if 'first_underscores_to_slash' in config['mqtt']['topic_names']: + self.first_underscores_to_slash = config['mqtt']['topic_names']['first_underscores_to_slash'] + if 'prefix' in config['mqtt']['topic_names']: + self.prefix = config['mqtt']['topic_names']['prefix'] + self.db = db + self.mqtt_client = mqtt_client + + def on_message_received(self, m): + if self.first_ts == 0: + self.first_ts = m.timestamp + + try: + msg = self.db.decode_message(m.arbitration_id, m.data) + for signal_id in msg: + topic = signal_id.lower() + if self.first_underscores_to_slash: + xxx + topic = topic.replace('_', '/', self.first_underscores_to_slash) + if self.prefix: + topic = self.prefix + topic + data = round(msg[signal_id], 5) + if topic not in self.last or self.last[topic]['data'] != data or m.timestamp - self.last[topic]['timestamp'] > 60: + self.last[topic] = {'data': data, 'timestamp': m.timestamp} + self.mqtt_client.publish(topic, data) + except KeyError: + pass diff --git a/can2mqtt/test/__init__.py b/can2mqtt/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/can2mqtt/test/mqtt_dummy.py b/can2mqtt/test/mqtt_dummy.py new file mode 100644 index 0000000..b3da237 --- /dev/null +++ b/can2mqtt/test/mqtt_dummy.py @@ -0,0 +1,6 @@ +class MqttDummy(): + + def publish(self, topic, data): + self.topic = topic + self.data = data + diff --git a/can2mqtt/test/simple.dbc b/can2mqtt/test/simple.dbc new file mode 100644 index 0000000..1a31c31 --- /dev/null +++ b/can2mqtt/test/simple.dbc @@ -0,0 +1,2 @@ +BO_ 500 IO_DEBUG: 4 IO + SG_ IO_DEBUG_test_unsigned : 0|8@1+ (1,0) [0|0] "" DBG diff --git a/can2mqtt/test/test_main.py b/can2mqtt/test/test_main.py new file mode 100644 index 0000000..7ec616a --- /dev/null +++ b/can2mqtt/test/test_main.py @@ -0,0 +1,24 @@ +import unittest + +import can +import struct + +from can2mqtt.test.mqtt_dummy import MqttDummy +from can2mqtt.can_listener import CanListener +from can2mqtt.app import load_dbc_db + +class TestStringMethods(unittest.TestCase): + + def test_can_listener_converts_topic_names(self): + mqtt_dummy = MqttDummy() + + config = {} + can_listener = CanListener(load_dbc_db(['can2mqtt/test/simple.dbc']), mqtt_dummy, config) + + message = can.Message(arbitration_id=500, data=struct.pack('4b', 0, 0, 0, 50)) + can_listener.on_message_received(message) + self.assertEqual(mqtt_dummy.topic, 'io_debug_test_unsigned') + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100755 index 419a8ee..0000000 --- a/main.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/python3 - -import can -import cantools -import paho.mqtt.client as mqtt -import yaml - -try: - with open("config.yaml", 'r') as stream: - config = yaml.load(stream) -except FileNotFoundError as exc: - print("Could not open file 'config.yaml'") - exit(1) -except yaml.YAMLError as exc: - print("Could not parse file 'config.yaml':") - print(exc) - exit(1) - -class CanListener(can.Listener): - - first_ts = 0 - last = {} - first_underscores_to_slash = False - prefix = False - if 'topic_names' in config['mqtt']: - if 'first_underscores_to_slash' in config['mqtt']['topic_names']: - first_underscores_to_slash = config['mqtt']['topic_names']['first_underscores_to_slash'] - if 'prefix' in config['mqtt']['topic_names']: - prefix = config['mqtt']['topic_names']['prefix'] - - def __init__(self, db, mqtt_client): - self.db = db - self.mqtt_client = mqtt_client - - def on_message_received(self, m): - if self.first_ts == 0: - self.first_ts = m.timestamp - - try: - msg = self.db.decode_message(m.arbitration_id, m.data) - for signal_id in msg: - topic = signal_id.lower() - if self.first_underscores_to_slash: - topic = topic.replace("_", "/", self.first_underscores_to_slash) - if self.prefix: - topic = self.prefix + topic - data = round(msg[signal_id], 5) - if topic not in self.last or self.last[topic]['data'] != data or m.timestamp - self.last[topic]['timestamp'] > 60: - self.last[topic] = {'data': data, 'timestamp': m.timestamp} - self.mqtt_client.publish(topic, data) - except KeyError: - pass - - - -def main_program(): - mqtt_client = mqtt.Client('can2mqtt') - mqtt_client.username_pw_set(username=config['mqtt']['username'],password=config['mqtt']['password']) - mqtt_client.connect('hassio.fritz.box') - bus = can.interface.Bus(bustype='socketcan', channel=config['can']['interface'], bitrate=config['can']['bitrate']) - - db = cantools.database.Database() - for file in config['dbc_files']: - with open (file, 'r') as fin: - db.add_dbc(fin) - - can_listener = CanListener(db, mqtt_client) - can.Notifier(bus, [can_listener]) - - while True: - pass - -main_program() diff --git a/setup.py b/setup.py index 0f4140d..f4d12da 100644 --- a/setup.py +++ b/setup.py @@ -136,7 +136,7 @@ # # For an analysis of "install_requires" vs pip's requirements files see: # https://packaging.python.org/en/latest/requirements.html - install_requires=['python-can', 'cantools', 'paho-mqtt'], # Optional + install_requires=['python-can', 'cantools', 'paho-mqtt', 'pyyaml'], # Optional # List additional groups of dependencies here (e.g. development # dependencies). Users will be able to install these using the "extras" @@ -148,7 +148,7 @@ # projects. extras_require={ # Optional 'dev': [''], - 'test': [''], + 'test': ['pytest'], }, # If there are data files included in your packages that need to be