zondag 6 december 2015

Smart Meter Dashboard (1) - Uitlezen Chint Omvormer

Samen met een vriend zijn we een eigen (verbeterde) versie aan het maken van het Smart Meter Dashboard.
Hij is bezig met de Web-pagina's en ik hou me bezig met de Python scripts om de data te verzamelen.

Ik ben de afgelopen weken bezig geweest met het uitkunnen lezen van de Chint-Omvormer van mijn Zonnepanelen.
Hiervoor heb ik op internet een Python library gevonden, namelijk pv - Python-based PV Inverter Monitoring Library.

In het bestand cms.py heb ik echter een regel toegevoegd omdat ik deze miste in de output:
Orgineel

                        'Ipv3':         ('\x06',                10.0),          # PV3 Current (A)
                        'Vpv':          ('\x40',                10.0),          # PV Voltage (V)
Aangepast:
                        'Ipv3':         ('\x06',                10.0),          # PV3 Current (A)
                        'E-Today':      ('\x0D',                100.0),         # Energy to grid Today (kWh)
                        'Vpv':          ('\x40',                10.0),          # PV Voltage (V)


Ik heb dit script geplaatst op een Raspberry Pi, uitgerust met de nieuwste versie van Raspbian (Jessie), die met een USB-Serieel-kabel is aangesloten op de RS-232 poort van de omvormer.

Daarnaast heb ik een script gemaakt dat gebruik maakt van de library en gekozen data in een MySQL database plaatst.
Het script dat ik heb gemaakt is het volgende:

MySQL_Loaded=True
import sys
import datetime
import threading
import pv
import serial
#from datetime import datetime, time
from pv import cms
try:
  import mysql.connector
except ImportError:
  MySQL_Loaded=False

p1_mysql_user = 'MySQL_SMD_Gebruiker'
p1_mysql_passwd = 'MySQL_SMD_Gebruiker_wachtwoord'
p1_mysql_host = 'localhost'
p1_mysql_db = 'SMD'

def show_error():
  ft = sys.exc_info()[0]
  fv = sys.exc_info()[1]
  print("Fout type: %s" % ft)
  print("Fout waarde: %s" % fv)
  return

def db_pv_telegram():
  query = "insert into pv_log values (\'" + \
    pv_timestamp + "\',\'" + \
    pv_meter_supplier + "\',\'" + \
    pv_header + "\',\'" + \
    str(pv_equipment_id) + "\',\'" + \
    str(pv_voltage) + "\',\'" + \
    str(pv_current_power_out) + "\',\'" + \
    str(pv_current_current_out) + "\',\'" + \
    str(pv_today_power_out) + "\',\'" + \
    str(pv_meterreading_out) + "\',\'" + \
    str(pv_hours) + "\',\'" + \
    str(pv_temp) + "\',\'" + \
    str(pv_mode) + "\',\'" + \
    str(pv_error) + "\')"

#  print (query)
  try:
    db = mysql.connector.connect(user=p1_mysql_user, password=p1_mysql_passwd, host=p1_mysql_host, database=p1_mysql_db)
    c = db.cursor()
    c.execute(query)
    db.commit()
    print("PV telegram in database %s / %s gelogd op: %s" % (p1_mysql_host, p1_mysql_db, pv_timestamp) )
    db.close()
  except:
    show_error()
    print("Fout bij het openen van / schrijven naar database %s / %s. Log kwijt." % (p1_mysql_host, p1_mysql_db) )
  return

#pv.debug()
#pv.debug_color()

port = serial.Serial('/dev/ttyUSB0', timeout=5)
inv = cms.Inverter(port)

