add dcu_power MQTT topic
This commit is contained in:
19
app/src/gen3plus/solarman_v5.py
Normal file → Executable file
19
app/src/gen3plus/solarman_v5.py
Normal file → Executable file
@@ -247,6 +247,7 @@ class SolarmanBase(Message):
|
|||||||
class SolarmanV5(SolarmanBase):
|
class SolarmanV5(SolarmanBase):
|
||||||
AT_CMD = 1
|
AT_CMD = 1
|
||||||
MB_RTU_CMD = 2
|
MB_RTU_CMD = 2
|
||||||
|
DCU_CMD = 5
|
||||||
AT_CMD_RSP = 8
|
AT_CMD_RSP = 8
|
||||||
MB_CLIENT_DATA_UP = 30
|
MB_CLIENT_DATA_UP = 30
|
||||||
'''Data up time in client mode'''
|
'''Data up time in client mode'''
|
||||||
@@ -532,6 +533,24 @@ class SolarmanV5(SolarmanBase):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.ifc.tx_clear()
|
self.ifc.tx_clear()
|
||||||
|
|
||||||
|
def send_dcu_cmd(self, pdu: bytearray):
|
||||||
|
if self.sensor_list != 0x3026:
|
||||||
|
logger.debug(f'[{self.node_id}] DCU CMD not allowed,'
|
||||||
|
f' for sensor: {self.sensor_list:#04x}')
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.state != State.up:
|
||||||
|
logger.warning(f'[{self.node_id}] ignore DCU CMD,'
|
||||||
|
' cause the state is not UP anymore')
|
||||||
|
return
|
||||||
|
self._build_header(0x4510)
|
||||||
|
self.ifc.tx_add(struct.pack('<BHLLL', self.DCU_CMD,
|
||||||
|
self.sensor_list, 0, 0, 0))
|
||||||
|
self.ifc.tx_add(pdu)
|
||||||
|
self._finish_send_msg()
|
||||||
|
self.ifc.tx_log(logging.INFO, f'Send DCU CMD :{self.addr}:')
|
||||||
|
self.ifc.tx_flush()
|
||||||
|
|
||||||
def __forward_msg(self):
|
def __forward_msg(self):
|
||||||
self.forward(self.ifc.rx_peek(), self.header_len+self.data_len+2)
|
self.forward(self.ifc.rx_peek(), self.header_len+self.data_len+2)
|
||||||
|
|
||||||
|
|||||||
21
app/src/mqtt.py
Normal file → Executable file
21
app/src/mqtt.py
Normal file → Executable file
@@ -2,6 +2,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import aiomqtt
|
import aiomqtt
|
||||||
import traceback
|
import traceback
|
||||||
|
import struct
|
||||||
|
|
||||||
from modbus import Modbus
|
from modbus import Modbus
|
||||||
from messages import Message
|
from messages import Message
|
||||||
@@ -32,6 +33,7 @@ class Mqtt(metaclass=Singleton):
|
|||||||
self.ha_status_topic = f"{ha['auto_conf_prefix']}/status"
|
self.ha_status_topic = f"{ha['auto_conf_prefix']}/status"
|
||||||
self.mb_rated_topic = f"{ha['entity_prefix']}/+/rated_load"
|
self.mb_rated_topic = f"{ha['entity_prefix']}/+/rated_load"
|
||||||
self.mb_out_coeff_topic = f"{ha['entity_prefix']}/+/out_coeff"
|
self.mb_out_coeff_topic = f"{ha['entity_prefix']}/+/out_coeff"
|
||||||
|
self.dcu_power_topic = f"{ha['entity_prefix']}/+/dcu_power"
|
||||||
self.mb_reads_topic = f"{ha['entity_prefix']}/+/modbus_read_regs"
|
self.mb_reads_topic = f"{ha['entity_prefix']}/+/modbus_read_regs"
|
||||||
self.mb_inputs_topic = f"{ha['entity_prefix']}/+/modbus_read_inputs"
|
self.mb_inputs_topic = f"{ha['entity_prefix']}/+/modbus_read_inputs"
|
||||||
self.mb_at_cmd_topic = f"{ha['entity_prefix']}/+/at_cmd"
|
self.mb_at_cmd_topic = f"{ha['entity_prefix']}/+/at_cmd"
|
||||||
@@ -85,6 +87,7 @@ class Mqtt(metaclass=Singleton):
|
|||||||
await self.__client.subscribe(self.ha_status_topic)
|
await self.__client.subscribe(self.ha_status_topic)
|
||||||
await self.__client.subscribe(self.mb_rated_topic)
|
await self.__client.subscribe(self.mb_rated_topic)
|
||||||
await self.__client.subscribe(self.mb_out_coeff_topic)
|
await self.__client.subscribe(self.mb_out_coeff_topic)
|
||||||
|
await self.__client.subscribe(self.dcu_power_topic)
|
||||||
await self.__client.subscribe(self.mb_reads_topic)
|
await self.__client.subscribe(self.mb_reads_topic)
|
||||||
await self.__client.subscribe(self.mb_inputs_topic)
|
await self.__client.subscribe(self.mb_inputs_topic)
|
||||||
await self.__client.subscribe(self.mb_at_cmd_topic)
|
await self.__client.subscribe(self.mb_at_cmd_topic)
|
||||||
@@ -148,6 +151,9 @@ class Mqtt(metaclass=Singleton):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if message.topic.matches(self.dcu_power_topic):
|
||||||
|
self.dcu_cmd(message)
|
||||||
|
|
||||||
if message.topic.matches(self.mb_reads_topic):
|
if message.topic.matches(self.mb_reads_topic):
|
||||||
await self.modbus_cmd(message,
|
await self.modbus_cmd(message,
|
||||||
Modbus.READ_REGS, 2)
|
Modbus.READ_REGS, 2)
|
||||||
@@ -194,3 +200,18 @@ class Mqtt(metaclass=Singleton):
|
|||||||
payload = message.payload.decode("UTF-8")
|
payload = message.payload.decode("UTF-8")
|
||||||
for fnc in self.each_inverter(message, "send_at_cmd"):
|
for fnc in self.each_inverter(message, "send_at_cmd"):
|
||||||
await fnc(payload)
|
await fnc(payload)
|
||||||
|
|
||||||
|
def dcu_cmd(self, message):
|
||||||
|
payload = message.payload.decode("UTF-8")
|
||||||
|
try:
|
||||||
|
val = round(float(payload) * 10)
|
||||||
|
if val < 1000 or val > 8000:
|
||||||
|
logger_mqtt.error('dcu_power: value must be in'
|
||||||
|
'the range 100..800,'
|
||||||
|
f' got: {payload}')
|
||||||
|
else:
|
||||||
|
pdu = struct.pack('>BBBBBBH', 1, 1, 6, 1, 0, 1, val)
|
||||||
|
for fnc in self.each_inverter(message, "send_dcu_cmd"):
|
||||||
|
fnc(pdu)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|||||||
20
app/tests/test_mqtt.py
Normal file → Executable file
20
app/tests/test_mqtt.py
Normal file → Executable file
@@ -69,6 +69,14 @@ def spy_modbus_cmd_client():
|
|||||||
yield wrapped_conn
|
yield wrapped_conn
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def spy_dcu_cmd():
|
||||||
|
conn = SolarmanV5(None, ('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl())
|
||||||
|
conn.node_id = 'inv_3/'
|
||||||
|
with patch.object(conn, 'send_dcu_cmd', wraps=conn.send_dcu_cmd) as wrapped_conn:
|
||||||
|
yield wrapped_conn
|
||||||
|
conn.close()
|
||||||
|
|
||||||
def test_native_client(test_hostname, test_port):
|
def test_native_client(test_hostname, test_port):
|
||||||
"""Sanity check: Make sure the paho-mqtt client can connect to the test
|
"""Sanity check: Make sure the paho-mqtt client can connect to the test
|
||||||
MQTT server. Otherwise the test set NO_MOSQUITTO_TEST to True and disable
|
MQTT server. Otherwise the test set NO_MOSQUITTO_TEST to True and disable
|
||||||
@@ -267,3 +275,15 @@ async def test_at_cmd_dispatch(config_mqtt_conn, spy_at_cmd):
|
|||||||
|
|
||||||
finally:
|
finally:
|
||||||
await m.close()
|
await m.close()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dcu_dispatch(config_mqtt_conn, spy_dcu_cmd):
|
||||||
|
_ = config_mqtt_conn
|
||||||
|
spy = spy_dcu_cmd
|
||||||
|
try:
|
||||||
|
m = Mqtt(None)
|
||||||
|
msg = aiomqtt.Message(topic= 'tsun/inv_3/dcu_power', payload= b'100.0', qos= 0, retain = False, mid= 0, properties= None)
|
||||||
|
await m.dispatch_msg(msg)
|
||||||
|
spy.assert_called_once_with(b'\x01\x01\x06\x01\x00\x01\x03\xe8')
|
||||||
|
finally:
|
||||||
|
await m.close()
|
||||||
|
|||||||
86
app/tests/test_solarman.py
Normal file → Executable file
86
app/tests/test_solarman.py
Normal file → Executable file
@@ -812,6 +812,15 @@ def dcu_data_rsp_msg(): # 0x1210
|
|||||||
msg += b'\x15'
|
msg += b'\x15'
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dcu_command_ind_msg(): # 0x4510
|
||||||
|
msg = b'\xa5\x17\x00\x10\x45\x94\x02' +get_dcu_sn() +b'\x05\x26\x30'
|
||||||
|
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
msg += b'\x01\x01\x06\x01\x00\x01\x03\xe8'
|
||||||
|
msg += correct_checksum(msg)
|
||||||
|
msg += b'\x15'
|
||||||
|
return msg
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def config_tsun_allow_all():
|
def config_tsun_allow_all():
|
||||||
Config.act_config = {
|
Config.act_config = {
|
||||||
@@ -2402,3 +2411,80 @@ async def test_proxy_at_blocked(my_loop, config_tsun_inv1, patch_open_connection
|
|||||||
|
|
||||||
assert Proxy.mqtt.key == 'tsun/inv1/at_resp'
|
assert Proxy.mqtt.key == 'tsun/inv1/at_resp'
|
||||||
assert Proxy.mqtt.data == "+ok"
|
assert Proxy.mqtt.data == "+ok"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dcu_cmd(my_loop, config_tsun_allow_all, dcu_dev_ind_msg, dcu_dev_rsp_msg, dcu_data_ind_msg, dcu_data_rsp_msg, dcu_command_ind_msg, at_command_rsp_msg):
|
||||||
|
_ = config_tsun_allow_all
|
||||||
|
m = MemoryStream(dcu_dev_ind_msg, (0,), True)
|
||||||
|
m.read() # read device ind
|
||||||
|
assert m.control == 0x4110
|
||||||
|
assert str(m.seq) == '01:92'
|
||||||
|
assert m.ifc.tx_fifo.get()==dcu_dev_rsp_msg
|
||||||
|
assert m.ifc.fwd_fifo.get()==dcu_dev_ind_msg
|
||||||
|
|
||||||
|
m.send_dcu_cmd(b'\x01\x01\x06\x01\x00\x01\x03\xe8')
|
||||||
|
assert m.ifc.tx_fifo.get()==b''
|
||||||
|
assert m.ifc.fwd_fifo.get()==b''
|
||||||
|
assert m.sent_pdu == b''
|
||||||
|
assert str(m.seq) == '01:92'
|
||||||
|
assert Proxy.mqtt.key == ''
|
||||||
|
assert Proxy.mqtt.data == ""
|
||||||
|
|
||||||
|
m.append_msg(dcu_data_ind_msg)
|
||||||
|
m.read() # read inverter ind
|
||||||
|
assert m.control == 0x4210
|
||||||
|
assert str(m.seq) == '02:93'
|
||||||
|
assert m.ifc.tx_fifo.get()==dcu_data_rsp_msg
|
||||||
|
assert m.ifc.fwd_fifo.get()==dcu_data_ind_msg
|
||||||
|
|
||||||
|
m.send_dcu_cmd(b'\x01\x01\x06\x01\x00\x01\x03\xe8')
|
||||||
|
assert m.ifc.fwd_fifo.get() == b''
|
||||||
|
assert m.ifc.tx_fifo.get()== b''
|
||||||
|
assert m.sent_pdu == dcu_command_ind_msg
|
||||||
|
m.sent_pdu = bytearray()
|
||||||
|
|
||||||
|
assert str(m.seq) == '02:94'
|
||||||
|
assert Proxy.mqtt.key == ''
|
||||||
|
assert Proxy.mqtt.data == ""
|
||||||
|
|
||||||
|
# m.append_msg(at_command_rsp_msg)
|
||||||
|
# m.read() # read at resp
|
||||||
|
# assert m.control == 0x1510
|
||||||
|
# assert str(m.seq) == '03:03'
|
||||||
|
# assert m.ifc.rx_get()==b''
|
||||||
|
# assert m.ifc.tx_fifo.get()==b''
|
||||||
|
# assert m.ifc.fwd_fifo.get()==b''
|
||||||
|
# assert Proxy.mqtt.key == 'tsun/at_resp'
|
||||||
|
# assert Proxy.mqtt.data == "+ok"
|
||||||
|
Proxy.mqtt.clear() # clear last test result
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dcu_cmd_not_supported(my_loop, config_tsun_allow_all, device_ind_msg, device_rsp_msg, inverter_ind_msg, inverter_rsp_msg):
|
||||||
|
_ = config_tsun_allow_all
|
||||||
|
m = MemoryStream(device_ind_msg, (0,), True)
|
||||||
|
m.read() # read device ind
|
||||||
|
assert m.control == 0x4110
|
||||||
|
assert str(m.seq) == '01:01'
|
||||||
|
assert m.ifc.tx_fifo.get()==device_rsp_msg
|
||||||
|
assert m.ifc.fwd_fifo.get()==device_ind_msg
|
||||||
|
|
||||||
|
m.send_dcu_cmd(b'\x01\x01\x06\x01\x00\x01\x03\xe8')
|
||||||
|
assert m.ifc.tx_fifo.get()==b''
|
||||||
|
assert m.ifc.fwd_fifo.get()==b''
|
||||||
|
assert m.sent_pdu == b''
|
||||||
|
assert str(m.seq) == '01:01'
|
||||||
|
assert Proxy.mqtt.key == ''
|
||||||
|
assert Proxy.mqtt.data == ""
|
||||||
|
|
||||||
|
m.append_msg(inverter_ind_msg)
|
||||||
|
m.read() # read inverter ind
|
||||||
|
assert m.control == 0x4210
|
||||||
|
assert str(m.seq) == '02:02'
|
||||||
|
assert m.ifc.tx_fifo.get()==inverter_rsp_msg
|
||||||
|
assert m.ifc.fwd_fifo.get()==inverter_ind_msg
|
||||||
|
|
||||||
|
m.send_dcu_cmd(b'\x01\x01\x06\x01\x00\x01\x03\xe8')
|
||||||
|
assert m.ifc.fwd_fifo.get() == b''
|
||||||
|
assert m.ifc.tx_fifo.get()== b''
|
||||||
|
assert m.sent_pdu == b''
|
||||||
|
Proxy.mqtt.clear() # clear last test result
|
||||||
|
|||||||
Reference in New Issue
Block a user