Blame | Last modification | View Log | RSS feed
#!python3# P1 Datalogger# $Revision: 9 $# $Author: raymond $# $Date: 2015-09-06 13:19:50 +0200 (zo, 06 sep 2015) $# Copyright (c) 2015 R. V. Slegers## Although there is a explicit copyright on this sourcecode, anyone may use it freely under a# "Creative Commons Naamsvermelding-NietCommercieel-GeenAfgeleideWerken 3.0 Nederland" licentie.# Please check http://creativecommons.org/licenses/by-nc-nd/3.0/nl/ for details## This software is provided as is and comes with absolutely no warranty.# The author is not responsible or liable (direct or indirect) to anyone for the use or misuse of this software.# Any person using this software does so entirely at his/her own risk.# That person bears sole responsibility and liability for any claims or actions, legal or civil, arising from such use.# If you believe this software is in breach of anyone's copyright you will inform the author immediately so the offending material# can be removed upon receipt of proof of copyright for that material.## Dependend on Python 3.x and Python 3.x packages: PySerial 2.5#progname='P1.py'version = "v0.73b"import_db = Trueimport sysimport serialimport datetimeimport csvimport osimport localeimport socketimport http.clientimport urllib.parseimport argparseimport configparserimport globsocket.setdefaulttimeout(30)MySQL_loaded = Truetry:import mysql.connectorexcept ImportError:MySQL_loaded=Falsefrom time import sleep###################################################################### pvoutput.org system parameters#####################################################################pvo_url = 'http://pvoutput.org/service/r2/addstatus.jsp'#####################################################################class P1_ChannelData:def __init__(self, id=0, type_id=0, type_desc='', equipment_id='', timestamp='0000-00-00 00:00:00', meterreading=0.0, unit='', valveposition=0):self.id = idself.type_id = type_idself.type_desc = type_descself.equipment_id = equipment_idself.timestamp = timestampself.meterreading = meterreadingself.unit = unitself.valveposition = valvepositiondef scan_serial():# scan for available ports. return a list of tuples (num, name)""" Lists serial port names:raises EnvironmentError:On unsupported or unknown platforms:returns:A list of the serial ports available on the system"""if sys.platform.startswith('win'):ports = ['COM%s' % (i + 1) for i in range(256)]elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):# this excludes your current terminal "/dev/tty"ports = glob.glob('/dev/tty[A-Za-z]*')elif sys.platform.startswith('darwin'):ports = glob.glob('/dev/tty.*')else:raise EnvironmentError('Unsupported platform')port_exceptions = ['/dev/ttyprintk','dev/ttyprintk']for port in port_exceptions:if port in ports:ports.remove(port)available = []for port in ports:try:s = serial.Serial(port)s.close()available.append(port)except (OSError, serial.SerialException):passif not available:available.append('Helaas geen seriele poorten gevonden')return available#################Error display #################def show_error():ft = sys.exc_info()[0]fv = sys.exc_info()[1]print("Fout type: %s" % ft )print("Fout waarde: %s" % fv )return#################Scherm output #################def print_p1_telegram():print ("---------------------------------------------------------------------------------------")print ("P1 telegram ontvangen op: %s" % p1_timestamp)if p1_meter_supplier == "KMP":print ("Meter fabrikant: Kamstrup")elif p1_meter_supplier == "ISk":print ("Meter fabrikant: IskraEmeco")elif p1_meter_supplier == "XMX":print ("Meter fabrikant: Xemex")elif p1_meter_supplier == "KFM":print ("Meter fabrikant: Kaifa")else:print ("Meter fabrikant: Niet herkend")print ("Meter informatie: %s" % p1_header )print (" 0. 2. 8 - DSMR versie: %s" % p1_dsmr_version )print ("96. 1. 1 - Meternummer Elektriciteit: %s" % p1_equipment_id )print (" 1. 8. 1 - Meterstand Elektriciteit levering (T1/Laagtarief): %0.3f %s" % (p1_meterreading_in_1,p1_unitmeterreading_in_1) )print (" 1. 8. 2 - Meterstand Elektriciteit levering (T2/Normaaltarief): %0.3f %s" % (p1_meterreading_in_2,p1_unitmeterreading_in_2) )print (" 2. 8. 1 - Meterstand Elektriciteit teruglevering (T1/Laagtarief): %0.3f %s" % (p1_meterreading_out_1,p1_unitmeterreading_out_1) )print (" 2. 8. 2 - Meterstand Elektriciteit teruglevering (T2/Normaaltarief): %0.3f %s" % (p1_meterreading_out_2,p1_unitmeterreading_out_2) )print ("96.14. 0 - Actueel tarief Elektriciteit: %d" % p1_current_tariff )print (" 1. 7. 0 - Actueel vermogen Electriciteit levering (+P): %0.3f %s" % (p1_current_power_in,p1_unit_current_power_in) )print (" 2. 7. 0 - Actueel vermogen Electriciteit teruglevering (-P): %0.3f %s" % (p1_current_power_out,p1_unit_current_power_out) )print ("17. 0. 0 - Actuele doorlaatwaarde Elektriciteit: %0.3f %s" % (p1_current_threshold,p1_unit_current_threshold) )print ("96. 3.10 - Actuele schakelaarpositie Elektriciteit: %s" % p1_current_switch_position )print ("96. 7.21 - Aantal onderbrekingen Elektriciteit: %s" % p1_powerfailures )print ("96. 7. 9 - Aantal lange onderbrekingen Elektriciteit: %s" % p1_long_powerfailures )print ("99.97. 0 - Lange onderbrekingen Elektriciteit logboek: %s" % p1_long_powerfailures_log )print ("32.32. 0 - Aantal korte spanningsdalingen Elektriciteit in fase 1: %s" % p1_voltage_sags_l1 )print ("52.32. 0 - Aantal korte spanningsdalingen Elektriciteit in fase 2: %s" % p1_voltage_sags_l2 )print ("72.32. 0 - Aantal korte spanningsdalingen Elektriciteit in fase 3: %s" % p1_voltage_sags_l3 )print ("32.36. 0 - Aantal korte spanningsstijgingen Elektriciteit in fase 1: %s" % p1_voltage_swells_l1 )print ("52.36. 0 - Aantal korte spanningsstijgingen Elektriciteit in fase 2: %s" % p1_voltage_swells_l2 )print ("72.36. 0 - Aantal korte spanningsstijgingen Elektriciteit in fase 3: %s" % p1_voltage_swells_l3 )print ("31. 7. 0 - Instantane stroom Elektriciteit in fase 1: %0.3f %s" % (p1_instantaneous_current_l1,p1_unit_instantaneous_current_l1) )print ("51. 7. 0 - Instantane stroom Elektriciteit in fase 2: %0.3f %s" % (p1_instantaneous_current_l2,p1_unit_instantaneous_current_l2) )print ("71. 7. 0 - Instantane stroom Elektriciteit in fase 3: %0.3f %s" % (p1_instantaneous_current_l3,p1_unit_instantaneous_current_l3) )print ("32. 7. 0 - Spanning Elektriciteit in fase 1: %0.3f %s" % (p1_voltage_l1,p1_unit_voltage_l1) )print ("52. 7. 0 - Spanning Elektriciteit in fase 2: %0.3f %s" % (p1_voltage_l2,p1_unit_voltage_l2) )print ("72. 7. 0 - Spanning Elektriciteit in fase 3: %0.3f %s" % (p1_voltage_l3,p1_unit_voltage_l3) )print ("21. 7. 0 - Instantaan vermogen Elektriciteit levering (+P) in fase 1: %0.3f %s" % (p1_instantaneous_active_power_in_l1,p1_unit_instantaneous_active_power_in_l1) )print ("41. 7. 0 - Instantaan vermogen Elektriciteit levering (+P) in fase 2: %0.3f %s" % (p1_instantaneous_active_power_in_l2,p1_unit_instantaneous_active_power_in_l2) )print ("61. 7. 0 - Instantaan vermogen Elektriciteit levering (+P) in fase 3: %0.3f %s" % (p1_instantaneous_active_power_in_l3,p1_unit_instantaneous_active_power_in_l3) )print ("22. 7. 0 - Instantaan vermogen Elektriciteit teruglevering (-P) in fase 1: %0.3f %s" % (p1_instantaneous_active_power_out_l1,p1_unit_instantaneous_active_power_out_l1) )print ("42. 7. 0 - Instantaan vermogen Elektriciteit teruglevering (-P) in fase 2: %0.3f %s" % (p1_instantaneous_active_power_out_l2,p1_unit_instantaneous_active_power_out_l2) )print ("62. 7. 0 - Instantaan vermogen Elektriciteit teruglevering (-P) in fase 3: %0.3f %s" % (p1_instantaneous_active_power_out_l3,p1_unit_instantaneous_active_power_out_l3) )print ("96.13. 1 - Bericht code: %s" % p1_message_code )print ("96.13. 0 - Bericht tekst: %s" % p1_message_text )channellist = [p1_channel_1, p1_channel_2, p1_channel_3, p1_channel_4]for channel in channellist:if channel.id != 0:print ("MBus Meterkanaal: %s" % channel.id )print ("24. 1. 0 - Productsoort: %s (%s)" % (channel.type_id, channel.type_desc) )print ("91. 1. 0 - Meternummer %s: %s" % (channel.type_desc, channel.equipment_id) )if p1_dsmr_version != "40":print ("24. 3. 0 - Tijdstip meterstand %s levering: %s" % (channel.type_desc, channel.timestamp) )print ("24. 3. 0 - Meterstand %s levering: %0.3f %s" % (channel.type_desc, channel.meterreading, channel.unit) )else:print ("24. 2. 1 - Tijdstip meterstand %s levering: %s" % (channel.type_desc, channel.timestamp) )print ("24. 2. 1 - Meterstand %s levering: %0.3f %s" % (channel.type_desc, channel.meterreading, channel.unit) )print ("24. 4. 0 - Actuele kleppositie %s: %s" % (channel.type_desc,channel.valveposition) )print ("Einde P1 telegram" )return#################Csv output #################def csv_p1_telegram():#New filename every daycsv_filename=datetime.datetime.strftime(datetime.datetime.today(), "P1_"+"%Y-%m-%d_"+str(log_interval*10)+"s.csv" )try:#If csv_file exists: open itcsv_file=open(csv_filename, 'rt')csv_file.close()csv_file=open(csv_filename, 'at', newline='', encoding="utf-8")writer = csv.writer(csv_file, dialect='excel', delimiter=';', quoting=csv.QUOTE_NONNUMERIC)except IOError:#Otherwise: create itcsv_file=open(csv_filename, 'wt', newline='', encoding="utf-8")writer = csv.writer(csv_file, dialect='excel', delimiter=';', quoting=csv.QUOTE_NONNUMERIC)#Write csv-headerwriter.writerow(['p1_timestamp','p1_meter_supplier','p1_header','p1_dsmr_version','p1_equipment_id','p1_meterreading_in_1','p1_unitmeterreading_in_1','p1_meterreading_in_2','p1_unitmeterreading_in_2','p1_meterreading_out_1','p1_unitmeterreading_out_1','p1_meterreading_out_2','p1_unitmeterreading_out_2','p1_current_tariff','p1_current_power_in','p1_unit_current_power_in','p1_current_power_out','p1_unit_current_power_out','p1_current_threshold','p1_unit_current_threshold','p1_current_switch_position','p1_powerfailures','p1_long_powerfailures','p1_long_powerfailures_log','p1_voltage_sags_l1','p1_voltage_sags_l2','p1_voltage_sags_l3','p1_voltage_swells_l1','p1_voltage_swells_l2','p1_voltage_swells_l3','p1_instantaneous_current_l1','p1_unit_instantaneous_current_l1','p1_instantaneous_current_l2','p1_unit_instantaneous_current_l2','p1_instantaneous_current_l3','p1_unit_instantaneous_current_l3','p1_voltage_l1','p1_unit_voltage_l1','p1_voltage_l2','p1_unit_voltage_l2','p1_voltage_l3','p1_unit_voltage_l3','p1_instantaneous_active_power_in_l1','p1_unit_instantaneous_active_power_in_l1','p1_instantaneous_active_power_in_l2','p1_unit_instantaneous_active_power_in_l2','p1_instantaneous_active_power_in_l3','p1_unit_instantaneous_active_power_in_l3','p1_instantaneous_active_power_out_l1','p1_unit_instantaneous_active_power_out_l1','p1_instantaneous_active_power_out_l2','p1_unit_instantaneous_active_power_out_l2','p1_instantaneous_active_power_out_l3','p1_unit_instantaneous_active_power_out_l3','p1_message_code','p1_message_text','p1_channel_1_id','p1_channel_1_type_id','p1_channel_1_type_desc','p1_channel_1_equipment_id','p1_channel_1_timestamp','p1_channel_1_meterreading','p1_channel_1_unit','p1_channel_1_valveposition','p1_channel_2_id','p1_channel_2_type_id','p1_channel_2_type_desc','p1_channel_2_equipment_id','p1_channel_2_timestamp','p1_channel_2_meterreading','p1_channel_2_unit','p1_channel_2_valveposition','p1_channel_3_id','p1_channel_3_type_id','p1_channel_3_type_desc','p1_channel_3_equipment_id','p1_channel_3_timestamp','p1_channel_3_meterreading','p1_channel_3_unit','p1_channel_3_valveposition','p1_channel_4_id','p1_channel_4_type_id','p1_channel_4_type_desc','p1_channel_4_equipment_id','p1_channel_4_timestamp','p1_channel_4_meterreading','p1_channel_4_unit','p1_channel_4_valveposition' ])print ("P1 telegram in %s gelogd op: %s" % (csv_filename, p1_timestamp) )writer.writerow([p1_timestamp,p1_meter_supplier,p1_header,p1_dsmr_version,p1_equipment_id,p1_meterreading_in_1, p1_unitmeterreading_in_1,p1_meterreading_in_2, p1_unitmeterreading_in_2,p1_meterreading_out_1,p1_unitmeterreading_out_1,p1_meterreading_out_2,p1_unitmeterreading_out_2,p1_current_tariff,p1_current_power_in,p1_unit_current_power_in,p1_current_power_out,p1_unit_current_power_out,p1_current_threshold,p1_unit_current_threshold,p1_current_switch_position,p1_powerfailures,p1_long_powerfailures,p1_long_powerfailures_log,p1_voltage_sags_l1,p1_voltage_sags_l2,p1_voltage_sags_l3,p1_voltage_swells_l1,p1_voltage_swells_l2,p1_voltage_swells_l3,p1_instantaneous_current_l1, p1_unit_instantaneous_current_l1,p1_instantaneous_current_l2, p1_unit_instantaneous_current_l2,p1_instantaneous_current_l3, p1_unit_instantaneous_current_l3,p1_voltage_l1, p1_unit_voltage_l1,p1_voltage_l2, p1_unit_voltage_l2,p1_voltage_l3, p1_unit_voltage_l3,p1_instantaneous_active_power_in_l1, p1_unit_instantaneous_active_power_in_l1,p1_instantaneous_active_power_in_l2, p1_unit_instantaneous_active_power_in_l2,p1_instantaneous_active_power_in_l3, p1_unit_instantaneous_active_power_in_l3,p1_instantaneous_active_power_out_l1, p1_unit_instantaneous_active_power_out_l1,p1_instantaneous_active_power_out_l2, p1_unit_instantaneous_active_power_out_l2,p1_instantaneous_active_power_out_l3, p1_unit_instantaneous_active_power_out_l3,p1_message_code,p1_message_text,p1_channel_1.id,p1_channel_1.type_id,p1_channel_1.equipment_id,p1_channel_1.timestamp,p1_channel_1.meterreading, p1_channel_1.unit,p1_channel_1.valveposition,p1_channel_2.id,p1_channel_2.type_id,p1_channel_2.equipment_id,p1_channel_2.timestamp,p1_channel_2.meterreading, p1_channel_2.unit,p1_channel_2.valveposition,p1_channel_3.id,p1_channel_3.type_id,p1_channel_3.equipment_id,p1_channel_3.timestamp,p1_channel_3.meterreading, p1_channel_3.unit,p1_channel_3.valveposition,p1_channel_4.id,p1_channel_4.type_id,p1_channel_4.equipment_id,p1_channel_4.timestamp,p1_channel_4.meterreading, p1_channel_4.unit,p1_channel_4.valveposition ])csv_file.close()return#################DB output #################ID = ''def db_p1_telegram():query = "insert into p1_log values (\'" + \ID + "\',\'" + \p1_timestamp + "\',\'" + \str(p1_meterreading_in_1) + "\',\'" + \str(p1_meterreading_in_2) + "\',\'" + \str(p1_meterreading_out_1) + "\',\'" +\str(p1_meterreading_out_2) + "\',\'" + \str(p1_current_tariff) + "\',\'" + \str(p1_current_power_in) + "\',\'" + \str(p1_current_power_out) + "\',\'" + \str(p1_channel_1.id) + "\',\'" + \str(p1_channel_1.type_id) + "\',\'" + \p1_channel_1.timestamp + "\',\'" + \str(p1_channel_1.meterreading) + "\',\'" + \p1_channel_1.unit + "\',\'" + \str(p1_channel_2.id) + "\',\'" + \str(p1_channel_2.type_id) + "\',\'" + \p1_channel_2.timestamp + "\',\'" + \str(p1_channel_2.meterreading) + "\',\'" + \p1_channel_2.unit + "\',\'" + \str(p1_channel_3.id) + "\',\'" + \str(p1_channel_3.type_id) + "\',\'" + \p1_channel_3.timestamp + "\',\'" + \str(p1_channel_3.meterreading) + "\',\'" + \p1_channel_3.unit + "\',\'" + \str(p1_channel_4.id) + "\',\'" + \str(p1_channel_4.type_id) + "\',\'" + \p1_channel_4.timestamp + "\',\'" + \str(p1_channel_4.meterreading) + "\',\'" + \p1_channel_4.unit + "\')"# print(query)try:db = mysql.connector.connect(user=p1_mysql_user, password=p1_mysql_passwd, host=p1_mysql_host, database=p1_mysql_db, get_warnings=True)c = db.cursor()c.execute (query)db.commit()print ("P1 telegram in database %s / %s gelogd op: %s" % (p1_mysql_host, p1_mysql_db, p1_timestamp) )c.close()db.close()except:show_error()print ("Fout bij het openen van / schrijven naar database %s / %s. P1 Telegram wordt gelogd in csv-bestand." % (p1_mysql_host, p1_mysql_db))csv_p1_telegram()returndef db_p2_telegram():query = "insert into p2_log values (\'" + \ID + "\',\'" + \p1_timestamp + "\',\'" + \p1_meter_supplier + "\',\'" + \p1_header + "\',\'" + \p1_dsmr_version + "\',\'" + \p1_equipment_id + "\',\'" + \str(p1_meterreading_in_1) + "\',\'" + \p1_unitmeterreading_in_1 + "\',\'" + \str(p1_meterreading_in_2) + "\',\'" + \p1_unitmeterreading_in_2 + "\',\'" + \str(p1_meterreading_out_1) + "\',\'" +\p1_unitmeterreading_out_1 + "\',\'" + \str(p1_meterreading_out_2) + "\',\'" + \p1_unitmeterreading_out_2 + "\',\'" + \str(p1_current_tariff) + "\',\'" + \str(p1_current_power_in) + "\',\'" + \p1_unit_current_power_in + "\',\'" + \str(p1_current_power_out) + "\',\'" + \p1_unit_current_power_out + "\',\'" + \str(p1_current_threshold) + "\',\'" + \p1_unit_current_threshold + "\',\'" + \str(p1_current_switch_position) + "\',\'" + \str(p1_powerfailures) + "\',\'" + \str(p1_long_powerfailures) + "\',\'" + \p1_long_powerfailures_log + "\',\'" + \str(p1_voltage_sags_l1) + "\',\'" + \str(p1_voltage_sags_l2) + "\',\'" + \str(p1_voltage_sags_l3) + "\',\'" + \str(p1_voltage_swells_l1) + "\',\'" + \str(p1_voltage_swells_l2) + "\',\'" + \str(p1_voltage_swells_l3) + "\',\'" + \str(p1_instantaneous_current_l1) + "\',\'" + \p1_unit_instantaneous_current_l1 + "\',\'" + \str(p1_instantaneous_current_l2) + "\',\'" + \p1_unit_instantaneous_current_l2 + "\',\'" + \str(p1_instantaneous_current_l3) + "\',\'" + \p1_unit_instantaneous_current_l3 + "\',\'" + \str(p1_voltage_l1) + "\',\'" + \p1_unit_voltage_l1 + "\',\'" + \str(p1_voltage_l2) + "\',\'" + \p1_unit_voltage_l2 + "\',\'" + \str(p1_voltage_l3) + "\',\'" + \p1_unit_voltage_l3 + "\',\'" + \str(p1_instantaneous_active_power_in_l1) + "\',\'" + \p1_unit_instantaneous_active_power_in_l1 + "\',\'" + \str(p1_instantaneous_active_power_in_l2) + "\',\'" + \p1_unit_instantaneous_active_power_in_l2 + "\',\'" + \str(p1_instantaneous_active_power_in_l3) + "\',\'" + \p1_unit_instantaneous_active_power_in_l3 + "\',\'" + \str(p1_instantaneous_active_power_out_l1) + "\',\'" + \p1_unit_instantaneous_active_power_out_l1 + "\',\'" + \str(p1_instantaneous_active_power_out_l2) + "\',\'" + \p1_unit_instantaneous_active_power_out_l2 + "\',\'" + \str(p1_instantaneous_active_power_out_l3) + "\',\'" + \p1_unit_instantaneous_active_power_out_l3 + "\',\'" + \p1_message_code + "\',\'" + \p1_message_text + "\',\'" + \str(p1_channel_1.id) + "\',\'" + \str(p1_channel_1.type_id) + "\',\'" + \p1_channel_1.type_desc + "\',\'" + \str(p1_channel_1.equipment_id) + "\',\'" + \p1_channel_1.timestamp + "\',\'" + \str(p1_channel_1.meterreading) + "\',\'" + \p1_channel_1.unit + "\',\'" + \str(p1_channel_1.valveposition) + "\',\'" + \str(p1_channel_2.id) + "\',\'" + \str(p1_channel_2.type_id) + "\',\'" + \p1_channel_2.type_desc + "\',\'" + \str(p1_channel_2.equipment_id) + "\',\'" + \p1_channel_2.timestamp + "\',\'" + \str(p1_channel_2.meterreading) + "\',\'" + \p1_channel_2.unit + "\',\'" + \str(p1_channel_2.valveposition) + "\',\'" + \str(p1_channel_3.id) + "\',\'" + \str(p1_channel_3.type_id) + "\',\'" + \p1_channel_3.type_desc + "\',\'" + \str(p1_channel_3.equipment_id) + "\',\'" + \p1_channel_3.timestamp + "\',\'" + \str(p1_channel_3.meterreading) + "\',\'" + \p1_channel_3.unit + "\',\'" + \str(p1_channel_3.valveposition) + "\',\'" + \str(p1_channel_4.id) + "\',\'" + \str(p1_channel_4.type_id) + "\',\'" + \p1_channel_4.type_desc + "\',\'" + \str(p1_channel_4.equipment_id) + "\',\'" + \p1_channel_4.timestamp + "\',\'" + \str(p1_channel_4.meterreading) + "\',\'" + \p1_channel_4.unit + "\',\'" + \str(p1_channel_4.valveposition) + "\')"# 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 ("P2 telegram in database %s / %s gelogd op: %s" % (p1_mysql_host, p1_mysql_db, p1_timestamp) )c.close()db.close()except:show_error()print ("Fout bij het openen van / schrijven naar database %s / %s." % (p1_mysql_host, p1_mysql_db))returndef db_p3_telegram():query = "insert into p3_log values (\'" + \ID + "\',\'" + \p1_timestamp + "\',\'" + \p1_meter_supplier + "\',\'" + \p1_header + "\',\'" + \p1_dsmr_version + "\',\'" + \p1_equipment_id + "\',\'" + \str(p1_meterreading_in_1) + "\',\'" + \p1_unitmeterreading_in_1 + "\',\'" + \str(p1_meterreading_in_2) + "\',\'" + \p1_unitmeterreading_in_2 + "\',\'" + \str(p1_meterreading_out_1) + "\',\'" +\p1_unitmeterreading_out_1 + "\',\'" + \str(p1_meterreading_out_2) + "\',\'" + \p1_unitmeterreading_out_2 + "\',\'" + \str(p1_current_tariff) + "\',\'" + \str(p1_current_power_in) + "\',\'" + \p1_unit_current_power_in + "\',\'" + \str(p1_current_power_out) + "\',\'" + \p1_unit_current_power_out + "\',\'" + \str(p1_current_threshold) + "\',\'" + \p1_unit_current_threshold + "\',\'" + \str(p1_current_switch_position) + "\',\'" + \str(p1_powerfailures) + "\',\'" + \str(p1_long_powerfailures) + "\',\'" + \p1_long_powerfailures_log + "\',\'" + \str(p1_voltage_sags_l1) + "\',\'" + \str(p1_voltage_sags_l2) + "\',\'" + \str(p1_voltage_sags_l3) + "\',\'" + \str(p1_voltage_swells_l1) + "\',\'" + \str(p1_voltage_swells_l2) + "\',\'" + \str(p1_voltage_swells_l3) + "\',\'" + \str(p1_instantaneous_current_l1) + "\',\'" + \p1_unit_instantaneous_current_l1 + "\',\'" + \str(p1_instantaneous_current_l2) + "\',\'" + \p1_unit_instantaneous_current_l2 + "\',\'" + \str(p1_instantaneous_current_l3) + "\',\'" + \p1_unit_instantaneous_current_l3 + "\',\'" + \str(p1_voltage_l1) + "\',\'" + \p1_unit_voltage_l1 + "\',\'" + \str(p1_voltage_l2) + "\',\'" + \p1_unit_voltage_l2 + "\',\'" + \str(p1_voltage_l3) + "\',\'" + \p1_unit_voltage_l3 + "\',\'" + \str(p1_instantaneous_active_power_in_l1) + "\',\'" + \p1_unit_instantaneous_active_power_in_l1 + "\',\'" + \str(p1_instantaneous_active_power_in_l2) + "\',\'" + \p1_unit_instantaneous_active_power_in_l2 + "\',\'" + \str(p1_instantaneous_active_power_in_l3) + "\',\'" + \p1_unit_instantaneous_active_power_in_l3 + "\',\'" + \str(p1_instantaneous_active_power_out_l1) + "\',\'" + \p1_unit_instantaneous_active_power_out_l1 + "\',\'" + \str(p1_instantaneous_active_power_out_l2) + "\',\'" + \p1_unit_instantaneous_active_power_out_l2 + "\',\'" + \str(p1_instantaneous_active_power_out_l3) + "\',\'" + \p1_unit_instantaneous_active_power_out_l3 + "\',\'" + \p1_message_code + "\',\'" + \p1_message_text + "\',\'" + \str(p1_channel_1.id) + "\',\'" + \str(p1_channel_1.type_id) + "\',\'" + \p1_channel_1.type_desc + "\',\'" + \str(p1_channel_1.equipment_id) + "\',\'" + \p1_channel_1.timestamp + "\',\'" + \str(p1_channel_1.meterreading) + "\',\'" + \p1_channel_1.unit + "\',\'" + \str(p1_channel_1.valveposition) + "\',\'" + \str(p1_channel_2.id) + "\',\'" + \str(p1_channel_2.type_id) + "\',\'" + \p1_channel_2.type_desc + "\',\'" + \str(p1_channel_2.equipment_id) + "\',\'" + \p1_channel_2.timestamp + "\',\'" + \str(p1_channel_2.meterreading) + "\',\'" + \p1_channel_2.unit + "\',\'" + \str(p1_channel_2.valveposition) + "\',\'" + \str(p1_channel_3.id) + "\',\'" + \str(p1_channel_3.type_id) + "\',\'" + \p1_channel_3.type_desc + "\',\'" + \str(p1_channel_3.equipment_id) + "\',\'" + \p1_channel_3.timestamp + "\',\'" + \str(p1_channel_3.meterreading) + "\',\'" + \p1_channel_3.unit + "\',\'" + \str(p1_channel_3.valveposition) + "\',\'" + \str(p1_channel_4.id) + "\',\'" + \str(p1_channel_4.type_id) + "\',\'" + \p1_channel_4.type_desc + "\',\'" + \str(p1_channel_4.equipment_id) + "\',\'" + \p1_channel_4.timestamp + "\',\'" + \str(p1_channel_4.meterreading) + "\',\'" + \p1_channel_4.unit + "\',\'" + \str(p1_channel_4.valveposition) + "\')"# 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 ("P3 telegram in database %s / %s gelogd op: %s" % (p1_mysql_host, p1_mysql_db, p1_timestamp) )c.close()db.close()except:show_error()print ("Fout bij het openen van / schrijven naar database %s / %s." % (p1_mysql_host, p1_mysql_db))return#######################PVOutput.org output #######################def pvo_p1_telegram():global pvo_prev_dateglobal p1_prev_meterreading_out_1, p1_prev_meterreading_out_2global p1_prev_meterreading_in_1, p1_prev_meterreading_in_2if pvo_url[0:7] != "http://":print("Invalid PVOutput.org URL to post to, must be of form http://host/service: %s" % pvo_url)sys.exit(1)url = pvo_url[7:].split('/')pvo_host = url[0]pvo_service = '/' + '/'.join(url[1:])## d Date# t Time# v1 energy generation (Wh) => P1 Export# v2 power generation (W) => P1 Export# v3 energy consumption (Wh) => P1 Import# v4 power consumption (W) => P1 Import# v5 temperature (c)# v6 voltage (V)# c1 cumulative flag: if set to '1' lifetime values are to be passed# n net flag: if set to '1' net import/export values are to be passed#pvo_date=str(datetime.datetime.strftime(datetime.datetime.strptime(p1_timestamp, "%Y-%m-%d %H:%M:%S" ), "%Y%m%d" ))pvo_time=str(datetime.datetime.strftime(datetime.datetime.strptime(p1_timestamp, "%Y-%m-%d %H:%M:%S" ), "%H:%M" ))# Initialize pvo volumes when a new day has startedif pvo_prev_date != pvo_date:print ("PVOutput volumes worden gereset, vorige datum: %s, huidige datum: %s" % (pvo_prev_date, pvo_date) )p1_prev_meterreading_out_1 = p1_meterreading_out_1p1_prev_meterreading_out_2 = p1_meterreading_out_2p1_prev_meterreading_in_1 = p1_meterreading_in_1p1_prev_meterreading_in_2 = p1_meterreading_in_2pvo_prev_date = pvo_datepvo_volume_out=round((p1_meterreading_out_1+p1_meterreading_out_2-p1_prev_meterreading_out_1-p1_prev_meterreading_out_2) * 1000)pvo_volume_in=round((p1_meterreading_in_1+p1_meterreading_in_2-p1_prev_meterreading_in_1-p1_prev_meterreading_in_2) *1000)pvo_power_out=round(p1_current_power_out * 1000)pvo_power_in=round(p1_current_power_in * 1000)print("PVOutput volume out (v1): %s"% pvo_volume_out)print("MR1 out: %s"% p1_meterreading_out_1)print("MR2 out: %s"% p1_meterreading_out_2)print("Prev MR1 out: %s"% p1_prev_meterreading_out_1)print("Prev MR2 out: %s"% p1_prev_meterreading_out_2)print("PVOutput volume in (v3): %s"% pvo_volume_in)print("MR1 in: %s"% p1_meterreading_in_1)print("MR2 in: %s"% p1_meterreading_in_2)print("Prev MR1 in: %s"% p1_prev_meterreading_in_1)print("Prev MR2 in: %s"% p1_prev_meterreading_in_2)pvo_cumulative = 0 # volumes are reset once a daypvo_net = 0 #Typically not correct but the best you can get with this PVOutput interface as net=1 discards the volume dataparams = urllib.parse.urlencode({ 'd' : pvo_date,'t' : pvo_time,# This is how it should be# 'v1' : pvo_volume_out,# 'v2' : pvo_power_out,# 'v3' : pvo_volume_in,# 'v4' : pvo_power_in,# Patch wrong behaviour of PVOutput in case of using net values because PVOutput obly uses power values in this case'v1' : pvo_volume_out,'v2' : pvo_power_out,'v3' : pvo_volume_in,'v4' : pvo_power_in,'c1' : pvo_cumulative,'n' : pvo_net })headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain","X-Pvoutput-SystemId" : pvo_systemid,"X-Pvoutput-Apikey" : pvo_apikey}print("Verbinden met %s" % pvo_host)try:conn = http.client.HTTPConnection(pvo_host)# print("Sending data: %s" % params)try:conn.request("POST", pvo_service, params, headers)response = conn.getresponse()if response.status != 200:print ("Fout bij het schrijven naar %s / %s. Response: %s %s %s" % (pvo_host, pvo_systemid, response.status, response.reason, response.read()))else:print ("Delta P1 telegram in %s / %s gelogd op: %s. Response: %s %s" % (pvo_host, pvo_systemid, p1_timestamp,response.status, response.reason) )except:show_error()print ("Fout bij het schrijven naar %s / %s." % (pvo_host, pvo_systemid))except:show_error()print ("Fout bij het verbinden met %s / %s." % (pvo_host, pvo_systemid))################################################################## Start of procedures to add other metering data to p1_telegram #################################################################################################################################### PV Inverter Data ##################################################################def get_pv_data(channelA,p1_channelA,channelB,p1_channelB):query = "select pv_timestamp, pv_equipmentmodel, pv_equipmentid, pv_cumvolume, pv_cumvolumeunit, pv_intvolume, pv_intvolumeunit, pv_power, pv_powerunit from pv_log order by pv_timestamp desc"# 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)pv_timestamp, pv_equipmentmodel, pv_equipmentid, pv_cumvolume, pv_cumvolumeunit, pv_intvolume, pv_intvolumeunit, pv_power, pv_powerunit = c.fetchone()p1_channelA.id = channelAp1_channelA.type_id = 1p1_channelA.type_desc = "E-Production volume"p1_channelA.equipment_id = pv_equipmentidp1_channelA.timestamp = str(datetime.datetime.strftime(pv_timestamp, "%Y-%m-%d %H:%M:%S" ))p1_channelA.meterreading = pv_cumvolumep1_channelA.unit = pv_cumvolumeunitp1_channelA.valveposition = 1print ("PV volume %s toegevoegd aan P1 telegram - kanaal %s" % (pv_timestamp, channelA ) )if channelB != 0:p1_channelB.id = channelBp1_channelB.type_id = 1p1_channelB.type_desc = "E-Production power"p1_channelB.equipment_id = pv_equipmentidp1_channelB.timestamp = str(datetime.datetime.strftime(pv_timestamp, "%Y-%m-%d %H:%M:%S" ))p1_channelB.meterreading = pv_powerp1_channelB.unit = pv_powerunitp1_channelB.valveposition = 1print ("PV vermogen %s toegevoegd aan P1 telegram - kanaal %s" % (pv_timestamp, channelB ) )#c.close()db.close()except:show_error()print ("Fout bij het openen / lezen van database %s / %s. PV telegram niet opgehaald." % (p1_mysql_host, p1_mysql_db))return################################################################## Heat Data ##################################################################def get_heat_data(channelA,p1_channelA,channelB,p1_channelB):query = "select heat_timestamp, heat_equipment_id, heat_meterreading_energy, heat_unitmeterreading_energy, heat_meterreading_volume, heat_unitmeterreading_volume from heat_log order by heat_timestamp desc"# 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)heat_timestamp, heat_equipment_id, heat_meterreading_energy, heat_unitmeterreading_energy, heat_meterreading_volume, heat_unitmeterreading_volume = c.fetchone()p1_channelA.id = channelAp1_channelA.type_id = 5p1_channelA.type_desc = "Heat energy"p1_channelA.equipment_id = heat_equipment_idp1_channelA.timestamp = str(datetime.datetime.strftime(heat_timestamp, "%Y-%m-%d %H:%M:%S" ))p1_channelA.meterreading = heat_meterreading_energyp1_channelA.unit = heat_unitmeterreading_energyp1_channelA.valveposition = 1print ("Warmte energie %s toegevoegd aan P1 telegram - kanaal %s" % (heat_timestamp, channelA ) )if channelB != 0:p1_channelB.id = channelBp1_channelB.type_id = 5p1_channelB.type_desc = "Heat flow"p1_channelB.equipment_id = heat_equipment_idp1_channelB.timestamp = str(datetime.datetime.strftime(heat_timestamp, "%Y-%m-%d %H:%M:%S" ))p1_channelB.meterreading = heat_meterreading_volumep1_channelB.unit = heat_unitmeterreading_volumep1_channelB.valveposition = 1print ("Warmte flow %s toegevoegd aan P1 telegram - kanaal %s" % (heat_timestamp, channelB ) )#c.close()db.close()except:show_error()print ("Fout bij het openen / lezen van database %s / %s. Heat telegram niet opgehaald." % (p1_mysql_host, p1_mysql_db))return################################################################## S0 Pulse Counter Data ##################################################################def get_s0_data(id,meter,channel,p1_channel,type_id,type_desc):# Use the total s0 volume to improve performance. In the S0 Datalogger, make sure it is not reset!!query = "select s0_timestamp, s0_id, s0_m" + meter + "_volume_total, s0_m" + meter + "_volume_total_unit from s0_log where s0_id = '" + id + "' order by s0_timestamp desc limit 1"# 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)s0_timestamp, s0_id, s0_volume_total, s0_volume_total_unit = c.fetchone()p1_channel.id = channelp1_channel.type_id = type_idp1_channel.type_desc = type_descp1_channel.equipment_id = s0_id + "-" + meterp1_channel.timestamp = str(datetime.datetime.strftime(s0_timestamp, "%Y-%m-%d %H:%M:%S" ))p1_channel.meterreading = s0_volume_totalp1_channel.unit = s0_volume_total_unitp1_channel.valveposition = "1"print ("S0 %s %s toegevoegd aan P1 telegram - kanaal %s" % (type_desc, s0_timestamp,channel))#c.close()db.close()except:show_error()print ("Fout bij het openen / lezen van database %s / %s. S0 telegram niet opgehaald." % (p1_mysql_host, p1_mysql_db))return################################################################## Electricity sub-meter ##################################################################def get_power_data(channel,p1_channel,type_id,type_desc):query = "select power_timestamp, power_equipment_id, power_meterreading_1_tot, power_unitmeterreading_1_tot from power_log order by power_timestamp desc"# 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)power_timestamp, power_equipment_id, power_meterreading_1_tot, power_unitmeterreading_1_tot = c.fetchone()p1_channel.id = channelp1_channel.type_id = type_idp1_channel.type_desc = type_descp1_channel.equipment_id = row.power_equipment_idp1_channel.timestamp = str(row.power_timestamp)p1_channel.meterreading = power_meterreading_1_totp1_channel.unit = power_unitmeterreading_1_totp1_channel.valveposition = "1"print ("Elektra %s %s toegevoegd aan P1 telegram - kanaal %s" % (type_desc, power_timestamp,channel) )#c.close()db.close()except:show_error()print ("Fout bij het openen / lezen van database %s / %s. Iskra telegram niet opgehaald." % (p1_mysql_host, p1_mysql_db))return################################################################## End of procedures to add other metering data to p1_telegram ###################################################################################################################################################################################################################Main program################################################################################################################################################print("%s %s" % (progname, version))comport=-1win_os = (os.name == 'nt')if win_os:print("Windows Mode")else:print("Non-Windows Mode")print("Python version %s.%s.%s" % sys.version_info[:3])print ("Control-C to abort")#################################################################################################################################################Commandline arguments parsing################################################################################################################################################parser = argparse.ArgumentParser(prog=progname, description='P1 Datalogger - www.smartmeterdashboard.nl', epilog="Copyright (c) 2011/2012/2013/2014 J. van der Linde. Although there is a explicit copyright on this sourcecode, anyone may use it freely under a 'Creative Commons Naamsvermelding-NietCommercieel-GeenAfgeleideWerken 3.0 Nederland' license.")#parser.add_argument("-c", "--comport", help="COM-port identifier", type=int)#parser.add_argument("-l", "--loginterval", help="Log frequency in 10 second-units, default=1", default=1, type=int)#parser.add_argument("-o", "--output", help="Output mode, default='screen'", default='screen', choices=['screen', 'csv', 'db'])#parser.add_argument("-pvo", "--pvoutput", help="Output to PVOutput ==EXPERIMENTAL==, default='N'", default='N', choices=['Y', 'N'])#parser.add_argument("-pvoapi", "--pvoutputapikey", help="PVOutput.org API key")#parser.add_argument("-pvosys", "--pvoutputsystemid", help="PVOutput.org system id", type=int)#parser.add_argument("-s", "--server", help="Database server, default='localhost'", default='localhost')#parser.add_argument("-u", "--user", help="Database user, default='root'", default='root')#parser.add_argument("-p", "--password", help="Database user password, default='password'", default='password')#parser.add_argument("-d", "--database", help="Database name, default=p1'", default='p1')parser.add_argument("-v", "--version", help="DSMR COM-port setting version, default=3'", choices=['2','3','4'], default='3')args = parser.parse_args()################################################################# Script arguments parsingmydir = os.path.dirname(os.path.abspath(__file__))config = configparser.RawConfigParser()#config.read([mydir + 'P1.cfg'])config.read(['/usr/local/P1.cfg'])cfgloginterval = config.getint('cfgGeneral', 'loginterval')cfgoutput = config.get('cfgGeneral', 'output')cfgserver = config.get('cfgDatabase', 'server')cfguser = config.get('cfgDatabase', 'user')cfgpassword = config.get('cfgDatabase', 'password')cfgdatabase = config.get('cfgDatabase', 'database')cfgtable = config.get('cfgDatabase', 'table')cfgtable2 = config.get('cfgDatabase', 'table2')cfgtable3 = config.get('cfgDatabase', 'table3')cfgcomport = config.get('cfgCOM', 'comport')cfgtcphost = config.get('cfgNET', 'tcphost')cfgtcpport = config.getint('cfgNET', 'tcpport')cfgpvo = config.get('cfgPVO', 'pvo')cfgpvoapi = config.get('cfgPVO', 'pvoapi')cfgpvosys = config.getint('cfgPVO', 'pvosys')# End parsing################################################################if cfgcomport == None:parser.print_help()print ("\r")print("%s: error: The following arguments are required: -c/--comport." % progname)print("Allowed values for argument -c/--comport:")#scanserial returns win_os serial ports and non win_os USB serial portsfor n,s in scan_serial():port=n+1print ("%d --> %s" % (port,s) )print ("Program aborted.")sys.exit()comport = int(cfgcomport)pvo_output = (cfgpvo == "Y")log_interval = cfglogintervalif pvo_output and (cfgpvoapi == None or cfgpvosys == None):parser.print_help()print ("\r")print("%s: error: If -pvo/--pvoutput is 'Y', the following arguments are required: -pvoapi/--pvoutputapikey and -pvosys/--pvoutputsystemid." % progname)print ("Program aborted.")sys.exit()if pvo_output and log_interval < 6:log_interval = 6print("%s: warning: If -pvo/--pvoutput is 'Y', log interval should be 6 or higher. Log interval 6 used instead." % progname)output_mode = cfgoutputdsmr_version = args.versionpvo_apikey = cfgpvoapipvo_systemid = cfgpvosyspvo_prev_date = ""#Show startup argumentsprint ("\r")print ("Startup parameters:")print ("Output mode : %s" % output_mode)print ("PVOutput.org logging : %s" % pvo_output)if pvo_output:print ("PVOutput.org API key : %s" % pvo_apikey)print ("PVOutput.org system ID: %s" % pvo_systemid)print ("Log interval : %s (once every %s seconds)" % (log_interval, log_interval * 10))print ("DSMR COM-port setting : %s" % dsmr_version)if (output_mode == "db" or import_db) and MySQL_loaded:p1_mysql_host=cfgserverp1_mysql_user=cfguserp1_mysql_passwd=cfgpasswordp1_mysql_db=cfgdatabaseprint ("Database credentials used:")print ("- Server : %s" % p1_mysql_host)print ("- User : %s" % p1_mysql_user)print ("- Password: %s" % p1_mysql_passwd)print ("- Database: %s" % p1_mysql_db)if (output_mode == "db" or import_db) and not MySQL_loaded:print("%s: warning: MySQL Connector/Python not found. Output mode 'db' not allowed. Output mode 'csv' used instead." % progname)output_mode = "csv"import_db = False##################################################################################################################################################Set COM port configif comport != 0:ser = serial.Serial()if dsmr_version == '2' or dsmr_version == '3':ser.baudrate = 9600ser.bytesize=serial.SEVENBITSser.parity=serial.PARITY_EVENser.stopbits=serial.STOPBITS_ONEser.xonxoff=1if dsmr_version == '4':ser.baudrate = 115200ser.bytesize=serial.EIGHTBITSser.parity=serial.PARITY_NONEser.stopbits=serial.STOPBITS_ONEser.xonxoff=1ser.rtscts=0ser.timeout=20if win_os:ser.port=comport-1print ("COM-port : %d (%s)" % (comport, ser.name) )else:ser.port="/dev/ttyUSB"+str(comport-1)port="/dev/ttyUSB"+str(comport-1) # Linux Style for /dev/ttyUSB0, /dev/ttyUSB1, etc...print ("COM-port : %d (%s)" % (comport, port) )else:print ("Inputfile assigned : 'p1test.log'")#Open COM portif comport != 0:try:ser.open()except:if win_os:sys.exit ("Error opening %s. Program aborted." % ser.name)else:sys.exit ("Error opening %s. Program aborted." % port)else:try:ser = open("p1test.log", "rt")except:sys.exit ("Error opening 'p1test.log'. Program aborted.")#Initializep1_telegram=Falsep1_meter_supplier=""p1_timestamp=""p1_dsmr_version="30"p1_current_threshold=0p1_unit_current_threshold=""p1_current_switch_position=1p1_powerfailures=0p1_long_powerfailures=0p1_long_powerfailures_log=""p1_voltage_sags_l1=0p1_voltage_sags_l2=0p1_voltage_sags_l3=0p1_voltage_swells_l1=0p1_voltage_swells_l2=0p1_voltage_swells_l3=0p1_instantaneous_current_l1=0p1_unit_instantaneous_current_l1=""p1_instantaneous_current_l2=0p1_unit_instantaneous_current_l2=""p1_instantaneous_current_l3=0p1_unit_instantaneous_current_l3=""p1_instantaneous_active_power_in_l1=0p1_unit_instantaneous_active_power_in_l1=""p1_instantaneous_active_power_in_l2=0p1_unit_instantaneous_active_power_in_l2=""p1_instantaneous_active_power_in_l3=0p1_unit_instantaneous_active_power_in_l3=""p1_instantaneous_active_power_out_l1=0p1_unit_instantaneous_active_power_out_l1=""p1_instantaneous_active_power_out_l2=0p1_unit_instantaneous_active_power_out_l2=""p1_instantaneous_active_power_out_l3=0p1_unit_instantaneous_active_power_out_l3=""p1_voltage_l1=0p1_unit_voltage_l1=""p1_voltage_l2=0p1_unit_voltage_l2=""p1_voltage_l3=0p1_unit_voltage_l3=""p1_prev_meterreading_out_1 = 0p1_prev_meterreading_out_2 = 0p1_prev_meterreading_in_1 = 0p1_prev_meterreading_in_2 = 0pvo_volume_initialize = Falsepvo_prev_date=""p1_teller=0while 1:p1_line=''#Read 1 linetry:p1_raw = ser.readline()except:if comport != 0:if win_os:sys.exit ("Error reading %s. Program aborted." % ser.name)else:sys.exit ("Error reading %s. Program aborted." % port)ser.close()else:sys.exit ("Error reading 'p1test.log'. Program aborted.")ser.close()if comport == 0 and len(p1_raw) == 0:ser.close()sys.exit ("Finished reading 'p1test.log'. Program ended.")p1_str=p1_rawif comport != 0:p1_str=str(p1_raw, "utf-8")p1_line=p1_str.strip()#Inspect 1st characterif p1_line[0:1] == "/":#Start of new P1 telegramp1_telegram=Truep1_teller=p1_teller+1#P1 Timestamp to cover DSMR 3 and beforep1_timestamp=datetime.datetime.strftime(datetime.datetime.today(), "%Y-%m-%d %H:%M:%S" )#Initialize P1 channeldatap1_channel_1=P1_ChannelData()p1_channel_2=P1_ChannelData()p1_channel_3=P1_ChannelData()p1_channel_4=P1_ChannelData()#Only proceed if P1 telegram start is recognized.if p1_telegram:if p1_line[0:1] == "/":#Header information#eg. /KMP5 KA6U001511209910 (Kamstrup Enexis)#eg. /ISk5\2ME382-1003 (InkraEmeco Liander)#eg. /XMX5XMXABCE000018914 (Landis&Gyr Stedin, Xemex communicatiemodule)#eg. /KFM5KAIFA-METER (Kaifa)p1_meter_supplier=p1_line[1:4]p1_header=p1_lineelif p1_line[4:9] == "1.0.0":#P1 Timestamp (DSMR 4)#eg. 0-0:1.0.0(101209113020W)if p1_line[10:23] != "000101010000W":#Check if meter clock is runningp1_timestamp="20"+p1_line[10:12]+"-"+p1_line[12:14]+"-"+p1_line[14:16]+" "+p1_line[16:18]+":"+p1_line[18:20]+":"+p1_line[20:22]else:print ("%s: warning: invalid P1-telegram date/time value '%s', system date/time used instead: '%s'" % (progname, p1_line[10:23], p1_timestamp) )elif p1_line[4:9] == "0.2.8":#DSMR Version (DSMR V4)#eg. 1-3:0.2.8(40)p1_lastpos=len(p1_line)-1p1_dsmr_version=p1_line[10:p1_lastpos]elif p1_line[4:10] == "96.1.1":######Channel 0 = E######Equipment identifier (Electricity)#eg. 0-0:96.1.1(204B413655303031353131323039393130)p1_lastpos=len(p1_line)-1p1_equipment_id=p1_line[11:p1_lastpos]elif p1_line[4:9] == "1.8.1":#Meter Reading electricity delivered to client (normal tariff)#eg. 1-0:1.8.1(00721.000*kWh) (DSMR 3)#eg. 1-0:1.8.1(000038.851*kWh) (DSMR 4)# p1_meterreading_in_1=float(p1_line[10:19])# p1_unitmeterreading_in_1=p1_line[20:23]p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_meterreading_in_1=float(p1_line[p1_num_start:p1_num_end])p1_unitmeterreading_in_1=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:9] == "1.8.2":#Meter Reading electricity delivered to client (low tariff)#eg. 1-0:1.8.2(00392.000*kWh)# p1_meterreading_in_2=float(p1_line[10:19])# p1_unitmeterreading_in_2=p1_line[20:23]p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_meterreading_in_2=float(p1_line[p1_num_start:p1_num_end])p1_unitmeterreading_in_2=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:9] == "2.8.1":#Meter Reading electricity delivered by client (normal tariff)#eg. 1-0:2.8.1(00000.000*kWh)# p1_meterreading_out_1=float(p1_line[10:19])# p1_unitmeterreading_out_1=p1_line[20:23]p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_meterreading_out_1=float(p1_line[p1_num_start:p1_num_end])p1_unitmeterreading_out_1=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:9] == "2.8.2":#Meter Reading electricity delivered by client (low tariff)#eg. 1-0:2.8.2(00000.000*kWh)# p1_meterreading_out_2=float(p1_line[10:19])# p1_unitmeterreading_out_2=p1_line[20:23]p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_meterreading_out_2=float(p1_line[p1_num_start:p1_num_end])p1_unitmeterreading_out_2=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:11] == "96.14.0":#Tariff indicator electricity#eg. 0-0:96.14.0(0001)#alternative 0-0:96.14.0(1)p1_lastpos=len(p1_line)-1p1_current_tariff=int(p1_line[12:p1_lastpos])elif p1_line[4:9] == "1.7.0":#Actual electricity power delivered to client (+P)#eg. 1-0:1.7.0(0000.91*kW)# p1_current_power_in=float(p1_line[10:17])# p1_unit_current_power_in=p1_line[18:20]p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_current_power_in=float(p1_line[p1_num_start:p1_num_end])p1_unit_current_power_in=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:9] == "2.7.0":#Actual electricity power delivered by client (-P)#1-0:2.7.0(0000.00*kW)# p1_current_power_out=float(p1_line[10:17])# p1_unit_current_power_out=p1_line[18:20]p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_current_power_out=float(p1_line[p1_num_start:p1_num_end])p1_unit_current_power_out=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "17.0.0":#Actual threshold Electricity#Companion standard, eg Kamstrup, Xemex#eg. 0-0:17.0.0(999*A)#Iskraemeco#eg. 0-0:17.0.0(0999.00*kW)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_current_threshold=float(p1_line[p1_num_start:p1_num_end])p1_unit_current_threshold=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:11] == "96.3.10":#Actual switch position Electricity (in/out/enabled).#eg. 0-0:96.3.10(1), default to 1p1_value=p1_line[12:13]if not isinstance(p1_value, int):p1_value=1p1_current_switch_position=int(p1_value)elif p1_line[4:11] == "96.7.21":#Number of powerfailures in any phase (DSMR4)#eg. 0-0:96.7.21(00004)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_powerfailures=int(float(p1_line[p1_num_start:p1_lastpos]))elif p1_line[4:10] == "96.7.9":#Number of long powerfailures in any phase (DSMR4)#eg. 0-0:96.7.9(00002)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_long_powerfailures=int(float(p1_line[p1_num_start:p1_lastpos]))elif p1_line[4:11] == "99.97.0":#Powerfailure eventlog (DSMR4)#eg. 1-0:99:97.0(2)(0:96.7.19)(101208152415W)(0000000240*s)(101208151004W)(00000000301*s)# 1-0:99.97.0(0)(0-0:96.7.19)p1_lastpos=len(p1_line)p1_log_start= p1_line.find("0:96.7.19") +10p1_long_powerfailures_log=p1_line[p1_log_start:p1_lastpos]elif p1_line[4:11] == "32.32.0":#Number of Voltage sags L1 (DSMR4)#eg. 1-0:32.32.0(00002)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_voltage_sags_l1=int(float(p1_line[p1_num_start:p1_lastpos]))elif p1_line[4:11] == "52.32.0":#Number of Voltage sags L2 (DSMR4)#eg. 1-0:52.32.0(00002)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_voltage_sags_l2=int(float(p1_line[p1_num_start:p1_lastpos]))elif p1_line[4:11] == "72.32.0":#Number of Voltage sags L3 (DSMR4)#eg. 1-0:72.32.0(00002)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_voltage_sags_l3=int(float(p1_line[p1_num_start:p1_lastpos]))elif p1_line[4:11] == "32.36.0":#Number of Voltage swells L1 (DSMR4)#eg. 1-0:32.36.0(00002)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_voltage_swells_l1=int(float(p1_line[p1_num_start:p1_lastpos]))elif p1_line[4:11] == "52.36.0":#Number of Voltage swells L2 (DSMR4)#eg. 1-0:52.36.0(00002)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_voltage_swells_l2=int(float(p1_line[p1_num_start:p1_lastpos]))elif p1_line[4:11] == "72.36.0":#Number of Voltage swells L3 (DSMR4)#eg. 1-0:72.36.0(00002)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_voltage_swells_l3=int(float(p1_line[p1_num_start:p1_lastpos]))elif p1_line[4:10] == "31.7.0":#Instantaneous current L1 in A (DSMR4)#eg. 1-0:31.7.0.255(001*A)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_current_l1=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_current_l1=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "51.7.0":#Instantaneous current L2 in A (DSMR4)#eg. 1-0:51.7.0.255(002*A)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_current_l2=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_current_l2=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "71.7.0":#Instantaneous current L3 in A (DSMR4)#eg. 1-0:71.7.0.255(003*A)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_current_l3=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_current_l3=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "21.7.0":#Instantaneous active power L1 (+P) in W (DSMR4)#eg 1-0:21.7.0.255(01.111*kW)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_active_power_in_l1=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_active_power_in_l1=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "41.7.0":#Instantaneous active power L2 (+P) in W (DSMR4)#eg 1-0:41.7.0.255(02.222*kW)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_active_power_in_l2=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_active_power_in_l2=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "61.7.0":#Instantaneous active power L3 (+P) in W (DSMR4)#eg 1-0:61.7.0.255(03.333*kW)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_active_power_in_l3=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_active_power_in_l3=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "22.7.0":#Instantaneous active power L1 (+P) in W (DSMR4)#eg 1-0:22.7.0.255(04.444*kW)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_active_power_out_l1=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_active_power_out_l1=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "42.7.0":#Instantaneous active power L2 (+P) in W (DSMR4)#eg 1-0:42.7.0.255(05.555*kW)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_active_power_out_l2=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_active_power_out_l2=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "62.7.0":#Instantaneous active power L3 (+P) in W (DSMR4)#eg 1-0:62.7.0.255(06.666*kW)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_instantaneous_active_power_out_l3=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_instantaneous_active_power_out_l3=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "32.7.0":#Voltage level L1 in V (DSMR4)#1-0:32.7.0(00234*V)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_voltage_l1=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_voltage_l1=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "52.7.0":#Voltage level L2 in V (DSMR4)#1-0:52.7.0(00234*V)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_voltage_l2=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_voltage_l2=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:10] == "72.7.0":#Voltage level L3 in V (DSMR4)#1-0:72.7.0(00234*V)p1_lastpos=len(p1_line)-1p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_voltage_l3=int(float(p1_line[p1_num_start:p1_num_end]))p1_unit_voltage_l3=p1_line[p1_num_end+1:p1_lastpos]elif p1_line[4:11] == "96.13.1":#Text message code: numeric 8 digits#eg. 0-0:96.13.1()p1_lastpos=len(p1_line)-1# p1_message_code=p1_line[12:p1_lastpos]p1_message_code=bytes.fromhex(p1_line[12:p1_lastpos]).decode('utf-8')elif p1_line[4:11] == "96.13.0":#Text message max 1024 characters.#eg. 0-0:96.13.0()p1_lastpos=len(p1_line)-1p1_message_text=bytes.fromhex(p1_line[12:p1_lastpos]).decode('utf-8')# p1_line[12:p1_lastpos]######Channels 1/2/3/4: MBus connected meters#####elif p1_line[4:10] == "24.1.0":#Device-Type#eg. 0-1:24.1.0(3)#or 0-1:24.1.0(03) 3=Gas;5=Heat;6=Cooling#or 0-1:24.1.0(03) 3/7=Gas;5=Heat;6=Cooling (Standard OBIS: 1-Electricity / 4-HeatCostAllocation / 5-Cooling / 6-Heat / 7-Gas / 8-ColdWater / 9-HotWater)p1_channel=int(p1_line[2:3])p1_lastpos=len(p1_line)-1p1_value=int(p1_line[11:p1_lastpos])if p1_value in [3,7]:p1_value2="Gas"elif p1_value == 4:p1_value2="HeatCost"elif p1_value == 5:p1_value2="Heat"elif p1_value == 6:p1_value2="Cold"elif p1_value == 8:p1_value2="Cold water"elif p1_value == 9:p1_value2="Hot water"else:p1_value2="Unknown"#self, id=None, type_id=None, type_desc=None, equipment_id=None, timestamp=None, meterreading=None, unit=None, valveposition=Noneif p1_channel==1:p1_channel_1.id=p1_channelp1_channel_1.type_id = p1_valuep1_channel_1.type_desc= p1_value2elif p1_channel==2:p1_channel_2.id=p1_channelp1_channel_2.type_id = p1_valuep1_channel_2.type_desc= p1_value2elif p1_channel==3:p1_channel_3.id=p1_channelp1_channel_3.type_id = p1_valuep1_channel_3.type_desc= p1_value2elif p1_channel==4:p1_channel_4.id=p1_channelp1_channel_4.type_id = p1_valuep1_channel_4.type_desc= p1_value2elif p1_line[4:10] == "96.1.0":#Equipment identifier#eg. 0-1:96.1.0(3238303039303031303434303132303130)p1_channel=int(p1_line[2:3])p1_lastpos=len(p1_line)-1p1_value=p1_line[11:p1_lastpos]#self, id=None, type_id=None, type_desc=None, equipment_id=None, timestamp=None, meterreading=None, unit=None, valveposition=Noneif p1_channel==1:p1_channel_1.equipment_id=p1_valueelif p1_channel==2:p1_channel_2.equipment_id=p1_valueelif p1_channel==3:p1_channel_3.equipment_id=p1_valueelif p1_channel==4:p1_channel_4.equipment_id=p1_valueelif p1_line[4:10] == "24.3.0":#Last hourly value delivered to client (DSMR < V4)#eg. Kamstrup/Iskraemeco:#0-1:24.3.0(110403140000)(000008)(60)(1)(0-1:24.2.1)(m3)#(00437.631)#eg. Companion Standard:#0-1:24.3.0(110403140000)(000008)(60)(1)(0-1:24.2.1)(m3)(00437.631)p1_channel=int(p1_line[2:3])p1_channel_timestamp="20"+p1_line[11:13]+"-"+p1_line[13:15]+"-"+p1_line[15:17]+" "+p1_line[17:19]+":"+p1_line[19:21]+":"+p1_line[21:23]p1_lastpos=len(p1_line)-1#Value is in next linep1_unit=p1_line[p1_lastpos-2:p1_lastpos]p1_raw = ser.readline()# p1_str=str(p1_raw, "utf-8")p1_str=p1_rawif comport != 0:p1_str=str(p1_raw, "utf-8")p1_line=p1_str.strip()#self, id=None, type_id=None, type_desc=None, equipment_id=None, timestamp= None, meterreading=None, unit=None, valveposition=Noneif p1_channel==1:p1_channel_1.timestamp=p1_channel_timestampp1_channel_1.meterreading=float(p1_line[1:10])p1_channel_1.unit=p1_unitelif p1_channel==2:p1_channel_2.timestamp=p1_channel_timestampp1_channel_2.meterreading=float(p1_line[1:10])p1_channel_2.unit=p1_unitelif p1_channel==3:p1_channel_3.timestamp=p1_channel_timestampp1_channel_3.meterreading=float(p1_line[1:10])p1_channel_3.unit=p1_unitelif p1_channel==4:p1_channel_4.timestamp=p1_channel_timestampp1_channel_4.meterreading=float(p1_line[1:10])p1_channel_4.unit=p1_unitelif p1_line[4:10] == "24.2.1":#Last hourly value delivered to client (DSMR v4)#eg. 0-1:24.2.1(101209110000W)(12785.123*m3)p1_channel=int(p1_line[2:3])p1_channel_timestamp="20"+p1_line[11:13]+"-"+p1_line[13:15]+"-"+p1_line[15:17]+" "+p1_line[17:19]+":"+p1_line[19:21]+":"+p1_line[21:23]p1_lastpos=len(p1_line)-1p1_line=p1_line[25:p1_lastpos]p1_lastpos=len(p1_line)p1_num_start = p1_line.find("(") +1p1_num_end = p1_line.find("*")p1_value=float(p1_line[p1_num_start:p1_num_end])p1_unit=p1_line[p1_num_end+1:p1_lastpos]#self, id=None, type_id=None, type_desc=None, equipment_id=None, timestamp= None, meterreading=None, unit=None, valveposition=Noneif p1_channel==1:p1_channel_1.timestamp=p1_channel_timestampp1_channel_1.meterreading=p1_valuep1_channel_1.unit=p1_unitelif p1_channel==2:p1_channel_2.timestamp=p1_channel_timestampp1_channel_2.meterreading=p1_valuep1_channel_2.unit=p1_unitelif p1_channel==3:p1_channel_3.timestamp=p1_channel_timestampp1_channel_3.meterreading=p1_valuep1_channel_3.unit=p1_unitelif p1_channel==4:p1_channel_4.timestamp=p1_channel_timestampp1_channel_4.meterreading=p1_valuep1_channel_4.unit=p1_unitelif p1_line[4:10] == "24.4.0":#Valve position (on/off/released)#eg. 0-1:24.4.0()#eg. 0-1:24.4.0(1)#Valveposition defaults to '1'(=Open) if invalid valuep1_channel=int(p1_line[2:3])p1_lastpos=len(p1_line)-1p1_value=p1_line[12:p1_lastpos].strip()if not isinstance(p1_value, int):p1_value=1if p1_channel==1:p1_channel_1.valveposition=p1_valueelif p1_channel==2:p1_channel_2.valveposition=p1_valueelif p1_channel==3:p1_channel_3.valveposition=p1_valueelif p1_channel==4:p1_channel_4.valveposition=p1_valueelif p1_line[0:1] == "" or p1_line[0:1] == " ":#Empty linep1_value=""elif p1_line[0:1] == "!":#in DSMR 4 telegrams there might be a checksum following the "!".#eg. !141B#CRC16 value calculated over the preceding characters in the data message (from “/” to “!” using the polynomial: x16+x15+x2+1).#the checksum is discarded#End of P1 telegram#Output if a complete telegram and matching log_intervalif p1_teller == log_interval:#################################################################Start of functionality to add other meterdata to p1-telegram ##################################################################Comment out / remove when not applicable #################################################################if import_db:######################HEAT: Mandatory 1st ChannelID, 1st ChannelDataElement, optional 2nd ChannelID, 2nd ChannelDataElementget_heat_data(1,p1_channel_1,2,p1_channel_2)######################POWER SUB METERING: ChannelID, ChannelDataElement, TypeID, TypeDescription# get_power_data(#,p1_channel_#,1,"E-Production volume")######################S0 SUB METERING: S0-ID, S0-Register, ChannelID, ChannelDataElement, TypeID, TypeDescription# get_s0_data('25325','1',3,p1_channel_3,1,"E-Production volume")######################PV INVERTER: Mandatory 1st ChannelID, 1st ChannelDataElement, optional 2nd ChannelID, 2nd ChannelDataElement# get_pv_data(1,p1_channel_1,2,p1_channel_2)#################################################################End of functionality to add other meterdata to p1-telegram ##################################################################Output to screenif output_mode=="screen": print_p1_telegram()#Output to csv_fileif output_mode=="csv": csv_p1_telegram()#Output to databaseif output_mode=="db":db_p1_telegram()db_p2_telegram()db_p3_telegram()#Output to PVOutput.orgif pvo_output: pvo_p1_telegram()################################################################p1_teller=0p1_telegram=False#to facilitate testing, when reading p1test.log always wait 10 seconds before proceeding to next telegram to simulate actual meter behaviourif comport == 0: sleep(10)else:#Always dump unrecognized data in identified telegram to screenprint ("Error interpreting P1-telegram, unrecognized data encountered: '%s'" % p1_line )# elif p1_line != '':#Always dump unrecognized data in identified telegram to screen# print ("Fout bij analyseren P1 data, nog geen compleet P1-telegram ontvangen: '%s'" % p1_line )#Close port and show statustry:ser.close()except:if win_os:sys.exit ("Error closing %s. Program aborted." % ser.name)else:sys.exit ("Error closing %s. Program aborted." % port)