diff --git a/Getting_Started.md b/Getting_Started.md
index fd4de83e..af8789c3 100644
--- a/Getting_Started.md
+++ b/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).
You are welcome to add more examples of faked chips. We will add that information here.
+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.
@@ -119,6 +136,18 @@ This is an example wiring using a Wemos D1 mini.
![Symbolic](doc/AhoyWemos_Steckplatine.jpg)
+#### ESP8266 wiring example on 30pin Lolin NodeMCU v3
+
+This is an example wiring using a NodeMCU V3.
+
+##### 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
diff --git a/doc/ESP8266_nRF24L01+_LolinNodeMCUv3.png b/doc/ESP8266_nRF24L01+_LolinNodeMCUv3.png
new file mode 100644
index 00000000..2b37a3b0
Binary files /dev/null and b/doc/ESP8266_nRF24L01+_LolinNodeMCUv3.png differ
diff --git a/doc/ESP8266_nRF24L01+_Schaltplan.jpg b/doc/ESP8266_nRF24L01+_Schaltplan.jpg
new file mode 100644
index 00000000..749e3fa3
Binary files /dev/null and b/doc/ESP8266_nRF24L01+_Schaltplan.jpg differ
diff --git a/tools/rpi/ahoy.service b/tools/rpi/ahoy.service
new file mode 100644
index 00000000..394bc09e
--- /dev/null
+++ b/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
+######################################################################
+
+[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
+
diff --git a/tools/rpi/ahoy.yml.example b/tools/rpi/ahoy.yml.example
index 04da1f18..7cb0c3b7 100644
--- a/tools/rpi/ahoy.yml.example
+++ b/tools/rpi/ahoy.yml.example
@@ -73,7 +73,17 @@ ahoy:
inverters:
- name: 'balkon'
serial: 114172220003
- txpower: 'low' # txpower per inverter (min,low,high,max)
+ 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}'
+ send_raw_enabled: false # allow inject debug data via mqtt
+ 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
+
diff --git a/tools/rpi/hoymiles/__init__.py b/tools/rpi/hoymiles/__init__.py
index c02a0c71..169ad732 100644
--- a/tools/rpi/hoymiles/__init__.py
+++ b/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:
diff --git a/tools/rpi/hoymiles/__main__.py b/tools/rpi/hoymiles/__main__.py
index 8fc1b727..d9e35df1 100644
--- a/tools/rpi/hoymiles/__main__.py
+++ b/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', {})
diff --git a/tools/rpi/hoymiles/decoders/__init__.py b/tools/rpi/hoymiles/decoders/__init__.py
index 0935110e..c379f2e4 100644
--- a/tools/rpi/hoymiles/decoders/__init__.py
+++ b/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
diff --git a/tools/rpi/hoymiles/outputs.py b/tools/rpi/hoymiles/outputs.py
index a365033a..e70c3b8d 100644
--- a/tools/rpi/hoymiles/outputs.py
+++ b/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'])