Compare commits
85 Commits
v0.10.0-pr
...
dev-0.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9420a137dc | ||
|
|
4abc308445 | ||
|
|
f527dfbbec | ||
|
|
a79b36c361 | ||
|
|
1357b0f665 | ||
|
|
d9b7b9e858 | ||
|
|
7c48ee4065 | ||
|
|
4e89abd2c9 | ||
|
|
f304aa009e | ||
|
|
9e218fdf41 | ||
|
|
18f6332784 | ||
|
|
26aebbcab8 | ||
|
|
a9c7ea386e | ||
|
|
6332976c4a | ||
|
|
cc233dcb17 | ||
|
|
9a9cf79aac | ||
|
|
3ce29d4a96 | ||
|
|
a09d489c94 | ||
|
|
2d4679a361 | ||
|
|
9ff1453922 | ||
|
|
5b36efc5e9 | ||
|
|
c71994c839 | ||
|
|
7d058e74fe | ||
|
|
373916bead | ||
|
|
f4b434cfef | ||
|
|
d14cbe87a2 | ||
|
|
8aa1ef59ce | ||
|
|
3d55ac57a8 | ||
|
|
8088e6ab3c | ||
|
|
4372e49a1e | ||
|
|
da832232bb | ||
|
|
e0568291f6 | ||
|
|
f5e7aa4292 | ||
|
|
5e360e1139 | ||
|
|
94f7f5faa2 | ||
|
|
4600fc9577 | ||
|
|
fa7bfe9e16 | ||
|
|
3cebab40c8 | ||
|
|
4649beb075 | ||
|
|
9138affdb9 | ||
|
|
80183598ca | ||
|
|
b688d04836 | ||
|
|
377c09bc66 | ||
|
|
abb9e7c280 | ||
|
|
d78e32dd12 | ||
|
|
30a6f75430 | ||
|
|
e22ad78dcd | ||
|
|
453d8b2aa2 | ||
|
|
f9b02f3486 | ||
|
|
b053c7e576 | ||
|
|
10346e888f | ||
|
|
f629246dbd | ||
|
|
dbff66affd | ||
|
|
ac534c20ed | ||
|
|
ff3ed83b49 | ||
|
|
ae94cd62fc | ||
|
|
a16a19cc2c | ||
|
|
dd351176bd | ||
|
|
cc8674d108 | ||
|
|
d7767cb5ea | ||
|
|
1e3bb31ef8 | ||
|
|
d6a44d9173 | ||
|
|
43a2ef5712 | ||
|
|
3209ebabde | ||
|
|
aac6cfd629 | ||
|
|
e8d32b45a5 | ||
|
|
06b63f554d | ||
|
|
53f6a5447d | ||
|
|
d6093e6b11 | ||
|
|
c8113e2f60 | ||
|
|
57d6785f15 | ||
|
|
ff8adb5632 | ||
|
|
1deab4be6a | ||
|
|
730229cfb0 | ||
|
|
7b9550773d | ||
|
|
3bc2b262b5 | ||
|
|
37c2246132 | ||
|
|
d0bd599420 | ||
|
|
661f699444 | ||
|
|
a499c5e6b0 | ||
|
|
9985917ad2 | ||
|
|
851bd54d8f | ||
|
|
81d551e47f | ||
|
|
63547bb51f | ||
|
|
6eebd0c852 |
@@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
|
||||||
- GEN3PLUS: add client_mode connection on port 8899 [#117](https://github.com/s-allius/tsun-gen3-proxy/issues/117)
|
|
||||||
|
|
||||||
## [0.9.0] - 2024-07-01
|
## [0.9.0] - 2024-07-01
|
||||||
|
|
||||||
- fix exception in MODBUS timeout callback
|
- fix exception in MODBUS timeout callback
|
||||||
|
|||||||
11
app/build.sh
11
app/build.sh
@@ -21,11 +21,11 @@ IMAGE=tsun-gen3-proxy
|
|||||||
if [[ $1 == debug ]] || [[ $1 == dev ]] ;then
|
if [[ $1 == debug ]] || [[ $1 == dev ]] ;then
|
||||||
IMAGE=docker.io/sallius/${IMAGE}
|
IMAGE=docker.io/sallius/${IMAGE}
|
||||||
VERSION=${VERSION}-$1
|
VERSION=${VERSION}-$1
|
||||||
elif [[ $1 == rc ]] || [[ $1 == rel ]] || [[ $1 == preview ]] ;then
|
elif [[ $1 == rc ]] || [[ $1 == rel ]];then
|
||||||
IMAGE=ghcr.io/s-allius/${IMAGE}
|
IMAGE=ghcr.io/s-allius/${IMAGE}
|
||||||
else
|
else
|
||||||
echo argument missing!
|
echo argument missing!
|
||||||
echo try: $0 '[debug|dev|preview|rc|rel]'
|
echo try: $0 '[debug|dev|rc|rel]'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -35,13 +35,6 @@ docker build --build-arg "VERSION=${VERSION}" --build-arg environment=dev --buil
|
|||||||
elif [[ $1 == dev ]];then
|
elif [[ $1 == dev ]];then
|
||||||
docker build --build-arg "VERSION=${VERSION}" --build-arg environment=production --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.opencontainers.image.version=${VERSION}" --label "org.opencontainers.image.revision=${BRANCH}" -t ${IMAGE}:dev app
|
docker build --build-arg "VERSION=${VERSION}" --build-arg environment=production --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.opencontainers.image.version=${VERSION}" --label "org.opencontainers.image.revision=${BRANCH}" -t ${IMAGE}:dev app
|
||||||
|
|
||||||
elif [[ $1 == preview ]];then
|
|
||||||
docker build --build-arg "VERSION=${VERSION}" --build-arg environment=production --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.opencontainers.image.version=${VERSION}" --label "org.opencontainers.image.revision=${BRANCH}" -t ${IMAGE}:preview -t ${IMAGE}:${VERSION} app
|
|
||||||
echo 'login to ghcr.io'
|
|
||||||
echo $GHCR_TOKEN | docker login ghcr.io -u s-allius --password-stdin
|
|
||||||
docker push -q ghcr.io/s-allius/tsun-gen3-proxy:preview
|
|
||||||
docker push -q ghcr.io/s-allius/tsun-gen3-proxy:${VERSION}
|
|
||||||
|
|
||||||
elif [[ $1 == rc ]];then
|
elif [[ $1 == rc ]];then
|
||||||
docker build --build-arg "VERSION=${VERSION}" --build-arg environment=production --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.opencontainers.image.version=${VERSION}" --label "org.opencontainers.image.revision=${BRANCH}" -t ${IMAGE}:rc -t ${IMAGE}:${VERSION} app
|
docker build --build-arg "VERSION=${VERSION}" --build-arg environment=production --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.opencontainers.image.version=${VERSION}" --label "org.opencontainers.image.revision=${BRANCH}" -t ${IMAGE}:rc -t ${IMAGE}:${VERSION} app
|
||||||
echo 'login to ghcr.io'
|
echo 'login to ghcr.io'
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ inverters.allow_all = true # allow inverters, even if we have no inverter mapp
|
|||||||
monitor_sn = 2000000000 # The "Monitoring SN:" can be found on a sticker enclosed with the inverter
|
monitor_sn = 2000000000 # The "Monitoring SN:" can be found on a sticker enclosed with the inverter
|
||||||
#node_id = '' # Optional, MQTT replacement for inverters serial number
|
#node_id = '' # Optional, MQTT replacement for inverters serial number
|
||||||
#suggested_area = '' # Optional, suggested installation place for home-assistant
|
#suggested_area = '' # Optional, suggested installation place for home-assistant
|
||||||
#client_mode = {host = '192.168.0.1', port = 8899}
|
|
||||||
#pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
#pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||||
#pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
#pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||||
#pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
#pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||||
|
|||||||
@@ -53,11 +53,7 @@ class Config():
|
|||||||
Use(lambda s: s + '/'
|
Use(lambda s: s + '/'
|
||||||
if len(s) > 0 and
|
if len(s) > 0 and
|
||||||
s[-1] != '/' else s)),
|
s[-1] != '/' else s)),
|
||||||
Optional('client_mode'): {
|
|
||||||
'host': Use(str),
|
|
||||||
Optional('port', default=8899):
|
|
||||||
And(Use(int), lambda n: 1024 <= n <= 65535)
|
|
||||||
},
|
|
||||||
Optional('suggested_area', default=""): Use(str),
|
Optional('suggested_area', default=""): Use(str),
|
||||||
Optional('pv1'): {
|
Optional('pv1'): {
|
||||||
Optional('type'): Use(str),
|
Optional('type'): Use(str),
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ class SolarmanV5(Message):
|
|||||||
self.db = InfosG3P()
|
self.db = InfosG3P()
|
||||||
self.time_ofs = 0
|
self.time_ofs = 0
|
||||||
self.forward_at_cmd_resp = False
|
self.forward_at_cmd_resp = False
|
||||||
self.no_forwarding = False
|
|
||||||
'''not allowed to connect to TSUN cloud by connection type'''
|
|
||||||
self.switch = {
|
self.switch = {
|
||||||
|
|
||||||
0x4210: self.msg_data_ind, # real time data
|
0x4210: self.msg_data_ind, # real time data
|
||||||
@@ -145,19 +143,6 @@ class SolarmanV5(Message):
|
|||||||
self.mb_timer.close()
|
self.mb_timer.close()
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
async def send_start_cmd(self, snr: int):
|
|
||||||
self.no_forwarding = True
|
|
||||||
self.snr = snr
|
|
||||||
self.__set_serial_no(snr)
|
|
||||||
|
|
||||||
self.__send_ack_rsp(0x1710, ftype=0)
|
|
||||||
await self.async_write('Send Start Command:')
|
|
||||||
self._send_buffer = bytearray(0)
|
|
||||||
|
|
||||||
self.state = State.up
|
|
||||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x2000, 64, logging.INFO)
|
|
||||||
self.mb_timer.start(self.MB_START_TIMEOUT)
|
|
||||||
|
|
||||||
def __set_serial_no(self, snr: int):
|
def __set_serial_no(self, snr: int):
|
||||||
serial_no = str(snr)
|
serial_no = str(snr)
|
||||||
if self.unique_id == serial_no:
|
if self.unique_id == serial_no:
|
||||||
@@ -213,8 +198,6 @@ class SolarmanV5(Message):
|
|||||||
return 0 # wait 0s before sending a response
|
return 0 # wait 0s before sending a response
|
||||||
|
|
||||||
def forward(self, buffer, buflen) -> None:
|
def forward(self, buffer, buflen) -> None:
|
||||||
if self.no_forwarding:
|
|
||||||
return
|
|
||||||
tsun = Config.get('solarman')
|
tsun = Config.get('solarman')
|
||||||
if tsun['enabled']:
|
if tsun['enabled']:
|
||||||
self._forward_buffer = buffer[:buflen]
|
self._forward_buffer = buffer[:buflen]
|
||||||
@@ -397,11 +380,11 @@ class SolarmanV5(Message):
|
|||||||
def mb_timout_cb(self, exp_cnt):
|
def mb_timout_cb(self, exp_cnt):
|
||||||
self.mb_timer.start(self.MB_REGULAR_TIMEOUT)
|
self.mb_timer.start(self.MB_REGULAR_TIMEOUT)
|
||||||
|
|
||||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x3000, 48, logging.DEBUG)
|
self._send_modbus_cmd(Modbus.READ_REGS, 0x3008, 21, logging.DEBUG)
|
||||||
|
|
||||||
if 0 == (exp_cnt % 30):
|
if 0 == (exp_cnt % 30):
|
||||||
# logging.info("Regular Modbus Status request")
|
# logging.info("Regular Modbus Status request")
|
||||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x2000, 64, logging.DEBUG)
|
self._send_modbus_cmd(Modbus.READ_REGS, 0x2007, 2, logging.DEBUG)
|
||||||
|
|
||||||
def at_cmd_forbidden(self, cmd: str, connection: str) -> bool:
|
def at_cmd_forbidden(self, cmd: str, connection: str) -> bool:
|
||||||
return not cmd.startswith(tuple(self.at_acl[connection]['allow'])) or \
|
return not cmd.startswith(tuple(self.at_acl[connection]['allow'])) or \
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ class Message(metaclass=IterRegistry):
|
|||||||
self._forward_buffer = bytearray(0)
|
self._forward_buffer = bytearray(0)
|
||||||
self.new_data = {}
|
self.new_data = {}
|
||||||
self.state = State.init
|
self.state = State.init
|
||||||
self.shutdown_started = False
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Empty methods, that have to be implemented in any child class which
|
Empty methods, that have to be implemented in any child class which
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ class Modbus():
|
|||||||
__crc_tab = []
|
__crc_tab = []
|
||||||
map = {
|
map = {
|
||||||
0x2007: {'reg': Register.MAX_DESIGNED_POWER, 'fmt': '!H', 'ratio': 1}, # noqa: E501
|
0x2007: {'reg': Register.MAX_DESIGNED_POWER, 'fmt': '!H', 'ratio': 1}, # noqa: E501
|
||||||
0x203e: {'reg': Register.NO_INPUTS, 'fmt': '!H', 'ratio': 1/256}, # noqa: E501
|
# 0x????: {'reg': Register.INVERTER_STATUS, 'fmt': '!H'}, # noqa: E501
|
||||||
|
|
||||||
0x3000: {'reg': Register.INVERTER_STATUS, 'fmt': '!H'}, # noqa: E501
|
|
||||||
0x3008: {'reg': Register.VERSION, 'fmt': '!H', 'eval': "f'V{(result>>12)}.{(result>>8)&0xf}.{(result>>4)&0xf}{result&0xf}'"}, # noqa: E501
|
0x3008: {'reg': Register.VERSION, 'fmt': '!H', 'eval': "f'V{(result>>12)}.{(result>>8)&0xf}.{(result>>4)&0xf}{result&0xf}'"}, # noqa: E501
|
||||||
0x3009: {'reg': Register.GRID_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
|
0x3009: {'reg': Register.GRID_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
|
||||||
0x300a: {'reg': Register.GRID_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
|
0x300a: {'reg': Register.GRID_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import logging
|
|
||||||
import traceback
|
|
||||||
import asyncio
|
|
||||||
from config import Config
|
|
||||||
|
|
||||||
# import gc
|
|
||||||
from gen3plus.inverter_g3p import InverterG3P
|
|
||||||
|
|
||||||
logger = logging.getLogger('conn')
|
|
||||||
|
|
||||||
|
|
||||||
class ModbusConn():
|
|
||||||
def __init__(self, host, port):
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.addr = (host, port)
|
|
||||||
self.stream = None
|
|
||||||
|
|
||||||
async def __aenter__(self) -> 'InverterG3P':
|
|
||||||
'''Establish a client connection to the TSUN cloud'''
|
|
||||||
connection = asyncio.open_connection(self.host, self.port)
|
|
||||||
reader, writer = await connection
|
|
||||||
self.stream = InverterG3P(reader, writer, self.addr)
|
|
||||||
logging.info(f'[{self.stream.node_id}:{self.stream.conn_no}] '
|
|
||||||
f'Connected to {self.addr}')
|
|
||||||
self.stream.inc_counter('Inverter_Cnt')
|
|
||||||
return self.stream
|
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc, tb):
|
|
||||||
self.stream.dec_counter('Inverter_Cnt')
|
|
||||||
|
|
||||||
|
|
||||||
class ModbusTcp():
|
|
||||||
|
|
||||||
def __init__(self, loop) -> None:
|
|
||||||
inverters = Config.get('inverters')
|
|
||||||
# logging.info(f'Inverters: {inverters}')
|
|
||||||
|
|
||||||
for inv in inverters.values():
|
|
||||||
if (type(inv) is dict
|
|
||||||
and 'monitor_sn' in inv
|
|
||||||
and 'client_mode' in inv):
|
|
||||||
client = inv['client_mode']
|
|
||||||
# logging.info(f"SerialNo:{inv['monitor_sn']} host:{client['host']} port:{client['port']}") # noqa: E501
|
|
||||||
loop.create_task(self.modbus_loop(client['host'],
|
|
||||||
client['port'],
|
|
||||||
inv['monitor_sn']))
|
|
||||||
|
|
||||||
async def modbus_loop(self, host, port, snr: int) -> None:
|
|
||||||
'''Loop for receiving messages from the TSUN cloud (client-side)'''
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
async with ModbusConn(host, port) as stream:
|
|
||||||
await stream.send_start_cmd(snr)
|
|
||||||
await stream.loop()
|
|
||||||
logger.info(f'[{stream.node_id}:{stream.conn_no}] '
|
|
||||||
f'Connection closed - Shutdown: '
|
|
||||||
f'{stream.shutdown_started}')
|
|
||||||
if stream.shutdown_started:
|
|
||||||
return
|
|
||||||
|
|
||||||
except (ConnectionRefusedError, TimeoutError) as error:
|
|
||||||
logging.info(f'{error}')
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
logging.error(
|
|
||||||
f"ModbusTcpCreate: Exception for {(host,port)}:\n"
|
|
||||||
f"{traceback.format_exc()}")
|
|
||||||
|
|
||||||
await asyncio.sleep(10)
|
|
||||||
@@ -11,7 +11,6 @@ from gen3.inverter_g3 import InverterG3
|
|||||||
from gen3plus.inverter_g3p import InverterG3P
|
from gen3plus.inverter_g3p import InverterG3P
|
||||||
from scheduler import Schedule
|
from scheduler import Schedule
|
||||||
from config import Config
|
from config import Config
|
||||||
from modbus_tcp import ModbusTcp
|
|
||||||
|
|
||||||
routes = web.RouteTableDef()
|
routes = web.RouteTableDef()
|
||||||
proxy_is_up = False
|
proxy_is_up = False
|
||||||
@@ -95,7 +94,6 @@ async def handle_shutdown(web_task):
|
|||||||
# first, disc all open TCP connections gracefully
|
# first, disc all open TCP connections gracefully
|
||||||
#
|
#
|
||||||
for stream in Message:
|
for stream in Message:
|
||||||
stream.shutdown_started = True
|
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(stream.disc(), 2)
|
await asyncio.wait_for(stream.disc(), 2)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -117,13 +115,6 @@ async def handle_shutdown(web_task):
|
|||||||
web_task.cancel()
|
web_task.cancel()
|
||||||
await web_task
|
await web_task
|
||||||
|
|
||||||
#
|
|
||||||
# now cancel all remaining (pending) tasks
|
|
||||||
#
|
|
||||||
pending = asyncio.all_tasks()
|
|
||||||
for task in pending:
|
|
||||||
task.cancel()
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# at last, start a coro for stopping the loop
|
# at last, start a coro for stopping the loop
|
||||||
#
|
#
|
||||||
@@ -173,7 +164,6 @@ if __name__ == "__main__":
|
|||||||
logging.info(f'ConfigErr: {ConfigErr}')
|
logging.info(f'ConfigErr: {ConfigErr}')
|
||||||
Inverter.class_init()
|
Inverter.class_init()
|
||||||
Schedule.start()
|
Schedule.start()
|
||||||
mb_tcp = ModbusTcp(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
|
||||||
|
|||||||
Reference in New Issue
Block a user