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'])