def printit():
#  print(datetime.datetime.now())
  global pv_timestamp, pv_meter_supplier, pv_header, pv_equipment_id, pv_voltage, pv_current_power_out, pv_current_current_out, pv_today_power_out
  global pv_yesterday_power_out, pv_meterreading_out, pv_hours, pv_temp, pv_mode, pv_error, pv_previous_power_out

  try:
    if pv_meter_supplier in globals():
      pv_timestamp = datetime.datetime.strftime(datetime.datetime.today(), "%Y-%m-%d %H:%M:%S" )
  except NameError:
    pv_timestamp = datetime.datetime.strftime(datetime.datetime.today(), "%Y-%m-%d %H:%M:%S" )
    pv_meter_supplier = '-'
    pv_header = '-'
    pv_equipment_id = 0
    pv_voltage = 0
    pv_current_power_out = 0
    pv_current_current_out = 0
    pv_today_power_out = 0
    pv_previous_power_out = 0
    pv_yesterday_power_out = 0
    pv_temp = 0
    pv_mode = 0
    pv_error = 0
    pv_meterreading_out = 0
    pv_hours = 0
    print ("===============================[ OPSTARTEN ]===============================")

  pv_timestamp = datetime.datetime.strftime(datetime.datetime.today(), "%Y-%m-%d %H:%M:%S" )

  print ("==========================[ %s ]==========================" % pv_timestamp )
  threading.Timer(10.0, printit).start()
  inv.reset()                    # Reset all communications on the serial connection
  sn = inv.discover()            # Look for connected devices
  if sn is None:
    print ("Inverter is not connected.")
    print ("pv_today_power_out     : %s" % pv_today_power_out)
    print ("pv_yesterday_power_out : %s" % pv_yesterday_power_out)
    pv_voltage = 0
    pv_current_power_out = 0
    pv_current_current_out = 0
    pv_temp = 0
    pv_mode = 0
    pv_error = 0
    now = datetime.datetime.now()
    now_time = now.time()
    if datetime.time(0,30) >= now_time >= datetime.time(0,0) and pv_yesterday_power_out == 0:
      pv_yesterday_power_out = pv_today_power_out
      pv_today_power_out = 0
    db_pv_telegram()
    return
  ok = inv.register(sn)        # Associates the inverter and assigns default address
  version =  (inv.version())
  splitted = version.split()
  count = 0
  while count <= len(splitted)-1:
    count = count + 1
  param_layout = inv.param_layout()
  parameters = inv.parameters(param_layout)
  status_layout = inv.status_layout()
  status = inv.status(status_layout)
  parameters = dict(parameters)
  status = dict(status)

  pv_meter_supplier = splitted[4]
  pv_header = version
  pv_equipment_id = splitted[5]
  pv_voltage = status['Vpv']
  pv_current_power_out = status['Pac']
  pv_current_current_out = status['Iac']
  pv_today_power_out = status['E-Today']
  pv_meterreading_out = status['E-Total']
  pv_hours = status['h-Total']
  pv_temp = status['Temp-inv']
  pv_mode = status['Mode']
  pv_error = status['Error']
  print ("[ Voor vergelijkingen: ]")
  print ("pv_today_power_out     : %s" % pv_today_power_out)
  print ("pv_previous_power_out  : %s" % pv_previous_power_out)
  print ("pv_yesterday_power_out : %s" % pv_yesterday_power_out)
  if (pv_today_power_out - pv_yesterday_power_out < 0) and (pv_today_power_out < pv_previous_power_out):
    pv_yesterday_power_out = 0
  else:
    pv_previous_power_out = pv_today_power_out
    pv_today_power_out = pv_today_power_out - pv_yesterday_power_out
  print ("[ Na vergelijkingen: ]")
  print ("pv_today_power_out     : %s" % pv_today_power_out)
  print ("pv_previous_power_out  : %s" % pv_previous_power_out)
  print ("pv_yesterday_power_out : %s" % pv_yesterday_power_out)
  db_pv_telegram()

  print ("==========================================================================================")
  return

printit()
Uiteraard zijn de database login gegevens geanonimiseerd.
In MySQL heb ik een database aangemaakt met de naam SMD, daarin heb ik een tabel aangemaakt met de naam: pv_log.
De code om de kolommen aan te maken is de volgende:
CREATE TABLE `pv_log` (
 `pv_timestamp` DATETIME NOT NULL,
 `pv_meter_supplier` TINYTEXT NULL,
 `pv_header` TINYTEXT NULL,
 `pv_equipment_id` TINYTEXT NULL,
 `pv_voltage` DECIMAL(6,3) NULL DEFAULT NULL,
 `pv_current_power_out` DECIMAL(10,3) NULL DEFAULT NULL,
 `pv_current_current_out` DECIMAL(6,3) NULL DEFAULT NULL,
 `pv_today_power_out` DECIMAL(6,3) NULL DEFAULT NULL,
 `pv_meterreading_out` DECIMAL(9,3) NULL DEFAULT NULL,
 `pv_hours` INT(11) NULL DEFAULT NULL,
 `pv_temp` DECIMAL(3,1) NULL DEFAULT NULL,
 `pv_mode` TINYINT(2) NULL DEFAULT NULL,
 `pv_error` TINYINT(3) NULL DEFAULT NULL
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
ROW_FORMAT=COMPACT
;

Tevens heb ik een gebruiker aangemaakt die de rechten heeft om gegevens naar de SMD/pv_log te schrijven en te lezen. op de Raspberry Pi heb ik python, python-mysql.connector en python-serial geïnstalleerd met behulp van apt-get.

De Chint omvormer, welke trouwens gemaakt wordt door PhoenixTec en ook onder andere merken verkrijgbaar is, heeft wat toevoegingen welke best lastig zijn voor je PV_Today.
Namelijk de volgende:
  1. Na opstarten tot de eerste 0,1 kWh is opgewekt, geeft de omvormer de eindstand van gisteren weer
  2. aan het eind van de dag "stuitert" de dag-opbrengst omdat de omvormer soms niet meer reageert (uitgeschakelt is) door te weinig zonlicht.
 Beide eigenaardigheden heb ik in mijn script kunnen ondervangen, en loopt de dagopbrengst (punt 2) door tot middernacht, waarna hij op 0 gezet wordt.

Het script dat ik dit blog beschrijf is nog een test-versie en ik zal in de komende tijd herschreven worden. Maar hopelijk hebben mensen die hun omvormer uit willen lezen er al wat aan.