├── README.md └── solis-4g.py /README.md: -------------------------------------------------------------------------------- 1 | # Solis-4G RS485 - Openenergymonitor EmonHub 2 | Python project to Read Solis-4G inverter over RS485 and report to EmonHub 3 | 4 | 1. Save python script to ~/data/solis-4g.py 5 | 2. Configure Emonhub with the additional settings below 6 | 3. Call python file using Node-Red on emonpi / emon hub 7 | 8 | 9 | ## MinimalModbus installation 10 | Set emonpi into read-write mode 11 | Install minimalmodbus 12 | Set emonpi to read-only mode 13 | 14 | ``` 15 | rpi-rw 16 | sudo pip install -U minimalmodbus 17 | rpi-ro 18 | ``` 19 | 20 | ## EmonHub Configuration 21 | Note the information must be in the correct section for emonhub to work. 22 | Once the interfacer has been added, emonhub needs to be restarted. 23 | Node configuration updates will be detected when the configuration is updated. 24 | 25 | 1. Add to [interfacers] section: 26 | 27 | ``` 28 | [[mysocketlistener]] 29 | Type = EmonHubSocketInterfacer 30 | [[[init_settings]]] 31 | port_nb = 8080 32 | [[[runtimesettings]]] 33 | pubchannels = ToEmonCMS, 34 | ``` 35 | 36 | 37 | 2. Add to [nodes] section: 38 | 39 | ``` 40 | [[3]] 41 | nodename = solis4g-kw 42 | [[[rx]]] 43 | names = AllTimeEnergyKW,TodayKW 44 | datacodes = I, H 45 | scales = 1,0.1 46 | units = kW,kW 47 | 48 | [[4]] 49 | nodename = solis4g-realtime 50 | [[[rx]]] 51 | names = ACRealtimeW,RealtimeDCV,RealtimeDCI,InverterC,ACRealTimeF,ACRealTimeV,ACRealTimeI 52 | datacodes = I, H, H, H, H, H, H 53 | scales = 1,0.1,0.1,0.1,0.01,0.1,0.1 54 | units = W,V,A,C,Hz,V,A 55 | ``` 56 | 57 | ## Node-Red Configuration 58 | (Based on https://stackoverflow.com/questions/32057882/how-to-trigger-python-script-on-raspberry-pi-from-node-red) 59 | In a new flow: 60 | 1. Add an inject node, set it to automatically inject at start and set the node to collect data every 5 seconds 61 | 2. Add an external exec node and configure it to call: 62 | python ~/data/solis-4g.py 63 | 64 | -------------------------------------------------------------------------------- /solis-4g.py: -------------------------------------------------------------------------------- 1 | import minimalmodbus 2 | import socket 3 | import serial 4 | 5 | ### PROGRAM FLOW: 6 | ### - Collect Data from Solis-4G inverter 7 | ### - Convert to individual bytes 8 | ### - Construct 2 messages 9 | ### - KWH Totals only sent when inverter is running, so they are not reset to zero 10 | ### - All other 'live' data set to zero when inverter shuts down 11 | ### - Send Packets to EMONHUB 12 | ### 13 | ### EmonHub Node IDs: 14 | ### - NodeID 3: All time energy KWH / Today KWH (not sent overnight) 15 | ### - NodeID 4: Live Data Readings - Zeros sent overnight 16 | 17 | 18 | ### COLLECT DATA FROM SOLIS-4G INVERTER ### 19 | 20 | instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1) # port name, slave address 21 | 22 | instrument.serial.baudrate = 9600 # Baud 23 | instrument.serial.bytesize = 8 24 | instrument.serial.parity = serial.PARITY_NONE 25 | instrument.serial.stopbits = 1 26 | instrument.serial.timeout = 0.2 # seconds 27 | 28 | success = False # Intialise Success/Failure Flag to ensure full data is only uploaded if all data is received. 29 | 30 | try: 31 | Realtime_ACW = instrument.read_long(3004, functioncode=4, signed=False) #Read AC Watts as Unsigned 32-Bit 32 | 33 | Realtime_DCV = instrument.read_register(3021, numberOfDecimals=0, functioncode=4, signed=False) #Read DC Volts as Unsigned 16-Bit 34 | Realtime_DCI = instrument.read_register(3022, numberOfDecimals=0, functioncode=4, signed=False) #Read DC Current as Unsigned 16-Bit 35 | Realtime_ACV = instrument.read_register(3035, numberOfDecimals=0, functioncode=4, signed=False) #Read AC Volts as Unsigned 16-Bit 36 | Realtime_ACI = instrument.read_register(3038, numberOfDecimals=0, functioncode=4, signed=False) #Read AC Current as Unsigned 16-Bit 37 | Realtime_ACF = instrument.read_register(3042, numberOfDecimals=0, functioncode=4, signed=False) #Read AC Frequency as Unsigned 16-Bit 38 | Inverter_C = instrument.read_register(3041, numberOfDecimals=0, functioncode=4, signed=True) #Read Inverter Temperature as Signed 16-Bit 39 | 40 | AlltimeEnergy_KW = instrument.read_long(3008, functioncode=4, signed=False) # Read All Time Energy (KWH Total) as Unsigned 32-Bit 41 | Today_KW = instrument.read_register(3014, numberOfDecimals=0, functioncode=4, signed=False) # Read Today Energy (KWH Total) as 16-Bit 42 | 43 | ##Convert Data to Bytes 44 | A1 = Realtime_ACW % 256 45 | A2 = (Realtime_ACW >> 8) % 256 46 | A3 = (Realtime_ACW >> 16) % 256 47 | A4 = (Realtime_ACW >> 24) % 256 48 | 49 | B1 = AlltimeEnergy_KW % 256 50 | B2 = (AlltimeEnergy_KW >> 8) % 256 51 | B3 = (AlltimeEnergy_KW >> 16) % 256 52 | B4 = (AlltimeEnergy_KW >> 24) % 256 53 | 54 | C1 = Today_KW % 256 55 | C2 = (Today_KW >> 8) % 256 56 | 57 | D1 = Realtime_DCV % 256 58 | D2 = (Realtime_DCV >> 8) % 256 59 | 60 | E1 = Realtime_DCI % 256 61 | E2 = (Realtime_DCI >> 8) % 256 62 | 63 | F1 = Inverter_C % 256 64 | F2 = (Inverter_C >> 8) % 256 65 | 66 | G1 = Realtime_ACF % 256 67 | G2 = (Realtime_ACF >> 8) % 256 68 | 69 | H1 = Realtime_ACV % 256 70 | H2 = (Realtime_ACV >> 8) % 256 71 | 72 | I1 = Realtime_ACI % 256 73 | I2 = (Realtime_ACI >> 8) % 256 74 | 75 | ##Flag to stream all data to EmonHub 76 | success = True 77 | 78 | except: 79 | ##EXCEPTION WILL OCCUR WHEN INVERTER SHUTS DOWN WHEN PANELS ARE OFF 80 | 81 | A1 = 0 82 | A2 = 0 83 | A3 = 0 84 | A4 = 0 85 | 86 | ## Not sent when inverter turns off 87 | ## B1 = 0 88 | ## B2 = 0 89 | ## B3 = 0 90 | ## B4 = 0 91 | 92 | ## C1 = 0 93 | ## C2 = 0 94 | ## END NOT SENT WHEN INVERTER TURNS OFF 95 | 96 | D1 = 0 97 | D2 = 0 98 | 99 | E1 = 0 100 | E2 = 0 101 | 102 | F1 = 0 103 | F2 = 0 104 | 105 | G1 = 0 106 | G2 = 0 107 | 108 | H1 = 0 109 | H2 = 0 110 | 111 | I1 = 0 112 | I2 = 0 113 | 114 | ##Flag to stream restricted data to EmonHub 115 | success = False 116 | 117 | ### END COLLECT DATA FROM SOLIS-4G INVERTER ### 118 | 119 | 120 | ### STREAM RESULT TO EMONHUB ### 121 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #Initialise Socket 122 | s.connect(('localhost', 8080)) #Connect to Local EmonHub 123 | 124 | ## NOT SENT WHEN INVERTER TURNS OFF 125 | if success == True: 126 | s.sendall('03 ' + str(B1) + ' ' + str(B2) + ' ' + str(B3) + ' ' + str(B4) + ' ' + str(C1) + ' ' + str(C2) + '\r\n') 127 | 128 | s.sendall('04 ' + str(A1) + ' ' + str(A2) + ' ' + str(A3) + ' ' + str(A4) + ' ' + str(D1) + ' ' + str(D2) + ' ' + str(E1) + ' ' + str(E2) + ' ' + str(F1) + ' ' + str(F2) + ' ' + str(G1) + ' ' + str(G2) + ' ' + str(H1) + ' ' + str(H2) + ' ' + str(I1) + ' ' + str(I2) + '\r\n') 129 | s.close() 130 | ### END SEND TO EMON HUB # 131 | --------------------------------------------------------------------------------