From fa9d5d73c4ab232e9a5f4e597f607563373080df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Tue, 10 Jan 2023 03:09:56 +0100 Subject: [PATCH 1/6] WIP: Initial work on configurable source device --- srnemqtt/__main__.py | 12 ++++++++++-- srnemqtt/config.py | 18 ++++++++++++++++++ srnemqtt/sources/__init__.py | 7 +++++++ srnemqtt/sources/feasycom.py | 7 +++++++ srnemqtt/sources/serial.py | 8 ++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 srnemqtt/sources/__init__.py create mode 100644 srnemqtt/sources/feasycom.py create mode 100644 srnemqtt/sources/serial.py diff --git a/srnemqtt/__main__.py b/srnemqtt/__main__.py index 70cb669..4470d7b 100755 --- a/srnemqtt/__main__.py +++ b/srnemqtt/__main__.py @@ -5,7 +5,8 @@ import time from decimal import Decimal from typing import cast -from bluepy import btle +from bluepy.btle import BTLEDisconnectError +from serial import SerialException from .config import get_config, get_consumers from .constants import MAC @@ -15,12 +16,19 @@ from .solar_types import DataName from .util import Periodical, log +class CommunicationError(BTLEDisconnectError, SerialException): + pass + + def main(): conf = get_config() consumers = get_consumers(conf) per_voltages = Periodical(interval=15) per_current_hist = Periodical(interval=60) + # import serial + + # ser = serial.Serial() try: while True: @@ -96,7 +104,7 @@ def main(): # else: # write(wd, CMD_ENABLE_LOAD) - except btle.BTLEDisconnectError: + except CommunicationError: log("ERROR: Disconnected") time.sleep(1) diff --git a/srnemqtt/config.py b/srnemqtt/config.py index de435eb..2b680a7 100644 --- a/srnemqtt/config.py +++ b/srnemqtt/config.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import importlib import os +from io import RawIOBase from time import sleep from typing import Any, Dict, List, Optional, Type @@ -52,6 +53,23 @@ def get_consumers(conf: Optional[Dict[str, Any]] = None) -> List[BaseConsumer]: return consumers +def get_producers(conf: Optional[Dict[str, Any]] = None) -> List[RawIOBase]: + raise NotImplementedError + if conf is None: + conf = get_config() + + consumers = [] + for name, consumer_config in conf["consumers"].items(): + # print(name, consumer_config) + mod = get_consumer(name) + if mod: + # print(mod) + consumers.append(mod(consumer_config)) + + write_config(conf) + return consumers + + if __name__ == "__main__": conf = get_config() diff --git a/srnemqtt/sources/__init__.py b/srnemqtt/sources/__init__.py new file mode 100644 index 0000000..5d697bc --- /dev/null +++ b/srnemqtt/sources/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from abc import ABCMeta +from io import RawIOBase + + +class BaseSource(RawIOBase, metaclass=ABCMeta): + pass diff --git a/srnemqtt/sources/feasycom.py b/srnemqtt/sources/feasycom.py new file mode 100644 index 0000000..c3c0859 --- /dev/null +++ b/srnemqtt/sources/feasycom.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# from ..lib.feasycom_ble import BTLEUart +from . import BaseSource + + +class FeasycomSource(BaseSource): + pass diff --git a/srnemqtt/sources/serial.py b/srnemqtt/sources/serial.py new file mode 100644 index 0000000..7322a31 --- /dev/null +++ b/srnemqtt/sources/serial.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# import serial + +from . import BaseSource + + +class SerialSource(BaseSource): + pass -- 2.40.1 From 7ab0b1ce62821dce3b85218a77ef9d684969a4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Fri, 7 Apr 2023 21:13:32 +0200 Subject: [PATCH 2/6] Add serial test scripts --- misc/test_serial.py | 29 +++++++++++++++++++++++++++++ misc/test_serial_loopback.py | 7 +++++++ requirements.txt | 1 + 3 files changed, 37 insertions(+) create mode 100644 misc/test_serial.py create mode 100644 misc/test_serial_loopback.py diff --git a/misc/test_serial.py b/misc/test_serial.py new file mode 100644 index 0000000..7ea6505 --- /dev/null +++ b/misc/test_serial.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +import os +import sys +from time import sleep + +from serial import Serial + +print(sys.path) +sys.path.insert(1, os.path.dirname(os.path.dirname(sys.argv[0]))) +# from srnemqtt.constants import MAC +# from srnemqtt.lib.feasycom_ble import BTLEUart +from srnemqtt.protocol import construct_request, write # noqa: E402 + +for rate in [1200, 2400, 4800, 9600, 115200]: + print(rate) + with Serial("/dev/ttyUSB0", baudrate=rate, timeout=2) as x: + sleep(2) + + print(x) + + write(x, construct_request(0x0E, words=3)) + print(x.read(3)) + print(x.read(6)) + print(x.read(2)) + + # x.timeout = 2 + + # print(x.read()) + # print(x.read(1)) diff --git a/misc/test_serial_loopback.py b/misc/test_serial_loopback.py new file mode 100644 index 0000000..3351171 --- /dev/null +++ b/misc/test_serial_loopback.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from serial import Serial + +with Serial("/dev/ttyUSB0", baudrate=9600, timeout=2) as x: + x.write(b"Hello, World!") + print(x.read(13)) + print(x.read(13)) diff --git a/requirements.txt b/requirements.txt index a12f1d1..ea50cd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ rrdtool bluepy libscrc paho-mqtt +pyserial -- 2.40.1 From 1ccea2bf9c32b849324e5a41e4d0b57eabb4f4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Fri, 7 Apr 2023 23:26:55 +0200 Subject: [PATCH 3/6] Move files --- srnemqtt/{sources => interfaces}/__init__.py | 2 +- srnemqtt/{sources => interfaces}/feasycom.py | 0 srnemqtt/{sources => interfaces}/serial.py | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename srnemqtt/{sources => interfaces}/__init__.py (62%) rename srnemqtt/{sources => interfaces}/feasycom.py (100%) rename srnemqtt/{sources => interfaces}/serial.py (100%) diff --git a/srnemqtt/sources/__init__.py b/srnemqtt/interfaces/__init__.py similarity index 62% rename from srnemqtt/sources/__init__.py rename to srnemqtt/interfaces/__init__.py index 5d697bc..e8ecc37 100644 --- a/srnemqtt/sources/__init__.py +++ b/srnemqtt/interfaces/__init__.py @@ -3,5 +3,5 @@ from abc import ABCMeta from io import RawIOBase -class BaseSource(RawIOBase, metaclass=ABCMeta): +class BaseInterface(RawIOBase, metaclass=ABCMeta): pass diff --git a/srnemqtt/sources/feasycom.py b/srnemqtt/interfaces/feasycom.py similarity index 100% rename from srnemqtt/sources/feasycom.py rename to srnemqtt/interfaces/feasycom.py diff --git a/srnemqtt/sources/serial.py b/srnemqtt/interfaces/serial.py similarity index 100% rename from srnemqtt/sources/serial.py rename to srnemqtt/interfaces/serial.py -- 2.40.1 From 80bfd414ec699ad22a8815c997f73f5585e01740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Fri, 7 Apr 2023 23:57:37 +0200 Subject: [PATCH 4/6] Implement get_interface --- config-example.yaml | 12 ++++++++++++ srnemqtt/__main__.py | 8 +++----- srnemqtt/config.py | 32 ++++++++++++++++++++------------ srnemqtt/interfaces/feasycom.py | 9 ++++++--- srnemqtt/interfaces/serial.py | 6 +++--- 5 files changed, 44 insertions(+), 23 deletions(-) create mode 100644 config-example.yaml diff --git a/config-example.yaml b/config-example.yaml new file mode 100644 index 0000000..cc72de9 --- /dev/null +++ b/config-example.yaml @@ -0,0 +1,12 @@ +consumers: + stdio.StdoutConsumer: {} +interface: + name: serial.SerialInterface + params: + device: /dev/ttyUSB0 + baudrate: 9600 + timeout: 2 +# name: feasycom.FeasycomInterface +# params: +# mac: DC:0D:30:9C:61:BA +# timeout: 5 diff --git a/srnemqtt/__main__.py b/srnemqtt/__main__.py index 4470d7b..38d8058 100755 --- a/srnemqtt/__main__.py +++ b/srnemqtt/__main__.py @@ -8,15 +8,13 @@ from typing import cast from bluepy.btle import BTLEDisconnectError from serial import SerialException -from .config import get_config, get_consumers -from .constants import MAC -from .lib.feasycom_ble import BTLEUart +from .config import get_config, get_consumers, get_interface from .protocol import parse_battery_state, parse_historical_entry, try_read_parse from .solar_types import DataName from .util import Periodical, log -class CommunicationError(BTLEDisconnectError, SerialException): +class CommunicationError(BTLEDisconnectError, SerialException, IOError): pass @@ -34,7 +32,7 @@ def main(): while True: try: log("Connecting...") - with BTLEUart(MAC, timeout=5) as dev: + with get_interface() as dev: log("Connected.") # write(dev, construct_request(0, 32)) diff --git a/srnemqtt/config.py b/srnemqtt/config.py index 2b680a7..c9c95d4 100644 --- a/srnemqtt/config.py +++ b/srnemqtt/config.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- import importlib import os -from io import RawIOBase from time import sleep from typing import Any, Dict, List, Optional, Type import yaml +from srnemqtt.interfaces import BaseInterface + from .consumers import BaseConsumer @@ -53,21 +54,28 @@ def get_consumers(conf: Optional[Dict[str, Any]] = None) -> List[BaseConsumer]: return consumers -def get_producers(conf: Optional[Dict[str, Any]] = None) -> List[RawIOBase]: - raise NotImplementedError +def _get_interface(name: str) -> Type[BaseInterface]: + mod_name, cls_name = name.rsplit(".", 1) + + mod = importlib.import_module(f".consumers.{mod_name}", package=__package__) + + res = getattr(mod, cls_name) + assert issubclass(res, BaseConsumer) + + return res + + +def get_interface(conf: Optional[Dict[str, Any]] = None) -> BaseInterface: if conf is None: conf = get_config() - consumers = [] - for name, consumer_config in conf["consumers"].items(): - # print(name, consumer_config) - mod = get_consumer(name) - if mod: - # print(mod) - consumers.append(mod(consumer_config)) + name = conf["interface"]["name"] + params = conf["interface"].get("params", {}) - write_config(conf) - return consumers + mod = _get_interface(name) + assert mod + + return mod(**params) if __name__ == "__main__": diff --git a/srnemqtt/interfaces/feasycom.py b/srnemqtt/interfaces/feasycom.py index c3c0859..82a1172 100644 --- a/srnemqtt/interfaces/feasycom.py +++ b/srnemqtt/interfaces/feasycom.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- -# from ..lib.feasycom_ble import BTLEUart -from . import BaseSource +from ..lib.feasycom_ble import BTLEUart +from . import BaseInterface -class FeasycomSource(BaseSource): +class FeasycomInterface(BTLEUart, BaseInterface): pass + + +# BTLEUart(mac=MAC, timeout=5) diff --git a/srnemqtt/interfaces/serial.py b/srnemqtt/interfaces/serial.py index 7322a31..bee3ff6 100644 --- a/srnemqtt/interfaces/serial.py +++ b/srnemqtt/interfaces/serial.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# import serial +import serial -from . import BaseSource +from . import BaseInterface -class SerialSource(BaseSource): +class SerialInterface(serial.Serial, BaseInterface): pass -- 2.40.1 From 3a8fd634c5e2f6d4bede09119c327ccb6a6d9b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Sat, 8 Apr 2023 00:22:14 +0200 Subject: [PATCH 5/6] Fix interface import --- .pre-commit-config.yaml | 13 ++++++++----- config-example.yaml | 2 +- requirements.txt | 2 ++ srnemqtt/config.py | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4b6d51..b615718 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -23,7 +23,7 @@ repos: - id: detect-private-key - repo: https://github.com/editorconfig-checker/editorconfig-checker.python - rev: 2.3.54 + rev: 2.7.1 hooks: - id: editorconfig-checker args: @@ -35,16 +35,19 @@ repos: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910-1 + rev: v1.2.0 hooks: - id: mypy + args: + - "--install-types" + - "--non-interactive" - repo: https://github.com/psf/black - rev: 21.10b0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.9.3 + rev: 5.12.0 hooks: - id: isort diff --git a/config-example.yaml b/config-example.yaml index cc72de9..7aca723 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -3,7 +3,7 @@ consumers: interface: name: serial.SerialInterface params: - device: /dev/ttyUSB0 + port: /dev/ttyUSB0 baudrate: 9600 timeout: 2 # name: feasycom.FeasycomInterface diff --git a/requirements.txt b/requirements.txt index ea50cd4..5a919f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ bluepy libscrc paho-mqtt pyserial + +types-PyYAML diff --git a/srnemqtt/config.py b/srnemqtt/config.py index c9c95d4..4b3a4c1 100644 --- a/srnemqtt/config.py +++ b/srnemqtt/config.py @@ -57,10 +57,10 @@ def get_consumers(conf: Optional[Dict[str, Any]] = None) -> List[BaseConsumer]: def _get_interface(name: str) -> Type[BaseInterface]: mod_name, cls_name = name.rsplit(".", 1) - mod = importlib.import_module(f".consumers.{mod_name}", package=__package__) + mod = importlib.import_module(f".interfaces.{mod_name}", package=__package__) res = getattr(mod, cls_name) - assert issubclass(res, BaseConsumer) + assert issubclass(res, BaseInterface) return res -- 2.40.1 From 140acba1f5fc4c9fd759a1ddc5bac674fae8cc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Sat, 8 Apr 2023 00:33:02 +0200 Subject: [PATCH 6/6] Update connector doc --- Connector.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Connector.md b/Connector.md index a880051..38562cb 100644 --- a/Connector.md +++ b/Connector.md @@ -1,7 +1,8 @@ # Connector -The connector is a RJ12 (6P6C, phone connector with all 6 positions populated) -The interface uses RS-232 levels (±15V) +The connector is a RJ12 (6P6C, phone connector with all 6 positions populated). +The interface uses RS-232 levels (±5V), make sure your adaptor can handle this! (Some adaptors require higher voltages). +TODO: Triple check RS-232 voltages. ## Pinout -- 2.40.1