Last Updated on 13 februari 2022 by Syds
Dit project heeft tot doel om de opbrengst, en nog wat aanvullende gegevens, van mijn zonnepanelen uit te lezen en te presenteren in een mooi dashboard. Ik heb 20 zonnepanelen op mijn dak welke per 10 aangesloten zijn op 2 Delta Solivia Omvormers (Inverters). Deze 2 omvormers hangen in de berging van mijn woning. Ik heb deze omvormers middels een standaard RJ-45 kabel op elkaar aangesloten, en vervolgens middels een standaard RJ-45 kabel waar ik één zijde van afgeknipt heb een middels een FT232RL + 75176 FTDI USB naar RS485 seriële adapter aangesloten op de USB-aansluiting van een Linksys NSLU2 (De NSLU2 is een netwerkapparaat dat wordt gemaakt door Linksys. Het wordt gebruikt om USB-opslagapparaten zoals USB-sticks en harde schijven met USB-interface toegankelijk te maken over een netwerk.). Deze NSLU2 had ik al jaren werkeloos is de doos liggen, maar is een ideaal apparaat om deze toepassing mee te realiseren. Natuurlijk kun je ook prima een Raspberry PI of ander linux doosje gebruiken. Dus deze afgestoft en van een vers Debian 7 linux operating voorzien en Python opgezet. Vervolgens twee python scripts en een shell-script gemaakt die de twee omvormers uitlezen en de output van de omvormers in JSON-formaat naar de MQTT-broker doorzet. Node-red pakt vervolgens deze output op en zet deze in een MariaDB-tabel. Tot slot met Google Data studio een flashy Dashboard gemaakt. Hieronder een overzicht van mijn twee Delta Solivia omvormers:Solivia 2.5 EU G3 | Solivia 3.0 TR EU G4 |
Variant 15 (paragraaf 8.6) | Variant 105 (paragraaf 8.7) |
Serienr. 220268151317000697 | Serienr. 220287161303000538 |
- 3 RJ-45 kabels
- FT232RL + 75176 FTDI USB naar RS485 seriële adapter
- USB 2.0 kabel A mannelijk – B mannelijk
- NSLU2 (of andere linux doos)
- Minimaal 4Gb USB-stick
- Vrije poort op je switch of router
- Optioneel: 3D printer + Filament, 2x female-female spacers 10mmx5mm, 2 schroefjes
- Node-red
- MQTT
- MariaDB (MySQL)
- Google account
Solivia 3.0 |
Solivia 2.5 |
||
RJ-45 pin – draad | FT232RL | RJ-45 pin – draad | FT232RL |
8 – Bruin | RX_B | 7 – Bruin/wit | TX_A |
7 – Bruin/wit | TX_A | 6 – Groen | RX_B |
4 – Blauw | Gnd | 4 – Blauw | Gnd |
- unset_serial_ctrlchars.sh /dev/ttyUSB1
- sudo apt-get install jpnevulator
- jpnevulator –ascii –timing-print –tty /dev/ttyUSB1 –read
- Cover: https://drive.google.com/file/d/1F2BgeZa6pmXcSo61dwrS96P0A5ET6YgE/view?usp=sharing
- Case: https://drive.google.com/file/d/174UoRRUeoo3FSdPu-MEKfaheaOAnN4kE/view?usp=sharing
Setting | Waarde |
Extruder temperature | 200o |
Hotbed temperature | 50o |
Layer height | 0,12 mm |
Infill density | 20% cubic |
Wall/Top thickness | 0,8 mm |
Generate support | Uitgevinkt |
Retraction | Aangevinkt |
Printing Speed | 80mm/s |
Cooling | 100% |
Onderdeel | Kleur | Doorlooptijd in minuten | Gram | File |
Cover | Grijs | 18 | 1 | Cover.stl |
Case | Grijs | 163 | 14 | Case.stl |
- NSLU2 Debian 7 firmware + OS geïnstalleerd conform beschrijving op https://www.cyrius.com/debian/nslu2/unpack/
- Verder qua inrichting het minimale gedaan, alleen de hostname gezet, het root wachtwoord aangepast en de NTP client geïnstalleerd en geconfigureerd naar de time servers van google
- De APT bibliotheek nog gerefreshed met:
sudo apt-get update
- NLSU2 met ethernet kabel aangesloten op een switch, en de USB kabel van de FT232RL adapter op de nog vrije USB poort van de NLSU2 aangesloten.
-
-
-
- PIP geïnstalleerd met
Note: Bij een herinstallatie werkten pip en pip3 niet, dit opgelost door:sudo apt-get install python3-pip
cd /tmp curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py sudo python3 get-pip.py
- pyserial met PIP geïnstalleerd met
sudo pip3 install pyserial
- Git geïnstalleerd met
- PIP geïnstalleerd met
sudo apt-get install git
- map /root/python aangemaakt mkdir /root/python
- cd /root/python
- Met Git Python scripts van https://github.com/bbinet/delta-rpi gehaald met
git clone https://github.com/bbinet/delta-rpi
-
- cd delta-rpi
- python script delta-rpi.py gekopieerd naar delta-solivia3.0.py respectievelijk delta-solivia2.5.py met
cp delta-rpi.py delta-solivia3.0.py cp delta-rpi.py delta-solivia2.5.py
-
- Conform Public RS485 Protocol 1V2.pdf “Public Solar Inverter Communication Protocol (Version 1.2)” van Delta Energy Systems, paragraaf 8.7 (Solivia 3.0 EU G4 TR = Variant 105 ) de DELTA_RPI_STRUCT in python script delta-solivia3.0.py aangepast, er voor gezorgd dat er JSON uit komt samen met wat andere noodzakelijke aanpassingen (geel gekleurd)
#!/usr/bin/python3 # -*- coding: utf-8 -*- import time import binascii import struct import serial import sys import os import signal from datetime import datetime from argparse import ArgumentParser from pprint import pprint import crc16 def ma_mi_bf(data): ma, mi, bf = struct.unpack('>BBB', data) return '{:02d}.{:02d}.{:02d}'.format(ma, mi, bf) DEBUG=False READ_BYTES = 1024 STX=0x02 ETX=0x03 ENQ=0x05 ACK=0x06 NAK=0x15 # Variables in the data-block of a Delta RPI M-series inverter, # as far as I've been able to establish their meaning. # The fields for each variable are as follows: # name, struct, size in bytes, decoder, multiplier-exponent (10^x), unit, SunSpec equivalent DELTA_RPI = ( ("SAP part number", "11s", str), ("SAP serial number", "18s", str), ("SAP date code", "4s", binascii.hexlify), ("SAP revision", "2s", binascii.hexlify), ("Software Revision System controller", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision Power controller", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision ENS controller", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision Watch dog controller", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision DC controller", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision DC 1", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision DC 2", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision DC 3", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision AC", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision AC 1", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision AC 2", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision AC 3", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision reserved", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Solar Power at Input 1", "H", int, 0, "W"), ("Solar Voltage at Input 1", "H", int, 0, "V"), ("Solar Current at Input 1", "H", int, 0, "A"), ("Solar Power at Input 2", "H", int, 0, "W"), ("Solar Voltage at Input 2", "H", int, 0, "V"), ("Solar Current at Input 2", "H", int, 0, "A"), ("Solar Power at Input 3", "H", int, 0, "W"), ("Solar Voltage at Input 3", "H", int, 0, "V"), ("Solar Current at Input 3", "H", int, 0, "A"), ("AC Current(Phase1)", "H", int, 0, "A", "AphA"), ("AC Voltage(Phase1)", "H", int, 0, "V"), ("AC Frequency(Phase1)", "H", int, 0, "Hz"), ("AC Active Power(Phase1)", "H", int, 0, "W"), ("AC Reactive Power(Phase1)", "h", int, 0, "VAR"), ("AC Current(Phase2)", "H", int, 0, "A", "AphA"), ("AC Voltage(Phase2)", "H", int, 0, "V"), ("AC Frequency(Phase2)", "H", int, 0, "Hz"), ("AC Active Power(Phase2)", "H", int, 0, "W"), ("AC Reactive Power(Phase2)", "h", int, 0, "VAR"), ("AC Current(Phase3)", "H", int, 0, "A", "AphA"), ("AC Voltage(Phase3)", "H", int, 0, "V"), ("AC Frequency(Phase3)", "H", int, 0, "Hz"), ("AC Active Power(Phase3)", "H", int, 0, "W"), ("AC Reactive Power(Phase3)", "h", int, 0, "VAR"), ("Solar Isolation plus", "H", int, 0, "kOhm"), ("Solar Isolation minus", "H", int, 0, "kOhm"), ("Temperature amb", "h", int, 0, "Celsius"), ("Temperature heatsink", "h", int, 0, "Celsius"), ("Supplied ac energy (total)", "Q", int, 0, "Wh"), ("Inverter runtime (total)", "L", int, 0, "Minutes"), ("Status AC Output 1", "L", int), ("Status AC Output 2", "L", int), ("Status AC Output 3", "L", int), ("Status AC Output 4", "L", int), ("Internal Status 1", "L", int), ("Internal Status 2", "L", int), ("Internal Status 3", "L", int), ("Internal Status 4", "L", int), ("Supplied ac energy (day)", "Q", int, 0, "Wh"), ("Inverter runtime (day)", "I", int, 0, "Minute"), ("reserved", "67s", binascii.hexlify), ) DELTA_RPI_STRUCT = '>' + ''.join([item[1] for item in DELTA_RPI]) DUMMY_DATA = ( b'802FA0E1000', # SAP part number b'O1S16300040WH', # SAP serial number b'0901', # SAP date code b'0\x00', # SAP revision b'\x01#', # DSP FW Rev b'\x0f0', # DSP FW Date b'\x01\r', # Redundant MCU FW Rev b'\x0f\x0e', # Redundant MCU FW Date b'\x01\x10', # Display MCU FW Rev b'\x0f0', # Display MCU FW Date b'\x00\x00', # Display WebPage Ctrl FW Rev b'\x00\x00', # Display WebPage Ctrl FW Date b'\x00\x00', # Display WiFi Ctrl FW Rev b'\x00\x00', # Display WiFi Ctrl FW Date 0, # AC Voltage(Phase1) 0, # AC Current(Phase1) 0, # AC Power(Phase1) 0, # AC Frequency(Phase1) 0, # AC Voltage(Phase1) [Redundant] 0, # AC Frequency(Phase1) [Redundant] 0, # AC Voltage(Phase2) 0, # AC Current(Phase2) 0, # AC Power(Phase2) 0, # AC Frequency(Phase2) 0, # AC Voltage(Phase2) [Redundant] 0, # AC Frequency(Phase2) [Redundant] 0, # AC Voltage(Phase3) 0, # AC Current(Phase3) 0, # AC Power(Phase3) 0, # AC Frequency(Phase3) 0, # AC Voltage(Phase3) [Redundant] 0, # AC Frequency(Phase3) [Redundant] 0, # Solar Voltage at Input 1 0, # Solar Current at Input 1 0, # Solar Power at Input 1 0, # Solar Voltage at Input 2 0, # Solar Current at Input 2 0, # Solar Power at Input 2 0, # ACPower 0, # (+) Bus Voltage 0, # (-) Bus Voltage 0, # Supplied ac energy today 0, # Inverter runtime today 0, # Supplied ac energy (total) 0, # Inverter runtime (total) 0, # Calculated temperature inside rack 0, # Status AC Output 1 0, # Status AC Output 2 0, # Status AC Output 3 0, # Status AC Output 4 0, # Status DC Input 1 0, # Status DC Input 2 0, # Error Status 0, # Error Status AC 1 0, # Global Error 1 0, # CPU Error 0, # Global Error 2 0, # Limits AC output 1 0, # Limits AC output 2 0, # Global Error 3 0, # Limits DC 1 0, # Limits DC 2 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', # History status messages ) def signal_handler(signal, frame): ''' Catch SIGINT/SIGTERM/SIGKILL and exit gracefully ''' print("Stop requested...") sys.exit(0) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) def send(conn, req, cmd, subcmd, data=b'', addr=1): """ Send cmd/subcmd (e.g. 0x60/0x01) and optional data to the RS485 bus """ assert req in (ENQ, ACK, NAK) # req should be one of ENQ, ACK, NAK msg = struct.pack('BBBBB', req, addr, 2 + len(data), cmd, subcmd) if len(data) > 0: msg = struct.pack('5s%ds' % len(data), msg, data) crcval = crc16.calcData(msg) lsb = crcval & (0xff) msb = (crcval >> 8) & 0xff data = struct.pack('B%dsBBB' % len(msg), STX, msg, lsb, msb, ETX) if DEBUG: print(">>> SEND:", binascii.hexlify(msg), "=>", binascii.hexlify(data)) conn.write(data) conn.flush() def receive(conn): """ Attempt to read messages from a serial connection """ data = bytearray() while True: buf = conn.read(READ_BYTES) if buf: if DEBUG: print(">>> RAW RECEIVE:", buf) data.extend(buf) if (not buf) or len(buf) < READ_BYTES: break idx = 0 while idx + 9 <= len(data): if data[idx] != STX: idx += 1 continue stx, req, addr, size = struct.unpack('>BBBB', data[idx:idx+4]) if req not in (ENQ, ACK, NAK): print("Bad req value: {:02x} (should be one of ENQ/ACK/NAK)".format(req)) idx += 1 continue if idx + 4 + size >= len(data): print("Can't read %d bytes from buffer" % size) idx += 1 continue msg, lsb, msb, etx = struct.unpack('>%dsBBB' % size, data[idx+4:idx+7+size]) if etx != ETX: print("Bad ETX value: {:02x}".format(etx)) idx += 1 continue crc_calc = crc16.calcData(data[idx+1:idx+4+size]) crc_msg = msb << 8 | lsb if crc_calc != crc_msg: print("Bad CRC check: %s <> %s" % (binascii.hexlify(crc_calc), binascii.hexlify(crc_msg))) idx += 1 continue if DEBUG: print(">>> RECV:", binascii.hexlify(data), "=>", binascii.hexlify(msg)) yield { "stx": stx, "req": req, "addr": addr, "size": size, "msg": msg, "lsb": lsb, "msb": msb, "etx": etx, } idx += 4 + size def decode_msg(data): req = data['req'] cmd, cmdsub = struct.unpack('>BB', data['msg'][0:2]) data['cmd'] = cmd data['cmdsub'] = cmdsub data['raw'] = data['msg'][2:] if req == NAK: print("NAK value received: cmd/subcmd request was invalid".format(req)) elif req == ENQ: if DEBUG: print("ENQ value received: request from master (datalogger)") elif req == ACK: if DEBUG: print("ACK value received: response from slave (inverter)") data['values'] = struct.unpack(DELTA_RPI_STRUCT, data['raw']) if DEBUG: pprint(data) return data def main(): global DEBUG, MODE parser = ArgumentParser(description='Delta inverter simulator (slave mode) or datalogger (master mode) for RPI M8A') parser.add_argument('-a', metavar='ADDRESS', type=int, default=1, help='slave address [default: 1]') parser.add_argument('-d', metavar='DEVICE', default='/dev/ttyUSB0', help='serial device port [default: /dev/ttyUSB0]') parser.add_argument('-b', metavar='BAUDRATE', default=9600, help='baud rate [default: 9600]') parser.add_argument('-t', metavar='TIMEOUT', type=float, default=2.0, help='timeout, in seconds (can be fractional, such as 1.5) [default: 2.0]') parser.add_argument('--debug', action="store_true", help='show debug information') parser.add_argument('mode', metavar='MODE', choices=['master', 'slave'], help='mode can either be "master" or "slave"') args = parser.parse_args() DEBUG = args.debug MODE = args.mode conn = serial.Serial(args.d, args.b, timeout=args.t); conn.flushOutput() conn.flushInput() if MODE == 'master': send(conn, ENQ, 0x60, 0x01, addr=args.a) time.sleep(0.1) for data in receive(conn): if MODE == 'master' and data['addr'] == args.a and data['req'] in (ACK, NAK,): d = decode_msg(data) if d['req'] == ACK: if not (d['cmd'] == 0x60 and d['cmdsub'] == 0x01): print("Can't decode request cmd=0x%02X, cmdsub=0x%02X" % (d['cmd'], d['cmdsub'])) print("The only supported request is cmd=0x60, cmdsub=0x01") continue # print(61 * '=') print('{') print('"inverter": 1,') print('"Datetime": "', datetime.now(), '",') for i, item in enumerate(DELTA_RPI): label = item[0] decoder = item[2] scale = item[3] if len(item) > 3 else 0 units = item[4] if len(item) > 4 else '' value = decoder(data['values'][i]) if decoder == float: value = value * pow(10, scale) # print('%-40s %20s %-10s' % (label, value, units)) print(' "' + label + '": [') print(' {') print(' "Value": ', end='') if isinstance(value, str) : print('"', end='') print(value, end='') print('",') elif isinstance(value, int) : print(value, end='') print(',') else: print('"', end='') print(value.decode('ascii'), end='') print('",') print('"Units": "' + units + '"') print('}') print('],') print(' "end": "TRUE"') print('}') if MODE == 'slave' and data['addr'] == args.a and data['req'] in (ENQ,): d = decode_msg(data) if d['cmd'] == 0x60 and d['cmdsub'] == 0x01: raw = struct.pack(DELTA_RPI_STRUCT, *DUMMY_DATA) send(conn, ACK, 0x60, 0x01, data=raw, addr=args.a) else: print("This simulator only replies to cmd=0x60 cmdsub=0x01 requests...") if __name__ == "__main__": main()
-
-
- Conform Public RS485 Protocol 1V2.pdf “Public Solar Inverter Communication Protocol (Version 1.2)” van Delta Energy Systems, paragraaf 8.6 (Solivia 2.5 EU G3 = Variant 15 de DELTA_RPI_STRUCT in python script delta-solivia2.5.py aangepast
#!/usr/bin/python3 # -*- coding: utf-8 -*- import time import binascii import struct import serial import sys import os import signal from datetime import datetime from argparse import ArgumentParser from pprint import pprint import crc16 def ma_mi_bf(data): ma, mi, bf = struct.unpack('>BBB', data) return '{:02d}.{:02d}.{:02d}'.format(ma, mi, bf) DEBUG=False READ_BYTES = 1024 STX=0x02 ETX=0x03 ENQ=0x05 ACK=0x06 NAK=0x15 # Variables in the data-block of a Delta RPI M-series inverter, # as far as I've been able to establish their meaning. # The fields for each variable are as follows: # name, struct, size in bytes, decoder, multiplier-exponent (10^x), unit, SunSpec equivalent DELTA_RPI = ( ("SAP part number", "11s", str), ("SAP serial number", "18s", str), ("SAP date code", "4s", binascii.hexlify), ("SAP revision", "2s", binascii.hexlify), ("Software Revision AC control", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision DC control", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision display", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Software Revision SC control", "3s", ma_mi_bf, 0, "Major,Minor,Bug fixing"), ("Solar Voltage at Input 1", "H", int, 0, "V"), ("Solar Current at Input 1", "H", int, 0, "A"), ("Solar Isolation resistance at Input 1", "H", int, 0, "kOhm"), ("Calculated temperature at ntc (DC side)", "h", int, 0, "C"), ("Solar Input MOV resistance", "H", int, 0, "kOhm"), ("AC Current", "H", int, 0, "A"), ("AC Voltage", "H", int, 0, "V"), ("AC Power", "H", int, 0, "W"), ("AC Frequency", "H", int, 0, "Hz"), ("Calculated temperature at ntc (AC side)", "h", int, 0, "C"), ("SC Grid Voltage", "H", int, 0, "V"), ("SC Grid ens Frequency", "H", int, 0, "Hz"), ("SC Grid DC injection current", "H", int, 0, "A"), ("AC Grid Voltage", "H", int, 0, "V"), ("AC Grid Frequency", "H", int, 0, "Hz"), ("AC Grid DC injection current", "H", int, 0, "A"), ("Day Supplied ac energy", "H", int, 0, "Wh"), ("Inverter runtime", "H", int, 0, "Minutes"), ("Maximum ac current of today", "H", int, 0, "A"), ("Minimum ac voltage of today", "H", int, 0, "V"), ("Maximum ac voltage of today", "H", int, 0, "V"), ("Maximum ac power of today", "H", int, 0, "W"), ("Minimum ac frequency of today", "H", int, 0, "Hz"), ("Maximum ac frequency of today", "H", int, 0, "Hz"), ("Supplied ac energy", "L", int, 0, "kWh"), ("Inverter runtime", "L", int, 0, "Hours"), ("Maximum solar 1 input current", "H", int, 0, "A"), ("Maximum solar 1 input voltage", "H", int, 0, "V"), ("Maximum solar 1 input power", "H", int, 0, "W"), ("Minimum Isolation resistance solar 1", "H", int, 0, "kOhm"), ("Maximum Isolation resistance solar 1", "H", int, 0, "kOhm"), ("Alarm status", "1s", binascii.hexlify), ("Status DC input", "1s", binascii.hexlify), ("Limits DC input", "1s", binascii.hexlify), ("Status AC output", "1s", binascii.hexlify), ("Limits AC output", "1s", binascii.hexlify), ("Warning status", "1s", binascii.hexlify), ("DC hardware failure", "1s", binascii.hexlify), ("AC hardware failure", "1s", binascii.hexlify), ("SC hardware failure", "1s", binascii.hexlify), ("Internal Bulk failure", "1s", binascii.hexlify), ("Internal communications failure", "1s", binascii.hexlify), ("AC hardware disturbance", "1s", binascii.hexlify), ("DC HW stage error", "1s", binascii.hexlify), ("Calibration Status", "1s", binascii.hexlify), ("Neutral error", "1s", binascii.hexlify), ("History status messages", "20s", binascii.hexlify), ) DELTA_RPI_STRUCT = '>' + ''.join([item[1] for item in DELTA_RPI]) DUMMY_DATA = ( b'802FA0E1000', # SAP part number b'O1S16300040WH', # SAP serial number b'0901', # SAP date code b'0\x00', # SAP revision b'\x01#', # DSP FW Rev b'\x0f0', # DSP FW Date b'\x01\r', # Redundant MCU FW Rev b'\x0f\x0e', # Redundant MCU FW Date b'\x01\x10', # Display MCU FW Rev b'\x0f0', # Display MCU FW Date b'\x00\x00', # Display WebPage Ctrl FW Rev b'\x00\x00', # Display WebPage Ctrl FW Date b'\x00\x00', # Display WiFi Ctrl FW Rev b'\x00\x00', # Display WiFi Ctrl FW Date 0, # AC Voltage(Phase1) 0, # AC Current(Phase1) 0, # AC Power(Phase1) 0, # AC Frequency(Phase1) 0, # AC Voltage(Phase1) [Redundant] 0, # AC Frequency(Phase1) [Redundant] 0, # AC Voltage(Phase2) 0, # AC Current(Phase2) 0, # AC Power(Phase2) 0, # AC Frequency(Phase2) 0, # AC Voltage(Phase2) [Redundant] 0, # AC Frequency(Phase2) [Redundant] 0, # AC Voltage(Phase3) 0, # AC Current(Phase3) 0, # AC Power(Phase3) 0, # AC Frequency(Phase3) 0, # AC Voltage(Phase3) [Redundant] 0, # AC Frequency(Phase3) [Redundant] 0, # Solar Voltage at Input 1 0, # Solar Current at Input 1 0, # Solar Power at Input 1 0, # Solar Voltage at Input 2 0, # Solar Current at Input 2 0, # Solar Power at Input 2 0, # ACPower 0, # (+) Bus Voltage 0, # (-) Bus Voltage 0, # Supplied ac energy today 0, # Inverter runtime today 0, # Supplied ac energy (total) 0, # Inverter runtime (total) 0, # Calculated temperature inside rack 0, # Status AC Output 1 0, # Status AC Output 2 0, # Status AC Output 3 0, # Status AC Output 4 0, # Status DC Input 1 0, # Status DC Input 2 0, # Error Status 0, # Error Status AC 1 0, # Global Error 1 0, # CPU Error 0, # Global Error 2 0, # Limits AC output 1 0, # Limits AC output 2 0, # Global Error 3 0, # Limits DC 1 0, # Limits DC 2 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', # History status messages ) def signal_handler(signal, frame): ''' Catch SIGINT/SIGTERM/SIGKILL and exit gracefully ''' print("Stop requested...") sys.exit(0) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) def send(conn, req, cmd, subcmd, data=b'', addr=1): """ Send cmd/subcmd (e.g. 0x60/0x01) and optional data to the RS485 bus """ assert req in (ENQ, ACK, NAK) # req should be one of ENQ, ACK, NAK msg = struct.pack('BBBBB', req, addr, 2 + len(data), cmd, subcmd) if len(data) > 0: msg = struct.pack('5s%ds' % len(data), msg, data) crcval = crc16.calcData(msg) lsb = crcval & (0xff) msb = (crcval >> 8) & 0xff data = struct.pack('B%dsBBB' % len(msg), STX, msg, lsb, msb, ETX) if DEBUG: print(">>> SEND:", binascii.hexlify(msg), "=>", binascii.hexlify(data)) conn.write(data) conn.flush() def receive(conn): """ Attempt to read messages from a serial connection """ data = bytearray() while True: buf = conn.read(READ_BYTES) if buf: if DEBUG: print(">>> RAW RECEIVE:", buf) data.extend(buf) if (not buf) or len(buf) < READ_BYTES: break idx = 0 while idx + 9 <= len(data): if data[idx] != STX: idx += 1 continue stx, req, addr, size = struct.unpack('>BBBB', data[idx:idx+4]) if req not in (ENQ, ACK, NAK): print("Bad req value: {:02x} (should be one of ENQ/ACK/NAK)".format(req)) idx += 1 continue if idx + 4 + size >= len(data): print("Can't read %d bytes from buffer" % size) idx += 1 continue msg, lsb, msb, etx = struct.unpack('>%dsBBB' % size, data[idx+4:idx+7+size]) if etx != ETX: print("Bad ETX value: {:02x}".format(etx)) idx += 1 continue crc_calc = crc16.calcData(data[idx+1:idx+4+size]) crc_msg = msb << 8 | lsb if crc_calc != crc_msg: print("Bad CRC check: %s <> %s" % (binascii.hexlify(crc_calc), binascii.hexlify(crc_msg))) idx += 1 continue if DEBUG: print(">>> RECV:", binascii.hexlify(data), "=>", binascii.hexlify(msg)) yield { "stx": stx, "req": req, "addr": addr, "size": size, "msg": msg, "lsb": lsb, "msb": msb, "etx": etx, } idx += 4 + size def decode_msg(data): req = data['req'] cmd, cmdsub = struct.unpack('>BB', data['msg'][0:2]) data['cmd'] = cmd data['cmdsub'] = cmdsub data['raw'] = data['msg'][2:] if req == NAK: print("NAK value received: cmd/subcmd request was invalid".format(req)) elif req == ENQ: if DEBUG: print("ENQ value received: request from master (datalogger)") elif req == ACK: if DEBUG: print("ACK value received: response from slave (inverter)") data['values'] = struct.unpack(DELTA_RPI_STRUCT, data['raw']) if DEBUG: pprint(data) return data def main(): global DEBUG, MODE parser = ArgumentParser(description='Delta inverter simulator (slave mode) or datalogger (master mode) for RPI M8A') parser.add_argument('-a', metavar='ADDRESS', type=int, default=1, help='slave address [default: 1]') parser.add_argument('-d', metavar='DEVICE', default='/dev/ttyUSB0', help='serial device port [default: /dev/ttyUSB0]') parser.add_argument('-b', metavar='BAUDRATE', default=9600, help='baud rate [default: 9600]') parser.add_argument('-t', metavar='TIMEOUT', type=float, default=2.0, help='timeout, in seconds (can be fractional, such as 1.5) [default: 2.0]') parser.add_argument('--debug', action="store_true", help='show debug information') parser.add_argument('mode', metavar='MODE', choices=['master', 'slave'], help='mode can either be "master" or "slave"') args = parser.parse_args() DEBUG = args.debug MODE = args.mode conn = serial.Serial(args.d, args.b, timeout=args.t); conn.flushOutput() conn.flushInput() if MODE == 'master': send(conn, ENQ, 0x60, 0x01, addr=args.a) time.sleep(0.1) for data in receive(conn): if MODE == 'master' and data['addr'] == args.a and data['req'] in (ACK, NAK,): d = decode_msg(data) if d['req'] == ACK: if not (d['cmd'] == 0x60 and d['cmdsub'] == 0x01): print("Can't decode request cmd=0x%02X, cmdsub=0x%02X" % (d['cmd'], d['cmdsub'])) print("The only supported request is cmd=0x60, cmdsub=0x01") continue # print(61 * '=') print('{') print('"inverter": 2,') print('"Datetime": "', datetime.now(), '",') for i, item in enumerate(DELTA_RPI): label = item[0] decoder = item[2] scale = item[3] if len(item) > 3 else 0 units = item[4] if len(item) > 4 else '' value = decoder(data['values'][i]) if decoder == float: value = value * pow(10, scale) # print('%-40s %20s %-10s' % (label, value, units)) print(' "' + label + '": [') print(' {') print(' "Value": ', end='') if isinstance(value, str) : print('"', end='') print(value, end='') print('",') elif isinstance(value, int) : print(value, end='') print(',') else: print('"', end='') print(value.decode('ascii'), end='') print('",') print('"Units": "' + units + '"') print('}') print('],') print(' "end": "TRUE"') print('}') if MODE == 'slave' and data['addr'] == args.a and data['req'] in (ENQ,): d = decode_msg(data) if d['cmd'] == 0x60 and d['cmdsub'] == 0x01: raw = struct.pack(DELTA_RPI_STRUCT, *DUMMY_DATA) send(conn, ACK, 0x60, 0x01, data=raw, addr=args.a) else: print("This simulator only replies to cmd=0x60 cmdsub=0x01 requests...") if __name__ == "__main__": main()
-
-
-
- met dmesg gechecked op welke USB poort de FT232RL aangesloten zit
dmesg [155013.243404] usb 3-1: new full-speed USB device number 5 using ohci_hcd [155013.485049] usb 3-1: New USB device found, idVendor=0403, idProduct=6001 [155013.491972] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [155013.499369] usb 3-1: Product: FT232R USB UART [155013.503916] usb 3-1: Manufacturer: FTDI [155013.507892] usb 3-1: SerialNumber: A50285BI [155013.527773] ftdi_sio 3-1:1.0: FTDI USB Serial Device converter detected [155013.549271] usb 3-1: Detected FT232RL [155013.553223] usb 3-1: Number of endpoints 2 [155013.557467] usb 3-1: Endpoint 1 MaxPacketSize 64 [155013.562236] usb 3-1: Endpoint 2 MaxPacketSize 64 [155013.567029] usb 3-1: Setting MaxPacketSize 64 [155013.585881] usb 3-1: FTDI USB Serial Device converter now attached to ttyUSB0
-
- M.b.v. het in de zip-file bijgesloten script
unset_serial_ctrlchars.sh
de USB poort vrijgemaakt van ctrl karakters
- M.b.v. het in de zip-file bijgesloten script
foobar:~/python/delta-rpi# ls -lia total 112 57353 drwxr-xr-x 4 root root 4096 Mar 23 10:05 . 57352 drwxr-xr-x 4 root root 4096 Mar 23 09:59 .. 57354 drwxr-xr-x 8 root root 4096 Mar 17 15:12 .git 57428 -rw-r--r-- 1 root root 1067 Mar 17 15:12 LICENSE 57429 -rw-r--r-- 1 root root 2721 Mar 17 15:12 README.md 57434 drwxr-xr-x 2 root root 4096 Mar 17 15:32 __pycache__ 57430 -rw-r--r-- 1 root root 3816 Mar 17 15:12 crc16.py 57431 -rw-r--r-- 1 root root 11970 Mar 17 15:12 delta-rpi.py 57620 -rwxrwxrwx 1 root root 411 Mar 19 12:48 delta-solivia.sh 57621 -rw-r--r-- 1 root root 12662 Mar 19 10:57 delta-solivia2.5.py 57619 -rw-r--r-- 1 root root 12041 Mar 18 10:20 delta-solivia3.0.backup 57618 -rw-r--r-- 1 root root 12913 Mar 18 16:09 delta-solivia3.0.py 57622 -rw-r--r-- 1 root root 12385 Mar 18 11:52 hhh 57432 -rw-r--r-- 1 root root 74 Mar 17 15:12 tty.settings 57433 -rwxr-xr-x 1 root root 649 Mar 17 15:12 unset_serial_ctrlchars.sh foobar:~/python/delta-rpi# ./unset_serial_ctrlchars.sh /dev/ttyUSB0
- JSON output gegenereerd met:
-
python3 /root/python/delta-rpi/delta-solivia3.0.py -d /dev/ttyUSB0 -b 19200 -a 1 master
-d /dev/ttyUSB0, de USB poort waarop de FT232RL adapter op aangesloten zit (uit vorige stap)
-b 19200, baudrate conform instellingen Omvormer
-a 1, RS485 adres van Omvormer
master, python script gedraagt zich als master (Let op! Slave mode niet geïmplementeerd in script voor Delta Solivia 3.0 of 2.5)
-
- JSON output geknipt in Terminal venster
{ "inverter": 1, "Datetime": "2021-03-23 19:41:10.373687", "SAP part number": [ { "Value": "b'EOE46010287'", "Units": "" } ], "SAP serial number": [ { "Value": "b'220287161303000538'", "Units": "" } ], "SAP date code": [ { "Value": "31333033", "Units": "" } ], "SAP revision": [ { "Value": "3136", "Units": "" } ], "Software Revision System controller": [ { "Value": "01.02.37", "Units": "Major,Minor,Bug fixing" } ], "Software Revision Power controller": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision ENS controller": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision Watch dog controller": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision DC controller": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision DC 1": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision DC 2": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision DC 3": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision AC": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision AC 1": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision AC 2": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision AC 3": [ { "Value": "00.00.00", "Units": "Major,Minor,Bug fixing" } ], "Software Revision reserved": [ { "Value": "00.01.00", "Units": "Major,Minor,Bug fixing" } ], "Solar Power at Input 1": [ { "Value": 0, "Units": "W" } ], "Solar Voltage at Input 1": [ { "Value": 0, "Units": "V" } ], "Solar Current at Input 1": [ { "Value": 0, "Units": "A" } ], "Solar Power at Input 2": [ { "Value": 0, "Units": "W" } ], "Solar Voltage at Input 2": [ { "Value": 0, "Units": "V" } ], "Solar Current at Input 2": [ { "Value": 0, "Units": "A" } ], "Solar Power at Input 3": [ { "Value": 0, "Units": "W" } ], "Solar Voltage at Input 3": [ { "Value": 0, "Units": "V" } ], "Solar Current at Input 3": [ { "Value": 0, "Units": "A" } ], "AC Current(Phase1)": [ { "Value": 0, "Units": "A" } ], "AC Voltage(Phase1)": [ { "Value": 0, "Units": "V" } ], "AC Frequency(Phase1)": [ { "Value": 0, "Units": "Hz" } ], "AC Active Power(Phase1)": [ { "Value": 0, "Units": "W" } ], "AC Reactive Power(Phase1)": [ { "Value": 0, "Units": "VAR" } ], "AC Current(Phase2)": [ { "Value": 0, "Units": "A" } ], "AC Voltage(Phase2)": [ { "Value": 0, "Units": "V" } ], "AC Frequency(Phase2)": [ { "Value": 0, "Units": "Hz" } ], "AC Active Power(Phase2)": [ { "Value": 0, "Units": "W" } ], "AC Reactive Power(Phase2)": [ { "Value": 0, "Units": "VAR" } ], "AC Current(Phase3)": [ { "Value": 0, "Units": "A" } ], "AC Voltage(Phase3)": [ { "Value": 0, "Units": "V" } ], "AC Frequency(Phase3)": [ { "Value": 0, "Units": "Hz" } ], "AC Active Power(Phase3)": [ { "Value": 0, "Units": "W" } ], "AC Reactive Power(Phase3)": [ { "Value": 0, "Units": "VAR" } ], "Solar Isolation plus": [ { "Value": 0, "Units": "kOhm" } ], "Solar Isolation minus": [ { "Value": 0, "Units": "kOhm" } ], "Temperature amb": [ { "Value": 0, "Units": "Celsius" } ], "Temperature heatsink": [ { "Value": 0, "Units": "Celsius" } ], "Supplied ac energy (total)": [ { "Value": 5835591, "Units": "Wh" } ], "Inverter runtime (total)": [ { "Value": 655794, "Units": "Minutes" } ], "Status AC Output 1": [ { "Value": 4, "Units": "" } ], "Status AC Output 2": [ { "Value": 0, "Units": "" } ], "Status AC Output 3": [ { "Value": 0, "Units": "" } ], "Status AC Output 4": [ { "Value": 0, "Units": "" } ], "Internal Status 1": [ { "Value": 0, "Units": "" } ], "Internal Status 2": [ { "Value": 0, "Units": "" } ], "Internal Status 3": [ { "Value": 0, "Units": "" } ], "Internal Status 4": [ { "Value": 0, "Units": "" } ], "Supplied ac energy (day)": [ { "Value": 9233, "Units": "Wh" } ], "Inverter runtime (day)": [ { "Value": 745, "Units": "Minute" } ], "reserved": [ { "Value": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Units": "" } ], "end": "TRUE" }
-
- en gevalideerd met https://jsonlint.com/
- Mosquitto MQTT cliënt geïnstalleerd met:
Shell script gemaakt en crontab job aangemaaktwget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key apt-key add mosquitto-repo.gpg.key apt-get update apt-get install mosquitto-clients
foobar:~/python/delta-rpi# cat delta-solivia.sh #!/bin/bash export PYTHONIOENCODING=utf8 # Solivia 3.0 message=`python3 /root/python/delta-rpi/delta-solivia3.0.py -d /dev/ttyUSB0 -b 19200 -a 1 master` mosquitto_pub -h 192.168.2.29 -p 1883 -m "$message" -t delta/inverter/1 # Solivia 2.5 message=`python3 /root/python/delta-rpi/delta-solivia2.5.py -d /dev/ttyUSB0 -b 19200 -a 2 master` mosquitto_pub -h 192.168.2.29 -p 1883 -m "$message" -t delta/inverter/2
-
- Zet execute rechten op het script met:
chmod 744 delta-solivia.sh
- crontab van root aangepast met
- Zet execute rechten op het script met:
crontab -e # Edit this file to introduce tasks to be run by cron. # # Each task to run has to be defined through a single line # indicating with different fields when the task will be run # and what command to run for the task # # To define the time you can provide concrete values for # minute (m), hour (h), day of month (dom), month (mon), # and day of week (dow) or use '*' in these fields (for 'any').# # Notice that tasks will be started based on the cron's system # daemon's notion of time and timezones. # # Output of the crontab jobs (including errors) is sent through # email to the user the crontab file belongs to (unless redirected). # # For example, you can run a backup of all your user accounts # at 5 a.m every week with: # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/ # # For more information see the manual pages of crontab(5) and cron(8) # # m h dom mon dow command */5 * * * * /root/python/delta-rpi/delta-solivia.sh
en de regel */5 * * * * /root/python/delta-rpi/delta-solivia.sh toegevoegd zodat iedere 5 minuten de omvormers uitgelezen worden en de JSON output middels MQTT op de topic delta/inverter/* wordt geplaatst
Node-red flow aangemaakt:- Deze flow zorg ervoor dat de JSON berichten van de omvormers resulteren in een record in een tabel in de MariaDb database
Node Doel MQTT in Leest topic delta/inverter/* uit Inverter Op basis van inverternummer (delta/inverter/1 of /delta/inverter/2) wordt de juiste functie aangeroepen om de JSON payload om te zetten in een SQL-statement Inverter 1 Zet JSON payload om naar een SQL-statement Inverter 2 Zet JSON payload om naar een SQL-statement Database Database connectie naar MariaDB Msg.payload Voor debugging doeleinden -
- Export van flow
[{"id":"de7bf9f6.4c6c48","type":"tab","label":"PVOutput","disabled":false,"info":""},{"id":"649056ba.f481e8","type":"mqtt in","z":"de7bf9f6.4c6c48","name":"MQTT in","topic":"delta/inverter/#","qos":"2","datatype":"json","broker":"2483a28c.e1053e","x":100,"y":240,"wires":[["7c9c8973.c0afe8"]]},{"id":"d14b2151.f70ab","type":"function","z":"de7bf9f6.4c6c48","name":"Inverter 1","func":"// https://pvoutput.org/service/r2/addoutput.jsp?key=Your-API-Key&sid=Your-System-Id&d=20100830&g=12000\n//var apiKey=\"753e8f6c6b5c5be53ea92a1c3d1e48f46d302ae7\";\n//var systemId=\"82717\";\n//var url = \"https://pvoutput.org/service/r2/addoutput.jsp?key=\" + apiKey + \"&sid=\" + systemId;\nvar sql = \"insert into pv (`inverter`, `sample datetime`, `Solar Power`, `Solar Voltage`, `Solar Current`, `AC Power`, `AC Voltage`, `AC Current`, `AC Frequency`, `Temperature`, `Status AC Output`, `Supplied AC Energy`, `Inverter runtime`,`delta Supplied AC Energy`)\"\nsql = sql + \"values (:inverter, :sample_datetime, :Solar_Power, :Solar_Voltage, :Solar_Current, :AC_Power, :AC_Voltage, :AC_Current, :AC_Frequency, :Temperature, :Status_AC_Output, :Supplied_AC_Energy, :Inverter_runtime, :delta_Supplied_AC_Energy);\";\n\n//var datetime=msg.payload.Datetime.substr(0,4) + msg.payload.Datetime.substr(5,2) + msg.payload.Datetime.substr(8,2);\n//var generated=msg.payload[\"Supplied ac energy (day)\"][0].Value;\n\n// url=url + \"&d=\" + datetime + \"&g=\" + generated + \"&tm=\" + flow.get(\"minTemp\") + \"&tx=\" + flow.get(\"maxTemp\") + \"&cd=\" + flow.get(\"situatie\");\n\nmsg.payload.inverter=1;\nmsg.payload.sample_datetime=msg.payload.Datetime;\nmsg.payload.Solar_Power=msg.payload[\"Solar Power at Input 1\"][0].Value;\nmsg.payload.Solar_Voltage=msg.payload[\"Solar Voltage at Input 1\"][0].Value;\nmsg.payload.Solar_Current=msg.payload[\"Solar Current at Input 1\"][0].Value;\nmsg.payload.AC_Power=msg.payload[\"AC Active Power(Phase1)\"][0].Value;\nmsg.payload.AC_Voltage=msg.payload[\"AC Voltage(Phase1)\"][0].Value;\nmsg.payload.AC_Current=msg.payload[\"AC Current(Phase1)\"][0].Value;\nmsg.payload.AC_Frequency=msg.payload[\"AC Frequency(Phase1)\"][0].Value;\nmsg.payload.Temperature=msg.payload[\"Temperature amb\"][0].Value;\nmsg.payload.Status_AC_Output=msg.payload[\"Status AC Output 1\"][0].Value;\nmsg.payload.Supplied_AC_Energy=msg.payload[\"Supplied ac energy (day)\"][0].Value;\nmsg.payload.Inverter_runtime=msg.payload[\"Inverter runtime (day)\"][0].Value;\nmsg.payload.delta_Supplied_AC_Energy=(msg.payload[\"Supplied ac energy (day)\"][0].Value - flow.get(\"Inverter1\")/1000);\n\nmsg.topic=sql;\nflow.set(\"Inverter1\",msg.payload[\"Supplied ac energy (day)\"][0].Value);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":540,"y":200,"wires":[["bf2791ef.c0254"]]},{"id":"10851652.ea8c9a","type":"debug","z":"de7bf9f6.4c6c48","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":930,"y":240,"wires":[]},{"id":"7c9c8973.c0afe8","type":"switch","z":"de7bf9f6.4c6c48","name":"Inverter","property":"payload.inverter","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"2","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":300,"y":240,"wires":[["d14b2151.f70ab"],["34049ab1.066266"]]},{"id":"34049ab1.066266","type":"function","z":"de7bf9f6.4c6c48","name":"Inverter 2","func":"var sql = \"insert into pv (`inverter`, `sample datetime`, `Solar Voltage`, `Solar Current`, `AC Power`, `AC Voltage`, `AC Current`, `AC Frequency`, `Temperature`, `Status AC Output`, `Supplied AC Energy`, `Inverter runtime`, `delta Supplied AC Energy`)\"\nsql = sql + \"values (:inverter, :sample_datetime, :Solar_Voltage, :Solar_Current, :AC_Power, :AC_Voltage, :AC_Current, :AC_Frequency, :Temperature, :Status_AC_Output, :Supplied_AC_Energy, :Inverter_runtime, :delta_Supplied_AC_Energy);\";\n\nmsg.payload.inverter=2;\nmsg.payload.sample_datetime=msg.payload.Datetime;\nmsg.payload.Solar_Voltage=msg.payload[\"Solar Voltage at Input 1\"][0].Value;\nmsg.payload.Solar_Current=msg.payload[\"Solar Current at Input 1\"][0].Value;\nmsg.payload.AC_Power=msg.payload[\"AC Power\"][0].Value;\nmsg.payload.AC_Voltage=msg.payload[\"AC Voltage\"][0].Value;\nmsg.payload.AC_Current=msg.payload[\"AC Current\"][0].Value;\nmsg.payload.AC_Frequency=msg.payload[\"AC Frequency\"][0].Value;\nmsg.payload.Temperature=msg.payload[\"Calculated temperature at ntc (AC side)\"][0].Value;\nmsg.payload.Status_AC_Output=msg.payload[\"Status AC output\"][0].Value;\nmsg.payload.Supplied_AC_Energy=msg.payload[\"Supplied ac energy\"][0].Value;\nmsg.payload.Inverter_runtime=msg.payload[\"Inverter runtime\"][0].Value * 60;\nmsg.payload.delta_Supplied_AC_Energy=(msg.payload[\"Supplied ac Energy\"][0].Value - flow.get(\"Inverter2\")/1000);\n\nmsg.topic=sql;\nflow.set(\"Inverter2\",msg.payload[\"Supplied ac Energy\"][0].Value);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":540,"y":280,"wires":[["bf2791ef.c0254"]]},{"id":"bf2791ef.c0254","type":"mysql","z":"de7bf9f6.4c6c48","mydb":"1d3f61d0.71501e","name":"Database","x":760,"y":240,"wires":[["10851652.ea8c9a"]]},{"id":"594706f6.cf8248","type":"inject","z":"de7bf9f6.4c6c48","name":"Run once","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":100,"y":100,"wires":[["cc4ae98d.1d2e68","20a1c55d.cfa38a"]]},{"id":"cc4ae98d.1d2e68","type":"function","z":"de7bf9f6.4c6c48","name":"Get last added values inverter 1","func":"var sql = \"select * from pv where `sample datetime` = (select max(`sample datetime`) from pv where inverter = 1) and inverter = 1;\"\n\nmsg.topic=sql;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":370,"y":60,"wires":[["a49133f.7f24cd"]]},{"id":"a49133f.7f24cd","type":"mysql","z":"de7bf9f6.4c6c48","mydb":"4167a952.5f75b8","name":"Database","x":620,"y":60,"wires":[["27db1dc4.892dc2"]]},{"id":"27db1dc4.892dc2","type":"function","z":"de7bf9f6.4c6c48","name":"Set previous meter readings","func":"flow.set(\"Inverter1\",msg.payload[0][\"Supplied AC Energy\"]);\n\n// node.warn(flow.get(\"Inverter1\"));\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":880,"y":60,"wires":[[]]},{"id":"20a1c55d.cfa38a","type":"function","z":"de7bf9f6.4c6c48","name":"Get last added values inverter 2","func":"var sql = \"select * from pv where `sample datetime` = (select max(`sample datetime`) from pv where inverter = 2) and inverter = 2;\"\n\nmsg.topic=sql;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":370,"y":140,"wires":[["7408d3d0.0cf0ec"]]},{"id":"7408d3d0.0cf0ec","type":"mysql","z":"de7bf9f6.4c6c48","mydb":"4167a952.5f75b8","name":"Database","x":620,"y":140,"wires":[["7ebd4e54.aa27c"]]},{"id":"7ebd4e54.aa27c","type":"function","z":"de7bf9f6.4c6c48","name":"Set previous meter readings","func":"flow.set(\"Inverter2\",msg.payload[0][\"Supplied AC Energy\"]);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":880,"y":140,"wires":[[]]},{"id":"2483a28c.e1053e","type":"mqtt-broker","z":"","name":"pi","broker":"192.168.2.29","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"1d3f61d0.71501e","type":"MySQLdatabase","z":"","name":"mysql","host":"localhost","port":"3306","db":"energy","tz":"","charset":"UTF8"},{"id":"4167a952.5f75b8","type":"MySQLdatabase","z":"","name":"mysql","host":"localhost","port":"3306","db":"energy","tz":"","charset":"UTF8"}]
MariaDB geconfigureerd- Mysql tool gestart met
mysql -u root -p
- Database “
energy
” aangemaakt met
create database energy;
-
- Tabel “
pv
” aangemaakt met
- Tabel “
use energy; CREATE TABLE `pv` ( `inverter` int(1) NOT NULL, `sample datetime` datetime NOT NULL, `Solar Power` int(32) DEFAULT NULL, `Solar Power unit` char(6) DEFAULT 'W', `Solar Voltage` int(32) DEFAULT NULL, `Solar Voltage unit` char(6) DEFAULT 'V', `Solar Current` int(32) DEFAULT NULL, `Solar Current unit` char(6) DEFAULT 'A', `AC Power` int(32) DEFAULT NULL, `AC Power unit` char(6) DEFAULT 'W', `AC Voltage` int(32) DEFAULT NULL, `AC Voltage unit` char(6) DEFAULT 'V', `AC Current` int(32) DEFAULT NULL, `AC Current unit` char(6) DEFAULT 'A', `AC Frequency` int(32) DEFAULT NULL, `AC Frequency unit` char(6) DEFAULT 'Hz', `Temperature` int(32) DEFAULT NULL, `Temperature unit` char(6) DEFAULT 'C', `Status AC Output` int(32) DEFAULT NULL, `Supplied AC Energy` int(32) DEFAULT NULL, `Supplied AC Energy unit` char(6) DEFAULT 'Wh', `Inverter runtime` int(32) DEFAULT NULL, `Inverter runtime unit` char(6) DEFAULT 'Minute', PRIMARY KEY (`inverter`,`sample datetime`) );
-
- gebruiker “googledata” aangemaakt met select rechten op tabel pv
create user googledata@% identified by 'password'; grant select on energy.* to googledata@%;
Firewall aangepast en poort naar MariaDB database opengezet: Dashboard aangemaakt met Google Data Studio:-
- Login in met je Google account op https://datastudio.google.com/
- Voor de 3 scoreboards heb ik de volgende MySQL verbinding gedefinieerd
- Op dezelfde wijze heb ik de volgende MySQL verbindingen gemaakt voor elementen op het dashboard
Dashboard element SQL-query Inverter: 1,2 Vandaag, Deze maand, Totaal select inverter, date(`sample datetime`) as datum, max(time(`sample datetime`)) as tijd, round((max(`Supplied AC Energy`)-min(`Supplied AC Energy`))/10) as energy, `Supplied AC Energy unit` as unit from pv where inverter = 2 group by date(`sample datetime`) union select inverter, date(`sample datetime`) as datum, max(time(`sample datetime`)) as tijd, round(max(`Supplied AC Energy`)/1000) as energy, `Supplied AC Energy unit` as unit from pv where inverter = 1 group by date(`sample datetime`); Laatste update select max(time(`sample datetime`)) as tijd from pv where date(`sample datetime`) = curdate() Inverter temperature, Inverter voltage, Inverter solarpower select inverter, temperature, `AC Voltage` as voltage, `Solar Power` as solarpower from pv where `sample datetime` = (select max(`sample datetime`) from pv where inverter = 1) union select inverter, temperature, `AC Voltage` as voltage, `Solar Power` as solarpower from pv where `sample datetime` = (select max(`sample datetime`) from pv where inverter = 2) Opgewekte energie Inverter 1, Opgewekte energie Inverter 2 select inverter, `sample datetime` as T, `Supplied AC Energy`/1000 as A, (select min(`Supplied AC Energy`/1000) from pv where inverter = 1 and date(`sample datetime`) = curdate()) as B, (`Supplied AC Energy` – (select min(`Supplied AC Energy`) from pv where inverter = 1 and date(`sample datetime`) = curdate()))/1000 as C from pv where inverter = 1 and date(`sample datetime`) = curdate(); - Hieronder screenshots van de configuratie van de verschillende dashboard onderdelen:
-
-
Dankjewel Syd, na hel lezen gelijk maar wat RS485 interface-jes besteld bij Aliexpr. Ik heb hier nog een Raspberry pi liggen voor de interfacing. Mooie apparaten die Delta solivia’s, wel jammer dat bij veel na verloop van tijd ze er mee stoppen met de foutmelding Relay-failure of AC-failure. Volgens mij ligt dit probleem bij de 1-polige Relays (2stuks) die erin zitten. Slijtage door vonken/ te weinig contact druk. Ik ben nog wel opzoek naar ideeen hoe dit te repareren (liefst zonder de hele print eruit te moeten halen). Heeft er iemand schema’s? Wat is een goed vervangend relais?
Bill, ik kan je helaas niet aan een schema helpen. En mijn twee ontvangers hebben tot nu toe geen problemen gegeven. Ik wel problemen met de (goedkope) RS485 interface. Niet onoverkomelijk gelukkig. De interface mist soms berichten of connectie. Vandaar dat ik een loop ingebouwd heb ik het script zodat 10x achterelkaar de omvormer wordt bevraagd. Maar dan nog mist hij soms het bericht. Het is aan te bevelen om een iets betere RS485 ontvanger te kopen als je ook deze problemen ervaart.
Hi mannen, die RS485 moet echt foutloos kunnen zijn. Zelf gebruik ik altijd een 3-draads systeem. Ik gebruik dus ook niet de getoonde converter, al kun je daarvan ook gewoon de min aansluiten als Gnd in de inverter.
Dat AC relais heb ik ook gehad bij een van de twee inverters die ik heb. Na een jaar of acht. Ik heb er een Omron relais in gezet, jammer genoeg net iets kleiner. Maar met betere specs. De hele print moest er uit. Aan lak over het koper van de print kon je zien dat de sporen, zeg maar vlakken, warm waren geweest. Omdat ik iets wilde leren, heb ik het relais open gemaakt en vond toen dat een van de contacted was verdraaid. Dus een hele inverter faalt omdat er een kwalitatief ondermaats relais in zit. Als consument ben je dan genept. Jij had redelijkerwijs mogen verwachten dat de inverter 20 jaar meegaat. Maar ook zo’n fabrikant heeft een fijn probleem, want redelijkerwijs had hij ook, zeker onbelast, het aantal schakelingen mogen verwachten. Niet erg fraai. Want als de inverter terug gaat, ben ik bang dat ik kosten ga krijgen die zo hoog zijn dat het niet meer loont. Geen beste beurt. Relais vervangen dus, al jaren geen problemen meer.