diff --git a/Dockerfile b/Dockerfile index 53df130..6462984 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,15 @@ FROM python:3.10-slim-buster +RUN mkdir /app +COPY . /app +COPY pyproject.toml /app +WORKDIR /app +ENV PYTHONPATH=${PYTHONPATH}:${PWD} +RUN pip3 install poetry +RUN poetry config virtualenvs.create false +RUN poetry install --only main WORKDIR /app - -COPY requirements.txt requirements.txt -RUN pip3 install -r requirements.txt COPY . . diff --git a/docker-compose.yml.example b/docker-compose.yml.example index fae64ee..fb5033b 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -5,15 +5,12 @@ services: container_name: huawei-solar restart: unless-stopped image: huawei-solar-rtu:latest - user: root - devices: - - /dev/ttyUSB0:/dev/ttyUSB0 environment: - - INVERTER_PORT=/dev/ttyUSB0 - - MQTT_HOST=192.168.43.102 - - BROKER_PORT=1883 - - USE_CREDENTIALS=NO - - USER_NAME=none - - PASSWORD=none + - INVERTER_HOST=192.168.200.100 + - MQTT_HOST=192.168.1.100 + - USE_CREDENTIALS=YES + - USER_NAME=simon + - PASSWORD=bajsa123 + - LOGLEVEL=DEBUG - MQTT_TOPIC=raspberryTopic - DATA_MODE=INVERTER # INVERTER or OFFLINE diff --git a/pyproject.toml b/pyproject.toml index 2044233..15c4f9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ authors = ["Simon Milvert "] python = "^3.9" huawei-solar = "^2.2.4" paho-mqtt = "^1.6.1" +python-dotenv = "^1.0.0" [tool.poetry.dev-dependencies] pytest = "^7.2.1" diff --git a/src/__init__.py b/src/__init__.py index e69de29..13c4a9f 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -0,0 +1,14 @@ +import logging +import os + +from dotenv import load_dotenv + +load_dotenv() + +FORMAT = ( + "%(asctime)-15s %(threadName)-15s " + "%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s" +) + +LOGLEVEL = os.environ.get("LOGLEVEL", "INFO").upper() +logging.basicConfig(level=LOGLEVEL, format=FORMAT) diff --git a/src/inverter.py b/src/inverter.py index f569a6b..af56c30 100644 --- a/src/inverter.py +++ b/src/inverter.py @@ -1,8 +1,11 @@ +import logging + from src.data import InverterData -from src.log import log_info + +LOGGER = logging.getLogger(__name__) async def get_data(client, register, slave_id): result = await client.get(register, slave_id) - log_info(f"{register}: {result.value}") + LOGGER.debug(f"{register}: {result.value}") return InverterData(value=result.value, unit=result.unit, name=register) diff --git a/src/inverter_dummy.py b/src/inverter_dummy.py index 156722b..e3a0e58 100644 --- a/src/inverter_dummy.py +++ b/src/inverter_dummy.py @@ -1,8 +1,9 @@ +import logging import random from src.data import InverterData, ReadRegister -from src.log import log_info +LOGGER = logging.getLogger(__name__) POSSIBLE_SAMPLE_UNITS = [ "EXAMPLE_UNIT_A", "EXAMPLE_UNIT_B", @@ -14,7 +15,7 @@ POSSIBLE_SAMPLE_UNITS = [ async def get_dummy_data() -> InverterData: - log_info("Generate dummy data") + LOGGER.info("Generate dummy data") value = random.randint(0, 100) name = random.choice(ReadRegister.reg_to_read_measured) unit = random.choice(POSSIBLE_SAMPLE_UNITS) diff --git a/src/log.py b/src/log.py deleted file mode 100644 index 14702ed..0000000 --- a/src/log.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging - -FORMAT = ( - "%(asctime)-15s %(threadName)-15s " - "%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s" -) -logging.basicConfig(format=FORMAT) -log = logging.getLogger() -log.setLevel(logging.INFO) - - -def log_info(message): - log.info(message) diff --git a/src/main.py b/src/main.py index b96df76..8751f8b 100644 --- a/src/main.py +++ b/src/main.py @@ -1,59 +1,63 @@ import asyncio import json +import logging import os +import paho.mqtt.client from huawei_solar import AsyncHuaweiSolar from huawei_solar import register_values as rv +from src import mqtt from src.data import InverterData, ReadRegister from src.inverter import get_data from src.inverter_dummy import get_dummy_data -from src.log import log_info from src.mqtt import connect -DATA_MODE = os.getenv("DATA_MODE", "dummy") +DATA_MODE = os.getenv("DATA_MODE", "INVERTER") MQTT_TOPIC = os.getenv("MQTT_TOPIC", "inverter") +FORCE_GET = os.getenv("FORCE_GET", False) + -REQUEST_INTERVAL = 60 slave_id = os.getenv("INVERTER_ID", 1) port = os.getenv("INVERTER_PORT", 502) -host = os.getenv("INVERTER_HOST", "10.0.2.20") +host = os.getenv("INVERTER_HOST", "192.168.200.100") + +LOGGER = logging.getLogger(__name__) +REQUEST_INTERVAL = 60 -async def get_inverter_client() -> (InverterData, str): +async def get_inverter_client() -> AsyncHuaweiSolar: inverter_client = await AsyncHuaweiSolar.create(host, port, slave_id) - log_info("-- INVERTER -- Connected to Inverter") - status = await inverter_client.get( - ReadRegister.inverter_reg_status, slave_id - ) - - log_info(f"-- INVERTER -- Status: {status}") - return inverter_client, status + LOGGER.info("-- INVERTER -- Connected to Inverter") + return inverter_client async def main(): - inverter_client, status = await get_inverter_client() + inverter_client = await get_inverter_client() count = 0 while True: - if status.value == rv.DEVICE_STATUS_DEFINITIONS.get( - 0x0200 + status = await inverter_client.get( + ReadRegister.inverter_reg_status, slave_id + ) + + LOGGER.info(f"-- INVERTER -- Status: {status}") + + if ( + status.value == rv.DEVICE_STATUS_DEFINITIONS.get(0x0200) + or FORCE_GET ): # Inverter is active, Get data - # Get measured values + LOGGER.info("-- INVERTER -- Send measured values") for register in ReadRegister.reg_to_read_measured: - result = await get_inverter_data( - inverter_client, register, slave_id - ) + result = await get_inverter_data(inverter_client, register) send_data( mqtt_client, format_data_to_serialized_json(result), f"measure/{result.name}", ) - # Get calculated values + LOGGER.info("-- INVERTER -- Send calculated values") for register in ReadRegister.reg_to_read_calculated: - result = await get_inverter_data( - inverter_client, register, slave_id - ) + result = await get_inverter_data(inverter_client, register) send_data( mqtt_client, format_data_to_serialized_json(result), @@ -61,24 +65,30 @@ async def main(): ) if count == 5: + LOGGER.info("-- INVERTER -- Send status/alarm") for register in ReadRegister.reg_to_read_status: - result = await get_inverter_data( - inverter_client, register, slave_id - ) + result = await get_inverter_data(inverter_client, register) send_data( mqtt_client, format_data_to_serialized_json(result), f"status/{result.name}", ) count = 0 + else: + LOGGER.debug("-- INVERTER -- Not Active, No need to read values") + count += 1 + + LOGGER.info(f"Sleeping {REQUEST_INTERVAL}") await asyncio.sleep(REQUEST_INTERVAL) -def send_data(client, storable_data, topic=""): +def send_data( + client: paho.mqtt.client, storable_data: str, topic: str = "" +) -> None: try: - log_info( - f"Sending data\n topic: {MQTT_TOPIC + '/' + topic} \n" + LOGGER.debug( + f"Sending data\ntopic: {MQTT_TOPIC + '/' + topic} \n" f"msg: {storable_data}" ) client.publish( @@ -88,18 +98,18 @@ def send_data(client, storable_data, topic=""): retain=False, ) except Exception: - log_info(f"ERROR PUBLISHING DATA TO MQTT BROKER. \n{Exception}") + LOGGER.exception("ERROR PUBLISHING DATA TO MQTT BROKER.") def format_data_to_serialized_json(info: InverterData) -> str: data = {"value": info.value, "name": info.name, "unit": info.unit} json_obj = json.dumps(data) - log_info(f"json_obj: {json_obj}, type: {type(json_obj)}") + LOGGER.debug(f"json_obj: {json_obj}, type: {type(json_obj)}") return json_obj async def get_inverter_data( - inverter_client: AsyncHuaweiSolar, register: str, slave_id: int + inverter_client: AsyncHuaweiSolar, register: str ) -> InverterData: if DATA_MODE == "INVERTER": data = await get_data(inverter_client, register, slave_id) @@ -109,16 +119,19 @@ async def get_inverter_data( return data -log_info("| === START === |") -mqtt_client = connect() +LOGGER.info("| === START === |") +try: + mqtt_client = connect() +except TimeoutError: + LOGGER.exception("---- CANT CONNECT ----") + loop = asyncio.new_event_loop() -asyncio.set_event_loop(loop) try: - asyncio.ensure_future(main()) + asyncio.ensure_future(main(), loop=loop) loop.run_forever() except KeyboardInterrupt: pass finally: - log_info("| === END === |") + LOGGER.info("| === END === |") loop.close() diff --git a/src/mqtt.py b/src/mqtt.py index aa80ca8..1108f12 100644 --- a/src/mqtt.py +++ b/src/mqtt.py @@ -1,11 +1,12 @@ +import logging import os import time import paho.mqtt.client as mqtt -from src.log import log_info +LOGGER = logging.getLogger(__name__) -mqtt_host = os.getenv("MQTT_HOST", "10.0.0.3") +mqtt_host = os.getenv("MQTT_HOST", "mqtt") broker_port = os.getenv("BROKER_PORT", "1883") have_credentials = os.getenv("USE_CREDENTIALS", "NO") user_name = os.getenv("USER_NAME", "") @@ -15,9 +16,9 @@ password = os.getenv("PASSWORD", "") def on_connect(client, userdata, flags, rc): if rc == 0: client.connected_flag = True - log_info("MQTT OK!") + LOGGER.info("MQTT OK!") else: - log_info(f"MQTT FAILURE. ERROR CODE: {rc}") + LOGGER.error(f"MQTT FAILURE. ERROR CODE: {rc}") def setup_mqtt(): @@ -25,15 +26,15 @@ def setup_mqtt(): mqtt_client = mqtt.Client() mqtt_client.on_connect = on_connect mqtt_client.loop_start() - log_info("Connecting to MQTT broker: " + mqtt_host) - log_info("Port: " + broker_port) - if have_credentials == "YES": + LOGGER.info("Connecting to MQTT broker: " + mqtt_host) + LOGGER.info("Port: " + broker_port) + if have_credentials.upper() == "YES": mqtt_client.username_pw_set(username=user_name, password=password) mqtt_client.connect(mqtt_host, int(broker_port), 60) while not mqtt_client.connected_flag: - log_info("...") + LOGGER.info("...") time.sleep(1) - log_info("START MODBUS...") + LOGGER.info("CONNECTED") return mqtt_client