isolate Modbus fix
This commit is contained in:
@@ -64,10 +64,7 @@ COPY --chmod=0700 entrypoint.sh /root/entrypoint.sh
|
|||||||
COPY config .
|
COPY config .
|
||||||
COPY src .
|
COPY src .
|
||||||
RUN date > /build-date.txt
|
RUN date > /build-date.txt
|
||||||
EXPOSE 5005 8127 10000
|
EXPOSE 5005
|
||||||
|
|
||||||
# HEALTHCHECK --interval=10s --timeout=3s \
|
|
||||||
# CMD wget --no-verbose --tries=1 --spider http://localhost:8127/-/healthy || exit 1
|
|
||||||
|
|
||||||
# command to run on container start
|
# command to run on container start
|
||||||
ENTRYPOINT ["/root/entrypoint.sh"]
|
ENTRYPOINT ["/root/entrypoint.sh"]
|
||||||
|
|||||||
@@ -17,5 +17,6 @@ if [ "$environment" = "production" ] ; then \
|
|||||||
-name od -o \
|
-name od -o \
|
||||||
-name strings -o \
|
-name strings -o \
|
||||||
-name su -o \
|
-name su -o \
|
||||||
|
-name wget -o \
|
||||||
\) -delete \
|
\) -delete \
|
||||||
; fi
|
; fi
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
aiomqtt==2.0.1
|
aiomqtt==2.0.1
|
||||||
schema==0.7.5
|
schema==0.7.5
|
||||||
aiocron==1.8
|
aiocron==1.8
|
||||||
aiohttp==3.9.5
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
import time
|
|
||||||
from asyncio import StreamReader, StreamWriter
|
from asyncio import StreamReader, StreamWriter
|
||||||
from messages import hex_dump_memory
|
from messages import hex_dump_memory
|
||||||
from typing import Self
|
from typing import Self
|
||||||
@@ -18,8 +17,6 @@ class AsyncStream():
|
|||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.r_addr = ''
|
self.r_addr = ''
|
||||||
self.l_addr = ''
|
self.l_addr = ''
|
||||||
self.proc_start = None # start processing start timestamp
|
|
||||||
self.proc_max = 0
|
|
||||||
|
|
||||||
async def server_loop(self, addr: str) -> None:
|
async def server_loop(self, addr: str) -> None:
|
||||||
'''Loop for receiving messages from the inverter (server-side)'''
|
'''Loop for receiving messages from the inverter (server-side)'''
|
||||||
@@ -64,14 +61,8 @@ class AsyncStream():
|
|||||||
"""Async loop handler for precessing all received messages"""
|
"""Async loop handler for precessing all received messages"""
|
||||||
self.r_addr = self.writer.get_extra_info('peername')
|
self.r_addr = self.writer.get_extra_info('peername')
|
||||||
self.l_addr = self.writer.get_extra_info('sockname')
|
self.l_addr = self.writer.get_extra_info('sockname')
|
||||||
self.proc_start = time.time()
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
proc = time.time() - self.proc_start
|
|
||||||
if proc > self.proc_max:
|
|
||||||
self.proc_max = proc
|
|
||||||
self.proc_start = None
|
|
||||||
|
|
||||||
await self.__async_read()
|
await self.__async_read()
|
||||||
|
|
||||||
if self.unique_id:
|
if self.unique_id:
|
||||||
@@ -126,15 +117,6 @@ class AsyncStream():
|
|||||||
logger.debug(f'AsyncStream.close() l{self.l_addr} | r{self.r_addr}')
|
logger.debug(f'AsyncStream.close() l{self.l_addr} | r{self.r_addr}')
|
||||||
self.writer.close()
|
self.writer.close()
|
||||||
|
|
||||||
def healthy(self) -> bool:
|
|
||||||
elapsed = 0
|
|
||||||
if self.proc_start is not None:
|
|
||||||
elapsed = time.time() - self.proc_start
|
|
||||||
logging.debug('async_stream healthy() elapsed: '
|
|
||||||
f'{round(1000*elapsed)}ms'
|
|
||||||
f' max:{round(1000*self.proc_max)}ms')
|
|
||||||
return elapsed < 5
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Our private methods
|
Our private methods
|
||||||
'''
|
'''
|
||||||
@@ -142,7 +124,6 @@ class AsyncStream():
|
|||||||
"""Async read handler to read received data from TCP stream"""
|
"""Async read handler to read received data from TCP stream"""
|
||||||
data = await self.reader.read(4096)
|
data = await self.reader.read(4096)
|
||||||
if data:
|
if data:
|
||||||
self.proc_start = time.time()
|
|
||||||
self._recv_buffer += data
|
self._recv_buffer += data
|
||||||
self.read() # call read in parent class
|
self.read() # call read in parent class
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import shutil
|
import shutil
|
||||||
import tomllib
|
import tomllib
|
||||||
import logging
|
import logging
|
||||||
from typing import Tuple
|
|
||||||
from schema import Schema, And, Or, Use, Optional
|
from schema import Schema, And, Or, Use, Optional
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ class Config():
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def class_init(cls) -> None | str: # pragma: no cover
|
def class_init(cls): # pragma: no cover
|
||||||
try:
|
try:
|
||||||
# make the default config transparaent by copying it
|
# make the default config transparaent by copying it
|
||||||
# in the config.example file
|
# in the config.example file
|
||||||
@@ -95,12 +94,11 @@ class Config():
|
|||||||
"config/config.example.toml")
|
"config/config.example.toml")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return cls.read()
|
cls.read()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _read_config_file(cls) -> Tuple[dict, None | str]: # pragma: no cover
|
def _read_config_file(cls) -> dict: # pragma: no cover
|
||||||
usr_config = {}
|
usr_config = {}
|
||||||
err = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open("config/config.toml", "rb") as f:
|
with open("config/config.toml", "rb") as f:
|
||||||
@@ -112,7 +110,7 @@ class Config():
|
|||||||
'\n To create the missing config.toml file, '
|
'\n To create the missing config.toml file, '
|
||||||
'you can rename the template config.example.toml\n'
|
'you can rename the template config.example.toml\n'
|
||||||
' and customize it for your scenario.\n')
|
' and customize it for your scenario.\n')
|
||||||
return usr_config, err
|
return usr_config
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def read(cls, path='') -> None | str:
|
def read(cls, path='') -> None | str:
|
||||||
@@ -131,7 +129,7 @@ class Config():
|
|||||||
|
|
||||||
# overwrite the default values, with values from
|
# overwrite the default values, with values from
|
||||||
# the config.toml file
|
# the config.toml file
|
||||||
usr_config, err = cls._read_config_file()
|
usr_config = cls._read_config_file()
|
||||||
|
|
||||||
# merge the default and the user config
|
# merge the default and the user config
|
||||||
config = def_config.copy()
|
config = def_config.copy()
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ class ConnectionG3(AsyncStream, Talent):
|
|||||||
async def async_publ_mqtt(self) -> None:
|
async def async_publ_mqtt(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def healthy(self) -> bool:
|
|
||||||
logger.debug('ConnectionG3 healthy()')
|
|
||||||
return AsyncStream.healthy(self)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Our private methods
|
Our private methods
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ class ConnectionG3P(AsyncStream, SolarmanV5):
|
|||||||
async def async_publ_mqtt(self) -> None:
|
async def async_publ_mqtt(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def healthy(self) -> bool:
|
|
||||||
logger.debug('ConnectionG3P healthy()')
|
|
||||||
return AsyncStream.healthy(self)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Our private methods
|
Our private methods
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import asyncio
|
|||||||
import signal
|
import signal
|
||||||
import os
|
import os
|
||||||
from asyncio import StreamReader, StreamWriter
|
from asyncio import StreamReader, StreamWriter
|
||||||
from aiohttp import web
|
|
||||||
from logging import config # noqa F401
|
from logging import config # noqa F401
|
||||||
from messages import Message
|
from messages import Message
|
||||||
from inverter import Inverter
|
from inverter import Inverter
|
||||||
@@ -12,51 +11,6 @@ from gen3plus.inverter_g3p import InverterG3P
|
|||||||
from scheduler import Schedule
|
from scheduler import Schedule
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
routes = web.RouteTableDef()
|
|
||||||
proxy_is_up = False
|
|
||||||
|
|
||||||
|
|
||||||
@routes.get('/')
|
|
||||||
async def hello(request):
|
|
||||||
return web.Response(text="Hello, world")
|
|
||||||
|
|
||||||
|
|
||||||
@routes.get('/-/ready')
|
|
||||||
async def ready(request):
|
|
||||||
if proxy_is_up:
|
|
||||||
status = 200
|
|
||||||
text = 'Is ready'
|
|
||||||
else:
|
|
||||||
status = 503
|
|
||||||
text = 'Not ready'
|
|
||||||
return web.Response(status=status, text=text)
|
|
||||||
|
|
||||||
|
|
||||||
@routes.get('/-/healthy')
|
|
||||||
async def healthy(request):
|
|
||||||
|
|
||||||
if proxy_is_up:
|
|
||||||
# logging.info('web request healthy()')
|
|
||||||
for stream in Message:
|
|
||||||
try:
|
|
||||||
res = stream.healthy()
|
|
||||||
if not res:
|
|
||||||
return web.Response(status=503, text="I have a problem")
|
|
||||||
except Exception as err:
|
|
||||||
logging.info(f'Exception:{err}')
|
|
||||||
|
|
||||||
return web.Response(status=200, text="I'm fine")
|
|
||||||
|
|
||||||
|
|
||||||
async def webserver(runner, addr, port):
|
|
||||||
await runner.setup()
|
|
||||||
site = web.TCPSite(runner, addr, port)
|
|
||||||
await site.start()
|
|
||||||
logging.info(f'HTTP server listen on port: {port}')
|
|
||||||
|
|
||||||
while True:
|
|
||||||
await asyncio.sleep(3600) # sleep forever
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_client(reader: StreamReader, writer: StreamWriter):
|
async def handle_client(reader: StreamReader, writer: StreamWriter):
|
||||||
'''Handles a new incoming connection and starts an async loop'''
|
'''Handles a new incoming connection and starts an async loop'''
|
||||||
@@ -72,12 +26,10 @@ async def handle_client_v2(reader: StreamReader, writer: StreamWriter):
|
|||||||
await InverterG3P(reader, writer, addr).server_loop(addr)
|
await InverterG3P(reader, writer, addr).server_loop(addr)
|
||||||
|
|
||||||
|
|
||||||
async def handle_shutdown(loop, runner):
|
async def handle_shutdown(loop):
|
||||||
'''Close all TCP connections and stop the event loop'''
|
'''Close all TCP connections and stop the event loop'''
|
||||||
|
|
||||||
logging.info('Shutdown due to SIGTERM')
|
logging.info('Shutdown due to SIGTERM')
|
||||||
await runner.cleanup()
|
|
||||||
logging.info('HTTP server stopped')
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# first, disc all open TCP connections gracefully
|
# first, disc all open TCP connections gracefully
|
||||||
@@ -135,22 +87,15 @@ if __name__ == "__main__":
|
|||||||
logging.getLogger('tracer').setLevel(log_level)
|
logging.getLogger('tracer').setLevel(log_level)
|
||||||
# logging.getLogger('mqtt').setLevel(log_level)
|
# logging.getLogger('mqtt').setLevel(log_level)
|
||||||
|
|
||||||
|
# read config file
|
||||||
|
Config.class_init()
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
# read config file
|
|
||||||
ConfigErr = Config.class_init()
|
|
||||||
logging.debug(f'ConfigErr: {ConfigErr}')
|
|
||||||
Inverter.class_init()
|
Inverter.class_init()
|
||||||
Schedule.start()
|
Schedule.start()
|
||||||
|
|
||||||
#
|
|
||||||
# Setup webserver application and runner
|
|
||||||
#
|
|
||||||
app = web.Application()
|
|
||||||
app.add_routes(routes)
|
|
||||||
runner = web.AppRunner(app)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Register some UNIX Signal handler for a gracefully server shutdown
|
# Register some UNIX Signal handler for a gracefully server shutdown
|
||||||
# on Docker restart and stop
|
# on Docker restart and stop
|
||||||
@@ -158,7 +103,7 @@ if __name__ == "__main__":
|
|||||||
for signame in ('SIGINT', 'SIGTERM'):
|
for signame in ('SIGINT', 'SIGTERM'):
|
||||||
loop.add_signal_handler(getattr(signal, signame),
|
loop.add_signal_handler(getattr(signal, signame),
|
||||||
lambda loop=loop: asyncio.create_task(
|
lambda loop=loop: asyncio.create_task(
|
||||||
handle_shutdown(loop, runner)))
|
handle_shutdown(loop)))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Create tasks for our listening servers. These must be tasks! If we call
|
# Create tasks for our listening servers. These must be tasks! If we call
|
||||||
@@ -167,16 +112,12 @@ if __name__ == "__main__":
|
|||||||
#
|
#
|
||||||
loop.create_task(asyncio.start_server(handle_client, '0.0.0.0', 5005))
|
loop.create_task(asyncio.start_server(handle_client, '0.0.0.0', 5005))
|
||||||
loop.create_task(asyncio.start_server(handle_client_v2, '0.0.0.0', 10000))
|
loop.create_task(asyncio.start_server(handle_client_v2, '0.0.0.0', 10000))
|
||||||
loop.create_task(webserver(runner, '0.0.0.0', 8127))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if ConfigErr is None:
|
|
||||||
proxy_is_up = True
|
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
proxy_is_up = False
|
|
||||||
Inverter.class_close(loop)
|
Inverter.class_close(loop)
|
||||||
logging.info('Close event loop')
|
logging.info('Close event loop')
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import tomllib
|
import tomllib
|
||||||
from schema import SchemaMissingKeyError
|
from schema import SchemaMissingKeyError
|
||||||
from app.src.config import Config
|
from app.src.config import Config
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
class TstConfig(Config):
|
class TstConfig(Config):
|
||||||
|
|
||||||
@@ -11,8 +10,8 @@ class TstConfig(Config):
|
|||||||
cls.config = cnf
|
cls.config = cnf
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _read_config_file(cls) -> Tuple[dict, str| None]:
|
def _read_config_file(cls) -> dict:
|
||||||
return cls.config, None
|
return cls.config
|
||||||
|
|
||||||
|
|
||||||
def test_empty_config():
|
def test_empty_config():
|
||||||
|
|||||||
@@ -77,15 +77,10 @@ services:
|
|||||||
- ${DNS2:-4.4.4.4}
|
- ${DNS2:-4.4.4.4}
|
||||||
ports:
|
ports:
|
||||||
- 5005:5005
|
- 5005:5005
|
||||||
- 8127:8127
|
|
||||||
- 10000:10000
|
- 10000:10000
|
||||||
volumes:
|
volumes:
|
||||||
- ${PROJECT_DIR:-./}tsun-proxy/log:/home/tsun-proxy/log
|
- ${PROJECT_DIR:-./}tsun-proxy/log:/home/tsun-proxy/log
|
||||||
- ${PROJECT_DIR:-./}tsun-proxy/config:/home/tsun-proxy/config
|
- ${PROJECT_DIR:-./}tsun-proxy/config:/home/tsun-proxy/config
|
||||||
healthcheck:
|
|
||||||
test: wget --no-verbose --tries=1 --spider http://localhost:8127/-/healthy || exit 1
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
networks:
|
networks:
|
||||||
- outside
|
- outside
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user