Solar Inverter Monitoring

The data in my inverter is retrieved using Modbus RTU commands, generally via Serial. I have a Serial to Ethernet converter allowing me to interogate the hardware remotely, provided no other software is using the inverter. Every 10 seconds,  my software attempts to retrieve the data for the inverter from the Modbus Holding Registers. In this way, the Modbus side is abstracted from dealing with the data. Once the Modbus data is retrieved into an array, it is sent to a function called parse_dynamic, which does some simple calculations, generally moving from inverter units to SI units of some type. A segment of the code is shown below.

    def parse_dynamic(self,  response):       
        #x =0
        #for i in response:
        #    self.dbg_print (x, i)                   
        #    x=x+1
        global lasttime
        global today_Wh
        self.temp_heatsink = '{0}'.format(response[10])
        self.temp_internal = '{0}'.format(response[9])
        self.DC_bus_voltage = '{0}'.format(response[7] / 1.0)
        self.DC1_input_voltage = '{0}'.format(response[11] / 1.0)
        self.DC1_input_current= '{0}'.format(response[13] / 10.0)
        self.DC1_input_power= '{0}'.format(response[15] * 10.0)
        self.DC2_input_voltage = '{0}'.format(response[12] / 1.0)
        self.DC2_input_current= '{0}'.format(response[14] / 10.0)
        self.DC2_input_power= '{0}'.format(response[16] * 10.0)
            
        self.AC_voltage = '{0}'.format(response[2] / 1.0)
        self.AC_current= '{0}'.format(response[4] / 10.0)
        self.AC_power= '{0}'.format(response[0] * 10.0)
        self.AC_frequency= '{0}'.format(response[6] / 10.0)
        i = (response[17] << 16) + response[18]
        self.total_Wh = '{0}'.format(i) 

        if self.DC_bus_voltage > 0:
            if self.status == "Off":
                self.reconnected_time = datetime.datetime.now()
            self.status = "On"
        else:
            self.status = "Off"
            self.reconnected_time = ""

        wh = 0 
        if lasttime <> "":
            delta = datetime.datetime.now() - lasttime 
            if delta.days == 0:
                wh = float(self.AC_power) * delta.seconds / 3600.0
 

Missing from this function is the code to also determine the daily and absolute energy consumption of the inverter. Hey, we need to keep some things private.

Every 5 minutes when the inverter is generating energy, or when it has just turned off, we also wish to send the data to a site called PVOutput.ORG. This is automated, and allows people to compare the performance of my inverter with others throughout the country.

    if ((now.tm_min % 5 == 0) & (stats.status == 'On')) | (stats.status == 'Off')  :
        if runtime.status_sent == False:
            t_date = 'd={0}'.format(strftime('%Y%m%d'))
            t_time = 't={0}'.format(strftime('%H:%M'))
            t_energy = 'v1={0}'.format(int(stats.today_Wh) )
            #t_power = 'v2={0}'.format(runtime.p_accm/runtime.p_count)
            t_power = 'v2={0}'.format(p_inst)
            cmd = ['/usr/bin/curl',
                '-d', t_date,
                '-d', t_time,
                '-d', t_energy,
                '-d', t_power, 
                '-H', 'X-Pvoutput-Apikey: ' + PVOUTPUT_APIKEY, 
                '-H', 'X-Pvoutput-SystemId: ' + PVOUTPUT_SYSTEMID, 
                'http://pvoutput.org/service/r1/addstatus.jsp']
            l = 'NOW: p_inst = {0}W, p_avg = {1:.1f}W, e_total = {2}Wh, p_max {3}'.format(p_inst,  runtime.p_accm/runtime.p_count, stats.today_Wh, runtime.p_max)

When new data is retrieved, it is also sent to the PubSub engine in it’s entirity. The PubSub engine is described elsewhere.