Browse Source

Merge branch 'modem-man-gmx-main' into development02

pull/635/head
lumapu 2 years ago
parent
commit
9f8371fdef
  1. 35
      Getting_Started.md
  2. BIN
      doc/ESP8266_nRF24L01+_LolinNodeMCUv3.png
  3. BIN
      doc/ESP8266_nRF24L01+_Schaltplan.jpg
  4. 37
      tools/rpi/ahoy.service
  5. 12
      tools/rpi/ahoy.yml.example
  6. 8
      tools/rpi/hoymiles/__init__.py
  7. 54
      tools/rpi/hoymiles/__main__.py
  8. 79
      tools/rpi/hoymiles/decoders/__init__.py
  9. 56
      tools/rpi/hoymiles/outputs.py

35
Getting_Started.md

@ -6,9 +6,12 @@
- [Things needed](#things-needed)
- [There are fake NRF24L01+ Modules out there](#there-are-fake-nrf24l01-modules-out-there)
- [Wiring things up](#wiring-things-up)
- [ESP8266 wiring example](#esp8266-wiring-example)
- [ESP8266 wiring example on WEMOS D1](#esp8266-wiring-example)
- [Schematic](#schematic)
- [Symbolic view](#symbolic-view)
- [ESP8266 wiring example on 30pin Lolin NodeMCU v3](#esp8266-wiring-example-2)
- [Schematic](#schematic-2)
- [Symbolic view](#symbolic-view-2)
- [ESP32 wiring example](#esp32-wiring-example)
- [Schematic](#schematic-1)
- [Symbolic view](#symbolic-view-1)
@ -69,8 +72,9 @@ Make sure the NRF24L01+ module has the "+" in its name as we depend on the 250kb
| **Parts** | **Price** |
| --- | --- |
| D1 ESP8266 Mini WLAN Board Mikrokontroller | 4,40 Euro |
| D1 ESP8266 Mini WLAN Board Microcontroller | 4,40 Euro |
| NRF24L01+ SMD Modul 2,4 GHz Wi-Fi Funkmodul | 3,45 Euro |
| 100µF / 10V Capacitor Kondensator | 0,15 Euro |
| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro |
| **Total costs** | **10,34 Euro** |
@ -80,6 +84,7 @@ To also run our sister project OpenDTU and be upwards compatible for the future
| --- | --- |
| ESP32 Dev Board NodeMCU WROOM32 WiFi | 7,90 Euro |
| NRF24L01+ PA LNA SMA mit Antenne Long | 4,50 Euro |
| 100µF / 10V Capacitor Kondensator | 0,15 Euro |
| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro |
| **Total costs** | **14,89 Euro** |
@ -89,6 +94,18 @@ Watch out, there are some fake NRF24L01+ Modules out there that seem to use rebr
An example can be found in [Issue #230](https://github.com/lumapu/ahoy/issues/230).<br/>
You are welcome to add more examples of faked chips. We will add that information here.<br/>
Some users reported better connection or longer range through more walls when using the
"E01-ML01DP5" EBYTE 2,4 GHz Wireless Modul nRF24L01 + PA + LNA RF Modul, SMA-K Antenna connector,
which has an eye-catching HF cover. But beware: It comes without the antenna!
In any case you should stabilize the Vcc power by a capacitor and don't exceed the Amplifier Power Level "LOW".
Users reporting good connection over 10m through walls / ceilings with Amplifier Power Level "MIN".
It is not always the bigger the better...
Power levels "HIGH" and "MAX" are meant to wirings where the nRF24 is supplied by an extra 3.3 Volt regulator.
The bultin regulator on ESP boards has only low reserves in case WiFi and nRF are sending simultaneously.
If you operate additional interfaces like a display, the reserve is again reduced.
## Wiring things up
The NRF24L01+ radio module is connected to the standard SPI pins:
@ -107,7 +124,7 @@ Additional, there are 3 pins, which can be set individual:
*These pins can be changed from the /setup URL.*
#### ESP8266 wiring example
#### ESP8266 wiring example on WEMOS D1
This is an example wiring using a Wemos D1 mini.<br>
@ -119,6 +136,18 @@ This is an example wiring using a Wemos D1 mini.<br>
![Symbolic](doc/AhoyWemos_Steckplatine.jpg)
#### ESP8266 wiring example on 30pin Lolin NodeMCU v3
This is an example wiring using a NodeMCU V3.<br>
##### Schematic
![Schematic](doc/ESP8266_nRF24L01+_Schaltplan.jpg)
##### Symbolic view
![Symbolic](doc/ESP8266_nRF24L01+_bb.png)
#### ESP32 wiring example
Example wiring for a 38pin ESP32 module

BIN
doc/ESP8266_nRF24L01+_LolinNodeMCUv3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

BIN
doc/ESP8266_nRF24L01+_Schaltplan.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

37
tools/rpi/ahoy.service

@ -0,0 +1,37 @@
######################################################################
# systemd.service configuration for ahoy (lumapu)
# users can modify the lines:
# Description
# ExecStart (example: name of config file)
# WorkingDirectory (absolute path to your private ahoy dir)
# To change other config parameter, please consult systemd documentation
#
# To activate this service, create a link, enable and start the ahoy.service
# $ mkdir -p $HOME/.config/systemd/user
# $ ln -sf $(pwd)/ahoy/tools/rpi/ahoy.service -t $HOME/.config/systemd/user
# $ systemctl --user status ahoy
# $ systemctl --user enable ahoy
# $ systemctl --user start ahoy
# $ systemctl --user status ahoy
#
# 2023.01 <PaeserBastelstube>
######################################################################
[Unit]
Description=ahoy (lumapu) as Service
After=network.target local-fs.target time-sync.target
[Service]
ExecStart=/usr/bin/env python3 -um hoymiles --log-transactions --verbose --config ahoy.yml
RestartSec=10
Restart=on-failure
Type=simple
# WorkingDirectory must be an absolute path - not relative path
WorkingDirectory=/home/pi/ahoy/tools/rpi
EnvironmentFile=/etc/environment
[Install]
WantedBy=default.target

12
tools/rpi/ahoy.yml.example

@ -76,4 +76,14 @@ ahoy:
txpower: 'low' # txpower per inverter (min,low,high,max)
mqtt:
send_raw_enabled: false # allow inject debug data via mqtt
topic: 'hoymiles/114172221234' # defaults to 'hoymiles/{serial}'
topic: 'hoymiles/114172221234' # defaults to '{inverter-name}/{serial}'
strings: # list all available strings
- s_name: 'String 1 left' # String 1 name
s_maxpower: 395 # String 1 max power in Wp
- s_name: 'String 2 right' # String 2 name
s_maxpower: 400 # String 2 max power in Wp
- s_name: 'String 3 up' # String 3 name
s_maxpower: 405 # String 3 max power in Wp
- s_name: 'String 4 down' # String 4 name
s_maxpower: 410 # String 4 max power in Wp

8
tools/rpi/hoymiles/__init__.py

@ -144,6 +144,9 @@ class ResponseDecoder(ResponseDecoderFactory):
def __init__(self, response, **params):
"""Initialize ResponseDecoder"""
ResponseDecoderFactory.__init__(self, response, **params)
self.inv_name=params.get('inverter_name', None)
self.dtu_ser=params.get('dtu_ser', None)
self.strings=params.get('strings', None)
def decode(self):
"""
@ -164,7 +167,10 @@ class ResponseDecoder(ResponseDecoderFactory):
return device(self.response,
time_rx=self.time_rx,
inverter_ser=self.inverter_ser
inverter_ser=self.inverter_ser,
inverter_name=self.inv_name,
dtu_ser=self.dtu_ser,
strings=self.strings
)
class InverterPacketFragment:

54
tools/rpi/hoymiles/__main__.py

@ -118,6 +118,8 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
:type retries: int
"""
inverter_ser = inverter.get('serial')
inverter_name = inverter.get('name')
inverter_strings = inverter.get('strings')
# Queue at least status data request
inv_str = str(inverter_ser)
@ -161,25 +163,17 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response))
decoder = hoymiles.ResponseDecoder(response,
request=com.request,
inverter_ser=inverter_ser
inverter_ser=inverter_ser,
inverter_name=inverter_name,
dtu_ser=dtu_ser,
strings=inverter_strings
)
result = decoder.decode()
if isinstance(result, hoymiles.decoders.StatusResponse):
data = result.__dict__()
if hoymiles.HOYMILES_DEBUG_LOGGING:
dbg = f'{c_datetime} Decoded: temp={data["temperature"]}, total={data["energy_total"]/1000:.3f}'
if data['powerfactor'] is not None:
dbg += f', pf={data["powerfactor"]}'
phase_id = 0
for phase in data['phases']:
dbg += f' phase{phase_id}=voltage:{phase["voltage"]}, current:{phase["current"]}, power:{phase["power"]}, frequency:{data["frequency"]}'
phase_id = phase_id + 1
string_id = 0
for string in data['strings']:
dbg += f' string{string_id}=voltage:{string["voltage"]}, current:{string["current"]}, power:{string["power"]}, total:{string["energy_total"]/1000}, daily:{string["energy_daily"]}'
string_id = string_id + 1
logging.debug(dbg)
logging.debug(f'{c_datetime} Decoded: {result.__dict__()}')
if 'event_count' in data:
if event_message_index[inv_str] < data['event_count']:
@ -187,8 +181,9 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.AlarmData, alarm_id=event_message_index[inv_str]))
if mqtt_client:
mqtt_send_status(mqtt_client, inverter_ser, data,
topic=inverter.get('mqtt', {}).get('topic', None))
# mqtt_send_status(mqtt_client, inverter_ser, data, topic=inverter.get('mqtt', {}).get('topic', None))
mqtt_client.store_status(result, topic=inverter.get('mqtt', {}).get('topic', None))
if influx_client:
influx_client.store_status(result)
@ -209,21 +204,27 @@ def mqtt_send_status(broker, inverter_ser, data, topic=None):
if not topic:
topic = f'hoymiles/{inverter_ser}'
# Global Head
if data['time'] is not None:
broker.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S"))
# AC Data
phase_id = 0
for phase in data['phases']:
broker.publish(f'{topic}/emeter/{phase_id}/power', phase['power'])
broker.publish(f'{topic}/emeter/{phase_id}/voltage', phase['voltage'])
broker.publish(f'{topic}/emeter/{phase_id}/current', phase['current'])
broker.publish(f'{topic}/emeter/{phase_id}/Q_AC', phase['reactive_power'])
phase_id = phase_id + 1
# DC Data
string_id = 0
for string in data['strings']:
broker.publish(f'{topic}/emeter-dc/{string_id}/total', string['energy_total']/1000)
broker.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'])
broker.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage'])
broker.publish(f'{topic}/emeter-dc/{string_id}/current', string['current'])
broker.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'])
broker.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily'])
broker.publish(f'{topic}/emeter-dc/{string_id}/YieldTotal', string['energy_total']/1000)
string_id = string_id + 1
# Global
if data['powerfactor'] is not None:
@ -328,8 +329,6 @@ if __name__ == '__main__':
for radio_config in ahoy_config.get('nrf', [{}]):
hmradio = hoymiles.HoymilesNRF(**radio_config)
mqtt_client = None
event_message_index = {}
command_queue = {}
mqtt_command_topic_subs = []
@ -339,18 +338,11 @@ if __name__ == '__main__':
if global_config.verbose:
hoymiles.HOYMILES_DEBUG_LOGGING=True
mqtt_config = ahoy_config.get('mqtt', [])
if not mqtt_config.get('disabled', False):
mqtt_client = paho.mqtt.client.Client()
if mqtt_config.get('useTLS',False):
mqtt_client.tls_set()
mqtt_client.tls_insecure_set(mqtt_config.get('insecureTLS',False))
mqtt_client.username_pw_set(mqtt_config.get('user', None), mqtt_config.get('password', None))
mqtt_client.connect(mqtt_config.get('host', '127.0.0.1'), mqtt_config.get('port', 1883))
mqtt_client.loop_start()
mqtt_client.on_message = mqtt_on_command
mqtt_client = None
mqtt_config = ahoy_config.get('mqtt', {})
if mqtt_config and not mqtt_config.get('disabled', False):
from .outputs import MqttOutputPlugin
mqtt_client = MqttOutputPlugin(mqtt_config)
influx_client = None
influx_config = ahoy_config.get('influxdb', {})

79
tools/rpi/hoymiles/decoders/__init__.py

@ -74,9 +74,11 @@ class Response:
self.inverter_ser = params.get('inverter_ser', None)
self.inverter_name = params.get('inverter_name', None)
self.dtu_ser = params.get('dtu_ser', None)
self.response = args[0]
strings = params.get('strings', None)
self.inv_strings = strings
if isinstance(params.get('time_rx', None), datetime):
self.time_rx = params['time_rx']
else:
@ -91,7 +93,7 @@ class Response:
class StatusResponse(Response):
"""Inverter StatusResponse object"""
e_keys = ['voltage','current','power','energy_total','energy_daily','powerfactor']
e_keys = ['voltage','current','power','energy_total','energy_daily','powerfactor', 'reactive_power', 'irradiation']
temperature = None
frequency = None
powerfactor = None
@ -333,18 +335,29 @@ class HardwareInfoResponse(UnknownResponse):
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }
{ FLD_FW_Build_Hour_Minute, UNIT_NONE, CH0, 6, 2, 1 },
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 },
{ FLD_unknown, UNIT_NONE, CH0, 10, 2, 1 },
{ FLD_unknown, UNIT_NONE, CH0, 12, 2, 1 },
{ FLD_CRC-M, UNIT_NONE, CH0, 14, 2, 1 }
};
self.response = bytes('\x27\x1a\x07\xe5\x04\x4d\x03\x4a\x00\x68\x00\x00\x00\x00\xe6\xfb', 'latin1')
"""
fw_version, fw_build_yyyy, fw_build_mmdd, unknown, hw_id = struct.unpack('>HHHHH', self.response[0:10])
fw_version, fw_build_yyyy, fw_build_mmdd, fw_build_hhmm, hw_id = struct.unpack('>HHHHH', self.response[0:10])
responce_info = self.response
logging.debug(f'HardwareInfoResponse: {struct.unpack(">HHHHHHHH", responce_info)}')
fw_version_maj = int((fw_version / 10000))
fw_version_min = int((fw_version % 10000) / 100)
fw_version_pat = int((fw_version % 100))
fw_build_mm = int(fw_build_mmdd / 100)
fw_build_dd = int(fw_build_mmdd % 100)
logging.debug(f'Firmware: {fw_version_maj}.{fw_version_min}.{fw_version_pat} build at {fw_build_dd}/{fw_build_mm}/{fw_build_yyyy}, HW revision {hw_id}')
fw_build_HH = int(fw_build_hhmm / 100)
fw_build_MM = int(fw_build_hhmm % 100)
logging.debug(f'Firmware: {fw_version_maj}.{fw_version_min}.{fw_version_pat} '\
f'build at {fw_build_dd:>02}/{fw_build_mm:>02}/{fw_build_yyyy}T{fw_build_HH:>02}:{fw_build_MM:>02}, '\
f'HW revision {hw_id}')
class DebugDecodeAny(UnknownResponse):
"""Default decoder"""
@ -420,6 +433,12 @@ class Hm300Decode0B(StatusResponse):
def dc_energy_daily_0(self):
""" String 1 daily energy in Wh """
return self.unpack('>H', 12)[0]
@property
def dc_irradiation_0(self):
""" String 1 irratiation in percent """
if self.inv_strings is None:
return None
return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
@property
def ac_voltage_0(self):
@ -438,6 +457,10 @@ class Hm300Decode0B(StatusResponse):
""" Grid frequency in Hertz """
return self.unpack('>H', 16)[0]/100
@property
def ac_reactive_power_0(self):
""" reactive power """
return self.unpack('>H', 20)[0]/10
@property
def temperature(self):
""" Inverter temperature in °C """
return self.unpack('>h', 26)[0]/10
@ -482,6 +505,12 @@ class Hm600Decode0B(StatusResponse):
def dc_energy_daily_0(self):
""" String 1 daily energy in Wh """
return self.unpack('>H', 22)[0]
@property
def dc_irradiation_0(self):
""" String 1 irratiation in percent """
if self.inv_strings is None:
return None
return round(self.unpack('>H', 6)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
@property
def dc_voltage_1(self):
@ -503,6 +532,12 @@ class Hm600Decode0B(StatusResponse):
def dc_energy_daily_1(self):
""" String 2 daily energy in Wh """
return self.unpack('>H', 24)[0]
@property
def dc_irradiation_1(self):
""" String 2 irratiation in percent """
if self.inv_strings is None:
return None
return round(self.unpack('>H', 12)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3)
@property
def ac_voltage_0(self):
@ -511,7 +546,7 @@ class Hm600Decode0B(StatusResponse):
@property
def ac_current_0(self):
""" Phase 1 ampere """
return self.unpack('>H', 34)[0]/10
return self.unpack('>H', 34)[0]/100
@property
def ac_power_0(self):
""" Phase 1 watts """
@ -521,6 +556,10 @@ class Hm600Decode0B(StatusResponse):
""" Grid frequency in Hertz """
return self.unpack('>H', 28)[0]/100
@property
def ac_reactive_power_0(self):
""" reactive power """
return self.unpack('>H', 32)[0]/10
@property
def powerfactor(self):
""" Powerfactor """
return self.unpack('>H', 36)[0]/1000
@ -573,6 +612,12 @@ class Hm1200Decode0B(StatusResponse):
def dc_energy_daily_0(self):
""" String 1 daily energy in Wh """
return self.unpack('>H', 20)[0]
@property
def dc_irradiation_0(self):
""" String 1 irratiation in percent """
if self.inv_strings is None:
return None
return round(self.unpack('>H', 8)[0]/10/self.inv_strings[0]['s_maxpower']*100, 3)
@property
def dc_voltage_1(self):
@ -594,6 +639,12 @@ class Hm1200Decode0B(StatusResponse):
def dc_energy_daily_1(self):
""" String 2 daily energy in Wh """
return self.unpack('>H', 22)[0]
@property
def dc_irradiation_0(self):
""" String 2 irratiation in percent """
if self.inv_strings is None:
return None
return round(self.unpack('>H', 10)[0]/10/self.inv_strings[1]['s_maxpower']*100, 3)
@property
def dc_voltage_2(self):
@ -615,6 +666,12 @@ class Hm1200Decode0B(StatusResponse):
def dc_energy_daily_2(self):
""" String 3 daily energy in Wh """
return self.unpack('>H', 42)[0]
@property
def dc_irradiation_0(self):
""" String 3 irratiation in percent """
if self.inv_strings is None:
return None
return round(self.unpack('>H', 30)[0]/10/self.inv_strings[2]['s_maxpower']*100, 3)
@property
def dc_voltage_3(self):
@ -636,6 +693,12 @@ class Hm1200Decode0B(StatusResponse):
def dc_energy_daily_3(self):
""" String 4 daily energy in Wh """
return self.unpack('>H', 44)[0]
@property
def dc_irradiation_0(self):
""" String 4 irratiation in percent """
if self.inv_strings is None:
return None
return round(self.unpack('>H', 32)[0]/10/self.inv_strings[3]['s_maxpower']*100, 3)
@property
def ac_voltage_0(self):
@ -654,6 +717,10 @@ class Hm1200Decode0B(StatusResponse):
""" Grid frequency in Hertz """
return self.unpack('>H', 48)[0]/100
@property
def ac_reactive_power_0(self):
""" reactive power """
return self.unpack('>H', 52)[0]/10
@property
def powerfactor(self):
""" Powerfactor """
return self.unpack('>H', 56)[0]/1000

56
tools/rpi/hoymiles/outputs.py

@ -101,26 +101,32 @@ class InfluxOutputPlugin(OutputPluginFactory):
# AC Data
phase_id = 0
for phase in data['phases']:
data_stack.append(f'{measurement},phase={phase_id},type=power value={phase["power"]} {ctime}')
data_stack.append(f'{measurement},phase={phase_id},type=voltage value={phase["voltage"]} {ctime}')
data_stack.append(f'{measurement},phase={phase_id},type=current value={phase["current"]} {ctime}')
data_stack.append(f'{measurement},phase={phase_id},type=power value={phase["power"]} {ctime}')
data_stack.append(f'{measurement},phase={phase_id},type=Q_AC value={phase["reactive_power"]} {ctime}')
phase_id = phase_id + 1
# DC Data
string_id = 0
for string in data['strings']:
data_stack.append(f'{measurement},string={string_id},type=total value={string["energy_total"]/1000:.4f} {ctime}')
data_stack.append(f'{measurement},string={string_id},type=power value={string["power"]:.2f} {ctime}')
data_stack.append(f'{measurement},string={string_id},type=voltage value={string["voltage"]:.3f} {ctime}')
data_stack.append(f'{measurement},string={string_id},type=current value={string["current"]:3f} {ctime}')
data_stack.append(f'{measurement},string={string_id},type=power value={string["power"]:.2f} {ctime}')
data_stack.append(f'{measurement},string={string_id},type=YieldDay value={string["energy_daily"]:.2f} {ctime}')
data_stack.append(f'{measurement},string={string_id},type=YieldTotal value={string["energy_total"]/1000:.4f} {ctime}')
if 'irradiation' in string:
data_stack.append(f'{measurement},string={string_id},type=Irradiation value={string["irradiation"]:.2f} {ctime}')
string_id = string_id + 1
# Global
if data['event_count'] is not None:
data_stack.append(f'{measurement},type=total_events value={data["event_count"]} {ctime}')
if data['powerfactor'] is not None:
data_stack.append(f'{measurement},type=pf value={data["powerfactor"]:f} {ctime}')
data_stack.append(f'{measurement},type=frequency value={data["frequency"]:.3f} {ctime}')
data_stack.append(f'{measurement},type=temperature value={data["temperature"]:.2f} {ctime}')
data_stack.append(f'{measurement},type=Temp value={data["temperature"]:.2f} {ctime}')
if data['energy_total'] is not None:
data_stack.append(f'{measurement},type=total value={data["energy_total"]/1000:.3f} {ctime}')
@ -135,7 +141,7 @@ class MqttOutputPlugin(OutputPluginFactory):
""" Mqtt output plugin """
client = None
def __init__(self, *args, **params):
def __init__(self, config, **params):
"""
Initialize MqttOutputPlugin
@ -156,11 +162,14 @@ class MqttOutputPlugin(OutputPluginFactory):
:param topic: custom mqtt topic prefix (default: hoymiles/{inverter_ser})
:type topic: str
"""
super().__init__(*args, **params)
super().__init__(**params)
mqtt_client = paho.mqtt.client.Client()
mqtt_client.username_pw_set(params.get('user', None), params.get('password', None))
mqtt_client.connect(params.get('host', '127.0.0.1'), params.get('port', 1883))
if config.get('useTLS',False):
mqtt_client.tls_set()
mqtt_client.tls_insecure_set(config.get('insecureTLS',False))
mqtt_client.username_pw_set(config.get('user', None), config.get('password', None))
mqtt_client.connect(config.get('host', '127.0.0.1'), config.get('port', 1883))
mqtt_client.loop_start()
self.client = mqtt_client
@ -180,8 +189,11 @@ class MqttOutputPlugin(OutputPluginFactory):
raise ValueError('Data needs to be instance of StatusResponse')
data = response.__dict__()
topic = f'{data.get("inverter_name", "hoymiles")}/{data.get("inverter_ser", None)}'
topic = params.get('topic', f'hoymiles/{data["inverter_ser"]}')
# Global Head
if data['time'] is not None:
self.client.publish(f'{topic}/time', data['time'].strftime("%d.%m.%y - %H:%M:%S"))
# AC Data
phase_id = 0
@ -189,21 +201,27 @@ class MqttOutputPlugin(OutputPluginFactory):
self.client.publish(f'{topic}/emeter/{phase_id}/power', phase['power'])
self.client.publish(f'{topic}/emeter/{phase_id}/voltage', phase['voltage'])
self.client.publish(f'{topic}/emeter/{phase_id}/current', phase['current'])
self.client.publish(f'{topic}/emeter/{phase_id}/Q_AC', phase['reactive_power'])
phase_id = phase_id + 1
# DC Data
string_id = 0
for string in data['strings']:
self.client.publish(f'{topic}/emeter-dc/{string_id}/total', string['energy_total']/1000)
self.client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'])
self.client.publish(f'{topic}/emeter-dc/{string_id}/voltage', string['voltage'])
self.client.publish(f'{topic}/emeter-dc/{string_id}/current', string['current'])
self.client.publish(f'{topic}/emeter-dc/{string_id}/power', string['power'])
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldDay', string['energy_daily'])
self.client.publish(f'{topic}/emeter-dc/{string_id}/YieldTotal', string['energy_total']/1000)
if 'irradiation' in string:
self.client.publish(f'{topic}/emeter-dc/{string_id}/Irradiation', string['irradiation'])
string_id = string_id + 1
# Global
if data['powerfactor'] is not None:
self.client.publish(f'{topic}/pf', data['powerfactor'])
self.client.publish(f'{topic}/frequency', data['frequency'])
self.client.publish(f'{topic}/temperature', data['temperature'])
self.client.publish(f'{topic}/Temp', data['temperature'])
if data['energy_total'] is not None:
self.client.publish(f'{topic}/total', data['energy_total']/1000)
@ -241,26 +259,30 @@ class VzInverterOutput:
# AC Data
phase_id = 0
for phase in data['phases']:
self.try_publish(ts, f'ac_power{phase_id}', phase['power'])
self.try_publish(ts, f'ac_voltage{phase_id}', phase['voltage'])
self.try_publish(ts, f'ac_current{phase_id}', phase['current'])
self.try_publish(ts, f'ac_power{phase_id}', phase['power'])
self.try_publish(ts, f'ac_Q{phase_id}', phase['reactive_power'])
phase_id = phase_id + 1
# DC Data
string_id = 0
for string in data['strings']:
self.try_publish(ts, f'dc_power{string_id}', string['power'])
self.try_publish(ts, f'dc_voltage{string_id}', string['voltage'])
self.try_publish(ts, f'dc_current{string_id}', string['current'])
self.try_publish(ts, f'dc_total{string_id}', string['energy_total'])
self.try_publish(ts, f'dc_daily{string_id}', string['energy_daily'])
self.try_publish(ts, f'dc_power{string_id}', string['power'])
self.try_publish(ts, f'dc_YieldDay{string_id}', string['energy_daily'])
self.try_publish(ts, f'dc_YieldTotal{string_id}', string['energy_total'])
if 'irradiation' in string:
self.try_publish(ts, f'dc_Irradiation{string_id}', string['irradiation'])
string_id = string_id + 1
# Global
if data['powerfactor'] is not None:
self.try_publish(ts, f'powerfactor', data['powerfactor'])
self.try_publish(ts, f'frequency', data['frequency'])
self.try_publish(ts, f'temperature', data['temperature'])
self.try_publish(ts, f'Temp', data['temperature'])
if data['energy_total'] is not None:
self.try_publish(ts, f'total', data['energy_total'])

Loading…
Cancel
Save