Integrate solar inverter data through SBFspot

Some people use SBFspot to log the data from their inverters and possibly also export them to PVOutput.org. When you have an older SMA inverter (lacking the IP interface), you may access the inverter by means of Bluetooth, e.g. from a Raspberry Pi running SBFspot and openHAB.

I wrote a simple Python script that reads the data from the SQLite database managed by SBFspot and posts the relevant values to openHAB by means of the REST API.

Make sure to create the relevant Items first. I assign the proper semantic units to ensure proper UoM conversion where needed:

The Python script is located in the same folder as the SBFspot database (/home/openhabian/smadata/SBFspot_update_openHAB.py):

import sqlite3
from datetime import datetime
import requests

try:
    sqliteConnection = sqlite3.connect('SBFspot.db',
                             detect_types=sqlite3.PARSE_DECLTYPES |
                             sqlite3.PARSE_COLNAMES)
    cursor = sqliteConnection.cursor()

    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

except sqlite3.Error as error:
    print(f"[{timestamp}] Error while connecting to sqlite", error)
finally:
    if sqliteConnection:
        OPENHAB_REST_API = "http://localhost:8080/rest"
        headers_post = {'accept':'application./json','content-type':'text/plain'}

        # Select the latest update - max TimeStamp value:
        query = "SELECT MAX(TimeStamp) FROM SpotData"
        cursor.execute(query)
        result = cursor.fetchone()
        latestTimeStamp = result[0]

        # Note: there are other fields in the SpotData table (e.g., for 3-phase inverters)
        query = f"SELECT TimeStamp,Pdc1,Pdc2,Idc1,Idc2,Udc1,Udc2,Pac1,Iac1,Uac1,EToday,ETotal,Frequency,OperatingTime,FeedInTime,Status,GridRelay,Temperature FROM SpotData WHERE TimeStamp = {latestTimeStamp}"
        cursor.execute(query)
        result = cursor.fetchone()
        print(f"[{timestamp}]", result)

        # Note: update the variable list if the fields in the query have been changed:
        myTimeStamp, myPdc1, myPdc2, myIdc1, myIdc2, myUdc1, myUdc2, myPac1, myIac1, myUac1, myEToday, myETotal, myFrequency, myOperatingTime, myFeedInTime, myStatus, myGridRelay, myTemperature = result

        myDateTime = datetime.fromtimestamp(myTimeStamp).strftime('%Y-%m-%dT%H:%M:%S.000%z')
        myPower = myPac1 # + myPac2 + myPac3
        myGridVoltage = myUac1
        
        print(f"[{timestamp}] - TimeStamp ({myTimeStamp}), Pdc1 ({myPdc1}), Pdc2 ({myPdc2}), Idc1 ({myIdc1}), Idc2 ({myIdc2}), Udc1 ({myUdc1}), Udc2 ({myUdc2}), Pac1 ({myPac1}), Iac1 ({myIac1}), Uac1 ({myUac1}), EToday ({myEToday}), ETotal ({myETotal}), Frequency ({myFrequency}), OperatingTime ({myOperatingTime}), FeedInTime ({myFeedInTime}), Status ({myStatus}), GridRelay ({myGridRelay}), Temperature ({myTemperature})")

        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_TimeStamp/state", data = "%s" % (myDateTime), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_Energy_Today/state", data = "%s Wh" % (myEToday), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_Energy_Total/state", data = "%s Wh" % (myETotal), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_Status/state", data = "%s" % (myStatus.upper()), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_GridRelay/state", data = "%s" % (myGridRelay.upper()), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_Temperature/state", data = "%s" % (myTemperature), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_OperatingTime/state", data = "%s" % myOperatingTime * 3600, headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_FeedInTime/state", data = "%s" % myFeedInTime * 3600, headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_Grid_Frequency/state", data = "%s" % (myFrequency), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_Power/state", data = "%s" % (myPower), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_GridVoltage/state", data = "%s" % (myGridVoltage), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_Power_DC_String1/state", data = "%s" % (myPdc1), headers=headers_post)
        r = requests.put(f"{OPENHAB_REST_API}/items/SolarInverter_Power_DC_String2/state", data = "%s" % (myPdc2), headers=headers_post)

        cursor.close()
        sqliteConnection.close()
        print(f"[{timestamp}] Data have been pushed to openHAB REST API. The SQLite connection is closed")

This script can be improved, feel free to do so.

You may have to add a Bearer token in your HTTP requests if you properly secured access to openHAB.

I run the script 1 minute after SBFspot runs, so I added the following line to crontab of the openhabian user:

## SBFspot
*/5 5-22 * * * /usr/local/bin/sbfspot.3/daydata
55 05 * * * /usr/local/bin/sbfspot.3/monthdata

1-59/5 5-22 * * * cd /home/openhabian/smadata && /usr/bin/python3 /home/openhabian/smadata/SBFspot_update_openHAB.py >>/var/log/openhab/sbfspot_update_openhab.log 2&>1

I also compute the efficiency of my solar array by means of a rule which divides the instant power with the maximum power the solar array can deliver (in my case 4kW), and multiply the result by 100 to get a percentage:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: SolarInverter_Power
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "3"
    configuration:
      type: application/javascript
      script: |+
        var currentPower, efficiency;

        currentPower = itemRegistry.getItem('SolarInverter_Power').getState();
        efficiency = currentPower / 40;
        events.postUpdate('SolarInverter_PowerEfficiency', efficiency);

    type: script.ScriptAction

Have fun!