Browse Source

## 2022-11-17
- fix historic data calculation (until now this was only 'yesterday') did not change to the real "new yesterday" after date rollover
- add historic data and cost calculation: this week, last week, this month, last month, this year, last year, since last bill
- changed file log storage structure - now uses subfolders for year/month
- minor improvements

FloKra 2 years ago
parent
commit
961e1fcb5b
4 changed files with 526 additions and 114 deletions
  1. 8 0
      S0Meters_py/CHANGELOG.md
  2. 16 5
      S0Meters_py/README.md
  3. 487 105
      S0Meters_py/s0meters.py
  4. 15 4
      S0Meters_py/s0meters.yml

+ 8 - 0
S0Meters_py/CHANGELOG.md

@@ -1,3 +1,11 @@
+# CHANGELOG s0meters.py
+
+## 2022-11-17
+ - fix historic data calculation (until now this was only 'yesterday') did not change to the real "new yesterday" after date rollover
+ - add historic data and cost calculation: this week, last week, this month, last month, this year, last year, since last bill
+ - changed file log storage structure - now uses subfolders for year/month
+ - minor improvements
+
 ## 2022-11-16
  - fix cost calculation when ``cost_from_conv: False`` is configured
 

+ 16 - 5
S0Meters_py/README.md

@@ -134,12 +134,15 @@ In this YAML styled config file, all meters/counters are declared. The main iden
   unit: "kWh"
   digits: 3
   momType: "power"
-  #momUnit: "kW"
-  #momDigits: 3
-  #momFactor: 1
   momUnit: "W"
   momDigits: 0
   momFactor: 1000
+  #conv_unit: "kWh"
+  #conv_factor: 10.73184
+  #conv_digits: 2
+  cost_unit: "EUR"
+  cost_per_unit: 0.330995
+  cost_from_conv: False
   statTopic: "Powermeter"
   influxInstance_energy: "energymeters"
   influxInstance_mom: "energy_momentary"
@@ -148,6 +151,9 @@ In this YAML styled config file, all meters/counters are declared. The main iden
   influxFieldName_energy: "Energy_Total__kWh"
   influxFieldName_mom: "Power__W"
   influxMinWriteInterval_energy: 300
+  MQTTPublishJSON: False
+  historyPublishInterval: 300
+  billingStartDate: '2022-03-03'
 2: # meter ID as used in counter hardware (="C2")
   name: "Gas"
   impPerUnit: 100
@@ -183,6 +189,9 @@ In this YAML styled config file, all meters/counters are declared. The main iden
   #influxFieldName_mom_conv1: "Gas_Usage__kW"
   #influxFieldName_mom_conv2: "Gas_Usage__l_min"
   influxMinWriteInterval_energy: 300
+  MQTTPublishJSON: False
+  historyPublishInterval: 300
+  billingStartDate: '2022-03-02'
 ```
 
 - **name**: free text, used for path of file log and internally
@@ -255,10 +264,12 @@ In this YAML styled config file, all meters/counters are declared. The main iden
 - **influxFieldName_mom**: InfluxDB measurement name for momentary value
 - **influxFieldName_mom_conv1**: InfluxDB measurement name for converted momentary value
 - **influxFieldName_mom_conv2**: InfluxDB measurement name for 2nd converted momentary value
-- **influxMinWriteInterval**_energy: minimal InfluxDB write interval for energy readings
+- **influxMinWriteInterval_energy**: minimal InfluxDB write interval for energy readings
   If no interval is configured, EVERY impulse received will be written to InfluxDB!
   There is no such option for the momentary value, as there a high resolution is desired in the logs. 
-
+- **MQTTPublishJSON**: if set to True, a JSON object with all data is published on MQTT every time
+- **historyPublishInterval**: interval in seconds to publish history data (yesterday, this week, this month etc.)
+- **billingStartDate**: date on which the current billing period starts. Format: ``YYYY-MM-DD``. Used for statistics            (consumption since last bill)
 
 
 #### InfluxDB configuration

+ 487 - 105
S0Meters_py/s0meters.py

@@ -92,8 +92,9 @@ for instance in influxdb_yaml:
 
 data_energy = dict()
 data_momentary = dict()
-saved_todays_date = dict()
+saved_today_date = dict()
 influxdb_energy_lastWriteTime = dict()
+energy_history_lastPublished = dict()
 
 saved_energy_today_min = dict()
 saved_energy_yesterday_total = dict()
@@ -144,19 +145,26 @@ def processMeterData(data):
     #print(json.dumps(meters_yaml[cJson['C']]))
     #print(json.dumps(cJson))
     
+    # parse JSON object received from DualS0ImpCounter hardware
     if cJson:
+    
+        # extract variables from JSON
         cNum = cJson.get('C')
-        cReading = float(cJson.get('reading', None))
         #print(cNum)
-        
+        cReading = float(cJson.get('reading', None))
         dTime = cJson.get('dTime', None)
         
+        # declare variables for program sequence control
         write_energy_to_influxdb = False
         
-        cName = meters_yaml[cNum].get('name', False)
+        # get config data for this meter number
+        cName = meters_yaml[cNum].get('name', None)
         statTopic = meters_yaml[cNum].get('statTopic', None)
         unit = meters_yaml[cNum].get('unit', None)
+        publishJSON = meters_yaml[cNum].get('MQTTPublishJSON', False)
         
+        extendJSON = False
+        if publishJSON or verbose: extendJSON = True
         
         conv_unit = meters_yaml[cNum].get('conv_unit', None)
         conv_factor = meters_yaml[cNum].get('conv_factor', None)
@@ -167,7 +175,6 @@ def processMeterData(data):
         cost_per_unit = meters_yaml[cNum].get('cost_per_unit', None)
         cost_from_conv = meters_yaml[cNum].get('cost_from_conv', False)
         
-        
         momUnit = meters_yaml[cNum].get('momUnit', None)
         momType = meters_yaml[cNum].get('momType', None)
         
@@ -179,31 +186,28 @@ def processMeterData(data):
         
         impPerUnit = meters_yaml[cNum].get('impPerUnit', None)
         
-        if cName:
-            cJson['name'] = cName
-        
         momFactor = meters_yaml[cNum].get('momFactor', None)
-        if not momFactor:
+        if momFactor is None:
             momFactor = 1
         
         momDigits = meters_yaml[cNum].get('momDigits', None)
-        if momDigits == None:
+        if momDigits is None:
             momDigits = 3
             
         momFactor_conv1 = meters_yaml[cNum].get('momFactor_conv1', None)
-        if not momFactor_conv1:
+        if momFactor_conv1 is None:
             momFactor_conv1 = 1
         
         momDigits_conv1 = meters_yaml[cNum].get('momDigits_conv1', None)
-        if momDigits_conv1 == None:
+        if momDigits_conv1 is None:
             momDigits_conv1 = 3
             
         momFactor_conv2 = meters_yaml[cNum].get('momFactor_conv2', None)
-        if not momFactor_conv2:
+        if momFactor_conv2 is None:
             momFactor_conv2 = 1    
         
         momDigits_conv2 = meters_yaml[cNum].get('momDigits_conv2', None)
-        if momDigits_conv2 == None:
+        if momDigits_conv2 is None:
             momDigits_conv2 = 3
         
         digits = meters_yaml[cNum].get('digits', None)
@@ -211,34 +215,114 @@ def processMeterData(data):
         influxMinWriteInterval = meters_yaml[cNum].get('influxMinWriteInterval_energy', None)
         if influxMinWriteInterval == None: influxMinWriteInterval = 0
         
+        historyPublishInterval = meters_yaml[cNum].get('historyPublishInterval', 300)
+        
+        billingStartDate = meters_yaml[cNum].get('billingStartDate', None)
+        
+        # add meter name to JSON object
+        if cName is not None and extendJSON:
+            cJson['name'] = cName
+        
         
         # check for date rollover since last impulse
         today = datetime.date.today()
         today_str = today.strftime('%Y%m%d')
+        today_year_str = today.strftime('%Y')
+        today_mon_str = today.strftime('%m')
+        
         yesterday = today - datetime.timedelta(days = 1)
         yesterday_str = yesterday.strftime('%Y%m%d')
+        yesterday_year_str = yesterday.strftime('%Y')
+        yesterday_mon_str = yesterday.strftime('%m')
+        
+        
+        # needed for calendar week
+        isocal = datetime.date.fromisoformat(today.strftime('%Y-%m-%d')).isocalendar()
+        week = isocal[1]
+        year = isocal[0]
+        #print(year)
+        #print(week)
+        
+        # this week
+        ## week_day1 = datetime.date.fromisocalendar(year, week, 1)  # works only from python 3.8 - RPi only has 3.7.3
+        week_d = str(year) + "-W" + str(week)
+        #print(week_d)
+        week_day1 = datetime.datetime.strptime(week_d + '-1', "%Y-W%W-%w") # for python 3.7
+        week_day1_str = week_day1.strftime('%Y%m%d')
+        #print(week_day1_str)
+        week_day1_year_str = week_day1.strftime('%Y')
+        week_day1_mon_str = week_day1.strftime('%m')
+        
+        
+        # last week
+        if week > 1:
+            lastweek = week - 1
+            lastweek_year = year
+        else: 
+            # get highest week number of last year
+            lastweek_year = year - 1
+            lastyearlastweek = datetime.date.fromisoformat(str(lastweek_year) + "-12-31").isocalendar().week
+            lastweek = lastyearlastweek
+        lastweek_d = str(lastweek_year) + "-W" + str(lastweek)
+        #lastweek_day1 = datetime.date.fromisocalendar(lastweek_year, lastweek, 1)  # works only from python 3.8 - RPi only has 3.7.3
+        lastweek_day1 = datetime.datetime.strptime(lastweek_d + '-1', "%Y-W%W-%w") # for python 3.7
+        lastweek_day1_str = lastweek_day1.strftime('%Y%m%d')
+        lastweek_day1_year_str = lastweek_day1.strftime('%Y')
+        lastweek_day1_mon_str = lastweek_day1.strftime('%m')
+        
+        # this month
+        month = int(today.strftime('%m'))
+        month_day1_str = str(year) + str(month) + "01"
+        
+        
+        # last month
+        lastmonth_year = None
+        lastmonth = None
+        if month > 1:
+            lastmonth = month - 1
+            lastmonth_year = year
+        else:
+            lastmonth = 12
+            lastmonth_year = year - 1
+        lastmonth_day1_str = str(lastmonth_year) + str(lastmonth) + "01"
+        
+        
+        # billing start date
+        if billingStartDate is not None:
+            tmp_billdate = billingStartDate.split("-")
+            bill_year = tmp_billdate[0]
+            bill_month = tmp_billdate[1]
+            bill_day = tmp_billdate[2]
+            ##print("bill_year=" + str(bill_year))
+            ##print("bill_month=" + str(bill_month))
+            ##print("bill_day=" + str(bill_day))
+        
+        
         dateRollover = False
-        savedtoday = saved_todays_date.get(cName, False)
-        if not savedtoday or savedtoday != today:
+        savedtodaydate = saved_today_date.get(cName, False)
+        if not savedtodaydate or savedtodaydate != today:
             if debug:
                 print("date rollover happened or no date has been saved yet for meter " + str(cName))
-            if savedtoday and savedtoday == yesterday:
-                # a date rollover just happened, so change todays date to current and proceed with what has to be done
+            if savedtodaydate and savedtodaydate == yesterday:
+                # date rollover just happened, so change todays date to current and proceed
                 dateRollover = True
-                #log.debug(savedtoday)
-            saved_todays_date[cName] = today
+                #log.debug(savedtodaydate)
+            saved_today_date[cName] = today
         
         
         strformat = "{:.3f}"
         cReading_formatted = None
-        if digits:
+        if digits is not None:
             strformat = "{:."+str(digits)+"f}"
             cReading_formatted = strformat.format(cReading)
-            cJson['reading'] = round(cReading, digits)
+            if extendJSON: cJson['reading'] = round(cReading, digits)
         
-        if unit:
+        # add unit to JSON object
+        if unit is not None and extendJSON:
             cJson['unit'] = unit
         
+        # calculate momentane usage 
+        # if impulses per unit are set and measured time between last impulses is known
         if impPerUnit and dTime is not None:
             if dTime == 0:
                 momValue = 0.0
@@ -272,37 +356,30 @@ def processMeterData(data):
                 momValue_conv2 = round(momValue_conv2)
                 
                 
-            #cJson['momValue'] = momValue
-            #if momType:
-            #    cJson['momType'] = momType
-            #if momUnit:
-            #    cJson['momUnit'] = momUnit
-            
-            if momType:
+            if momType is not None and extendJSON:
                 cJson[momType] = momValue
             
-            if momType_conv1:
+            if momType_conv1 is not None and extendJSON:
                 cJson[momType_conv1] = momValue_conv1
             
-            if momType_conv2:
+            if momType_conv2 is not None and extendJSON:
                 cJson[momType_conv2] = momValue_conv2
             
-            if statTopic:
+            if statTopic and MQTTenabled:
                 if momType is not None:
-                    if MQTTenabled: 
-                        mqttc.publish(statTopic + "/" + momType, str(momValue), qos=0, retain=False)
-                        
-                        if momType_conv1 is not None:
-                            mqttc.publish(statTopic + "/" + momType_conv1, str(momValue_conv1), qos=0, retain=False)
-                        
-                        if momType_conv2 is not None:
-                            mqttc.publish(statTopic + "/" + momType_conv2, str(momValue_conv2), qos=0, retain=False)
+                    mqttc.publish(statTopic + "/" + momType, str(momValue), qos=0, retain=False)
+                    
+                    if momType_conv1 is not None:
+                        mqttc.publish(statTopic + "/" + momType_conv1, str(momValue_conv1), qos=0, retain=False)
+                    
+                    if momType_conv2 is not None:
+                        mqttc.publish(statTopic + "/" + momType_conv2, str(momValue_conv2), qos=0, retain=False)
                         
-            
+        
+        # publish current reading to MQTT
         if statTopic:
-            if cReading_formatted != None:
-                if MQTTenabled: 
-                    mqttc.publish(statTopic + "/reading", str(cReading_formatted), qos=0, retain=False)
+            if MQTTenabled and cReading_formatted is not None:
+                mqttc.publish(statTopic + "/reading", str(cReading_formatted), qos=0, retain=False)
         
         
         data_energy[cNum][meters_yaml[cNum].get('influxFieldName_energy', 'energyTotal')] = round(float(cReading), digits)
@@ -323,29 +400,49 @@ def processMeterData(data):
         #print(data_momentary[cNum])
         #print()
         
+        
+                
+        # check history publish interval - only publish if interval elapsed or first run after program start
+        publishEnergyHistory = False
+        ts = int(time.time())
+        energyHistoryPublish_elapsedTime = 0
+        if energy_history_lastPublished.get(cNum, None) != None:
+            energyHistoryPublish_elapsedTime = ts - energy_history_lastPublished.get(cNum)
+            if energyHistoryPublish_elapsedTime >= historyPublishInterval:
+                # write interval elapsed -> write to InfluxDB
+                publishEnergyHistory = True
+                energy_history_lastPublished[cNum] = ts
+        else:
+            # first run -> write to InfluxDB immediately
+            publishEnergyHistory = True
+            energy_history_lastPublished[cNum] = ts
+        
+
+        
+        
         # InfluxDB
         t_utc = datetime.datetime.utcnow()
         t_str = t_utc.isoformat() + 'Z'
         
         
-        
-        # InfluxDB - energy readings
+        # write to InfluxDB after date rollover
         if dateRollover:
             write_energy_to_influxdb = True
             
+        # check write interval - only write if interval elapsed or first run after program start
         ts = int(time.time())
         influxEnergyWrite_elapsedTime = 0
         if influxdb_energy_lastWriteTime.get(cNum, None) != None:
             influxEnergyWrite_elapsedTime = ts - influxdb_energy_lastWriteTime.get(cNum)
             if influxEnergyWrite_elapsedTime >= influxMinWriteInterval:
+                # write interval elapsed -> write to InfluxDB
                 write_energy_to_influxdb = True
-                influxdb_energy_lastWriteTime[cNum] = ts
         else:
-            # first run - do write immediately
+            # first run -> write to InfluxDB immediately
             write_energy_to_influxdb = True
-            influxdb_energy_lastWriteTime[cNum] = ts
             
         
+        # InfluxDB - energy readings
         influxInstance_energy = meters_yaml[cNum].get('influxInstance_energy', None)
         if influxInstance_energy is not None:
             if write_energy_to_influxdb:
@@ -367,8 +464,11 @@ def processMeterData(data):
                     print("Writing ENERGY to InfluxDB:")
                     print(json.dumps(jsondata_energy, indent = 4))
                 try:
-                    #influxclient_energy.write_points(jsondata_energy)
                     influxclient[influxInstance_energy].write_points(jsondata_energy)
+                    
+                    # remember write time to maintain interval
+                    influxdb_energy_lastWriteTime[cNum] = int(time.time())
+                    
                 except Exception as e:
                     print('Data not written!')
                     #log.error('Data not written!')
@@ -400,7 +500,6 @@ def processMeterData(data):
                 print("Writing MOMENTARY to InfluxDB:")
                 print(json.dumps(jsondata_momentary, indent = 4))
             try:
-                #influxclient_momentary.write_points(jsondata_momentary)
                 influxclient[influxInstance_mom].write_points(jsondata_momentary)
             except Exception as e:
                 print('Data not written!')
@@ -409,23 +508,35 @@ def processMeterData(data):
                 #log.error(e)
                 
         
-        # file log
+        # file log and calculation of today/yesterday values
         if config['filelog'].getboolean('enable'):
             # save and restore yesterday´s total energy to calculate today´s energy
             # check if total energy from yesterday is stored in memory, if not try to get it from saved file
             
-            file_path_meter = conf_storage_path + cName + "/"
+            file_path_meter_base = conf_storage_path + cName + "/" 
+            
+            file_path_meter_year = conf_storage_path + cName + "/" + today_year_str + "/"
+            file_path_meter = conf_storage_path + cName + "/" + today_year_str + "/" + today_mon_str + "/"
             file_today_min = file_path_meter + today_str + "_min.txt"
-            file_yesterday_total = file_path_meter + yesterday_str + "_total.txt"
             
+            file_path_meter_year_yday = conf_storage_path + cName + "/" + yesterday_year_str + "/"
+            file_path_meter_yday = conf_storage_path + cName + "/" + yesterday_year_str + "/" + yesterday_mon_str + "/"
+            
+            file_yesterday_total = file_path_meter_yday + yesterday_str + "_total.txt"
+            file_yesterday_min = file_path_meter_yday + yesterday_str + "_min.txt"
+            
+            energy_today_min = saved_energy_today_min.get(cName, None)
             energy_today_total = 0
             energy_yesterday_min = 0
-            energy_today_min = saved_energy_today_min.get(cName, None)
+            
+            energy_yesterday_total = None
             
             try:
+                # handle energy_today_min
                 if dateRollover:
+                    # after date rollover set to None in order to read from last saved file
                     energy_today_min = None
-                    
+                
                 if energy_today_min == None:
                     exists = os.path.isfile(file_today_min)
                     if exists:
@@ -439,11 +550,21 @@ def processMeterData(data):
                             saved_energy_today_min[cName] = energy_today_min
                             if verbose:
                                 print(cName + " - Energy Today min read from file -> = " + str(energy_today_min))
-                        else: energy_today_min = None
+                        else: 
+                            #energy_today_min = None
+                            energy_today_min = cReading # file was empty - take current reading as value
                     else:
-                        # save current Energy_total to min-file
-                        if not os.path.exists(file_path_meter):
-                            os.mkdir(file_path_meter)
+                        # save current energy_today to min-file
+                        try:
+                            if not os.path.exists(file_path_meter_base):
+                                os.mkdir(file_path_meter_base)
+                            if not os.path.exists(file_path_meter_year):
+                                os.mkdir(file_path_meter_year)
+                            if not os.path.exists(file_path_meter):
+                                os.mkdir(file_path_meter)
+                        except:
+                                e = sys.exc_info()[0]
+                                print( "Error creating directory: %s" % e )
                         f = open(file_today_min, "w+")
                         energy_today_min = cReading
                         saved_energy_today_min[cName] = energy_today_min
@@ -452,16 +573,18 @@ def processMeterData(data):
                         f.write(strformat.format(energy_today_min))
                         f.close()
                         
-                #try:
+                # calculate energy_today_total
                 if energy_today_min != None:
                     energy_today_total = cReading - energy_today_min
                     if verbose:
                         print(cName + " - Energy Today total: " + str('{0:.3f}'.format(energy_today_total)))
                 
-                
+                # handle energy_yesterday_total
                 energy_yesterday_total = saved_energy_yesterday_total.get(cName, None)
                 if dateRollover:
+                    # after date rollover set to None in order to read from last saved file
                     energy_yesterday_total = None
+                    
                 if energy_yesterday_total == None:
                     exists = os.path.isfile(file_yesterday_total)
                     if exists:
@@ -477,12 +600,10 @@ def processMeterData(data):
                                 print(cName + " - Energy Yesterday total read from file -> = " + str(energy_yesterday_total))
                         else: energy_yesterday_total = None
                     else:
-                        file_yesterday_min = file_path_meter + yesterday_str + "_min.txt"
+                        # no yesterday_total file exists - create one using file_yesterday_min
                         exists = os.path.isfile(file_yesterday_min)
                         if exists:
                             # load yesterday_min from file
-                            #if args_output_verbose1:
-                            #    print("file file_yesterday_min exists")
                             f = open(file_yesterday_min, "r")
                             if f.mode == 'r':
                                 contents =f.read()
@@ -496,54 +617,315 @@ def processMeterData(data):
                             
                             if energy_yesterday_min != None:
                                 energy_yesterday_total = round(energy_today_min - energy_yesterday_min, 3)
-                                ###log.debug(meter_id_name[meter['id']] + " - Energy yesterday total: " + str(energy_yesterday_total))
-                            
-                                if not os.path.exists(file_path_meter):
-                                    os.mkdir(file_path_meter)
+                                saved_energy_yesterday_total[cName] = energy_yesterday_total
+                                try:
+                                    if not os.path.exists(file_path_meter_base):
+                                        os.mkdir(file_path_meter_base)
+                                    if not os.path.exists(file_path_meter_year_yday):
+                                        os.mkdir(file_path_meter_year_yday)
+                                    if not os.path.exists(file_path_meter):
+                                        os.mkdir(file_path_meter)
+                                except:
+                                    e = sys.exc_info()[0]
+                                    print( "Error creating directory: %s" % e )
                                 f = open(file_yesterday_total, "w+")
-                                #f.write(str('{0:.3f}'.format(energy_yesterday_total)))
                                 f.write(strformat.format(energy_yesterday_total))
                                 f.close()
-                            #else:
-                            #    # file yesterday_min does not exist
+                            
             except:
                 e = sys.exc_info()[0]
-                print( "<p>Error in file log: %s</p>" % e )
+                print( "Error in file log: %s" % e )
             
-            if energy_today_total is not None:
-                cJson['Today__' + unit] = round(energy_today_total, digits)
-                if MQTTenabled: 
-                    mqttc.publish(statTopic + "/today__" + unit, str(round(energy_today_total, digits)), qos=0, retain=False)
+            
+            # calculate and MQTT publish energy_today and conversions/cost
+            if energy_today_total is not None and MQTTenabled:
+                # today in base unit
+                mqttc.publish(statTopic + "/Today__" + unit, str(round(energy_today_total, digits)), qos=0, retain=False)
+                if extendJSON: cJson['Today__' + unit] = round(energy_today_total, digits)
+                
+                # conversion
+                if conv_unit and conv_factor is not None:
+                    conv_value = energy_today_total * conv_factor
+                    mqttc.publish(statTopic + "/Today__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                    if extendJSON: cJson['Today__' + conv_unit] = round(conv_value, conv_digits)
+                    
+                    # cost from conversion
+                    if cost_unit and cost_per_unit is not None and cost_from_conv:
+                        cost_value = round(conv_value * cost_per_unit, 2)
+                        mqttc.publish(statTopic + "/Today_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['Today_cost__' + cost_unit] = round(cost_value, 2)
+                
+                # cost from base unit, not conversion
+                if not cost_from_conv:
+                    cost_value = round(energy_today_total * cost_per_unit, 2)                                
+                    mqttc.publish(statTopic + "/Today_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                    if extendJSON: cJson['Today_cost__' + cost_unit] = round(cost_value, 2)
+            
+            
+            # calculate and MQTT publish energy_yesterday and conversions/cost
+            if energy_yesterday_total is not None and MQTTenabled and publishEnergyHistory:
+                # yesterday in base unit
+                mqttc.publish(statTopic + "/Yesterday__" + unit, str(round(energy_yesterday_total, digits)), qos=0, retain=False)
+                if extendJSON: cJson['Yesterday__' + unit] = round(energy_yesterday_total, digits)
+                
+                # conversion
+                if conv_unit and conv_factor is not None:
+                    conv_value = energy_yesterday_total * conv_factor
+                    mqttc.publish(statTopic + "/Yesterday__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                    if extendJSON: cJson['Yesterday__' + conv_unit] = round(conv_value, conv_digits)
+                    
+                    # cost from conversion
+                    if cost_unit and cost_per_unit is not None and cost_from_conv:
+                        cost_value = round(conv_value * cost_per_unit, 2)
+                        mqttc.publish(statTopic + "/Yesterday_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['Yesterday_cost__' + cost_unit] = round(cost_value, 2)
+                
+                # cost from base unit, not conversion
+                if not cost_from_conv:
+                    cost_value = round(energy_yesterday_total * cost_per_unit, 2)                                
+                    mqttc.publish(statTopic + "/Yesterday_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                    if extendJSON: cJson['Yesterday_cost__' + cost_unit] = round(cost_value, 2)
+            
+            
+            # calculate this weeks total
+            energy_week_minfile = file_path_meter_base + week_day1_year_str + "/" + week_day1_mon_str + "/" + week_day1_str + "_min.txt"
+            energy_week_min = None
+            if os.path.isfile(energy_week_minfile) and MQTTenabled and publishEnergyHistory:
+                # load energy_week_minfile from file if exists
+                f = open(energy_week_minfile, "r")
+                if f.mode == 'r':
+                    contents = f.read()
+                    f.close()
+                if contents != '':
+                    energy_week_min = float(contents)
+                    energy_week_total = cReading - energy_week_min
+                    if verbose:
+                        print(cName + " - Energy Week min read from file '" + energy_week_minfile + "'")
+                        print("        -> = " + str(energy_week_min))
+                    mqttc.publish(statTopic + "/Week__" + unit, str(round(energy_week_total, digits)), qos=0, retain=False)
+                    if extendJSON: cJson['Week__' + unit] = round(energy_week_total, digits)
                     if conv_unit and conv_factor is not None:
-                        conv_value = energy_today_total * conv_factor
-                        mqttc.publish(statTopic + "/today__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
-                        cJson['Today__' + conv_unit] = round(conv_value, conv_digits)
+                        conv_value = energy_week_total * conv_factor
+                        mqttc.publish(statTopic + "/Week__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                        if extendJSON: cJson['Week__' + conv_unit] = round(conv_value, conv_digits)
+                        
+                        # cost from conversion
                         if cost_unit and cost_per_unit is not None and cost_from_conv:
                             cost_value = round(conv_value * cost_per_unit, 2)
-                            mqttc.publish(statTopic + "/cost_today__" + cost_unit, str(cost_value), qos=0, retain=False)
-                            cJson['cost_today__' + cost_unit] = round(cost_value, 2)
-                    elif not cost_from_conv:
-                        cost_value = round(energy_today_total * cost_per_unit, 2)                                
-                        mqttc.publish(statTopic + "/cost_today__" + cost_unit, str(cost_value), qos=0, retain=False)
-                        cJson['cost_today__' + cost_unit] = round(cost_value, 2)
-                
-            if energy_yesterday_total is not None:
-                cJson['Yesterday__' + unit] = round(energy_yesterday_total, digits)
-                if MQTTenabled: 
-                    mqttc.publish(statTopic + "/yesterday__" + unit, str(round(energy_yesterday_total, digits)), qos=0, retain=False)
+                            mqttc.publish(statTopic + "/Week_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                            if extendJSON: cJson['Week_cost__' + cost_unit] = round(cost_value, 2)
+                    
+                    # cost from base unit, not conversion
+                    if not cost_from_conv:
+                        cost_value = round(energy_week_total * cost_per_unit, 2)                                
+                        mqttc.publish(statTopic + "/Week_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['Week_cost__' + cost_unit] = round(cost_value, 2)
+                        
+                        
+            # calculate last weeks total
+            energy_lastweek_minfile = file_path_meter_base + lastweek_day1_year_str + "/" + lastweek_day1_mon_str + "/" + lastweek_day1_str + "_min.txt"
+            if os.path.isfile(energy_lastweek_minfile) and MQTTenabled and energy_week_min is not None and publishEnergyHistory:
+                # load energy_lastweek_minfile from file if exists
+                f = open(energy_lastweek_minfile, "r")
+                if f.mode == 'r':
+                    contents = f.read()
+                    f.close()
+                if contents != '':
+                    energy_lastweek_min = float(contents)
+                    energy_lastweek_total = energy_week_min - energy_lastweek_min
+                    if verbose:
+                        print(cName + " - Energy LastWeek min read from file '" + energy_lastweek_minfile + "'")
+                        print("        -> = " + str(energy_week_min))
+                    mqttc.publish(statTopic + "/LastWeek__" + unit, str(round(energy_lastweek_total, digits)), qos=0, retain=False)
+                    if extendJSON: cJson['LastWeek__' + unit] = round(energy_lastweek_total, digits)
+                    if conv_unit and conv_factor is not None:
+                        conv_value = energy_lastweek_total * conv_factor
+                        mqttc.publish(statTopic + "/LastWeek__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                        if extendJSON: cJson['LastWeek__' + conv_unit] = round(conv_value, conv_digits)
+                        
+                        # cost from conversion
+                        if cost_unit and cost_per_unit is not None and cost_from_conv:
+                            cost_value = round(conv_value * cost_per_unit, 2)
+                            mqttc.publish(statTopic + "/LastWeek_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                            if extendJSON: cJson['LastWeek_cost__' + cost_unit] = round(cost_value, 2)
+                    
+                    # cost from base unit, not conversion
+                    if not cost_from_conv:
+                        cost_value = round(energy_lastweek_total * cost_per_unit, 2)                                
+                        mqttc.publish(statTopic + "/LastWeek_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['LastWeek_cost__' + cost_unit] = round(cost_value, 2)
+                        
+                        
+                        
+            # calculate this months total
+            energy_month_minfile = file_path_meter_base + str(year) + "/" + str(month) + "/" + month_day1_str + "_min.txt"
+            energy_month_min = None
+            if os.path.isfile(energy_month_minfile) and MQTTenabled and publishEnergyHistory:
+                # load energy_month_minfile from file if exists
+                f = open(energy_month_minfile, "r")
+                if f.mode == 'r':
+                    contents = f.read()
+                    f.close()
+                if contents != '':
+                    energy_month_min = float(contents)
+                    energy_month_total = cReading - energy_month_min
+                    if verbose:
+                        print(cName + " - Energy Month min read from file '" + energy_month_minfile + "'")
+                        print("        -> = " + str(energy_month_min))
+                    mqttc.publish(statTopic + "/Month__" + unit, str(round(energy_month_total, digits)), qos=0, retain=False)
+                    if extendJSON: cJson['Month__' + unit] = round(energy_month_total, digits)
+                    if conv_unit and conv_factor is not None:
+                        conv_value = energy_month_total * conv_factor
+                        mqttc.publish(statTopic + "/Month__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                        if extendJSON: cJson['Month__' + conv_unit] = round(conv_value, conv_digits)
+                        
+                        # cost from conversion
+                        if cost_unit and cost_per_unit is not None and cost_from_conv:
+                            cost_value = round(conv_value * cost_per_unit, 2)
+                            mqttc.publish(statTopic + "/Month_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                            if extendJSON: cJson['Month_cost__' + cost_unit] = round(cost_value, 2)
+                    
+                    # cost from base unit, not conversion
+                    if not cost_from_conv:
+                        cost_value = round(energy_month_total * cost_per_unit, 2)                                
+                        mqttc.publish(statTopic + "/Month_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['Month_cost__' + cost_unit] = round(cost_value, 2)
+            
+            
+            # calculate last months total
+            energy_lastmonth_minfile = file_path_meter_base + str(lastmonth_year) + "/" + str(lastmonth) + "/" + lastmonth_day1_str + "_min.txt"
+            if os.path.isfile(energy_lastmonth_minfile) and MQTTenabled and energy_month_min is not None and publishEnergyHistory:
+                # load energy_lastmonth_minfile from file if exists
+                f = open(energy_lastmonth_minfile, "r")
+                if f.mode == 'r':
+                    contents = f.read()
+                    f.close()
+                if contents != '':
+                    energy_lastmonth_min = float(contents)
+                    energy_lastmonth_total = energy_month_min - energy_lastmonth_min
+                    if verbose:
+                        print(cName + " - Energy LastMonth min read from file '" + energy_lastmonth_minfile + "'")
+                        print("        -> = " + str(energy_lastmonth_min))
+                    mqttc.publish(statTopic + "/LastMonth__" + unit, str(round(energy_lastmonth_total, digits)), qos=0, retain=False)
+                    if extendJSON: cJson['LastMonth__' + unit] = round(energy_lastmonth_total, digits)
                     if conv_unit and conv_factor is not None:
-                        conv_value = energy_yesterday_total * conv_factor
-                        mqttc.publish(statTopic + "/yesterday__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
-                        cJson['Yesterday__' + conv_unit] = round(conv_value, conv_digits)
-                        if cost_unit and cost_per_unit is not None:
-                            if cost_from_conv:
-                                cost_value = round(conv_value * cost_per_unit, 2)
-                                mqttc.publish(statTopic + "/cost_yesterday__" + cost_unit, str(cost_value), qos=0, retain=False)
-                                cJson['cost_yesterday__' + cost_unit] = round(cost_value, 2)
-                    elif not cost_from_conv:
-                        cost_value = round(energy_yesterday_total * cost_per_unit, 2)                                
-                        mqttc.publish(statTopic + "/cost_yesterday__" + cost_unit, str(cost_value), qos=0, retain=False)
-                        cJson['cost_yesterday__' + cost_unit] = round(cost_value, 2)
+                        conv_value = energy_lastmonth_total * conv_factor
+                        mqttc.publish(statTopic + "/LastMonth__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                        if extendJSON: cJson['LastMonth__' + conv_unit] = round(conv_value, conv_digits)
+                        
+                        # cost from conversion
+                        if cost_unit and cost_per_unit is not None and cost_from_conv:
+                            cost_value = round(conv_value * cost_per_unit, 2)
+                            mqttc.publish(statTopic + "/LastMonth_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                            if extendJSON: cJson['LastMonth_cost__' + cost_unit] = round(cost_value, 2)
+                    
+                    # cost from base unit, not conversion
+                    if not cost_from_conv:
+                        cost_value = round(energy_lastmonth_total * cost_per_unit, 2)                                
+                        mqttc.publish(statTopic + "/LastMonth_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['LastMonth_cost__' + cost_unit] = round(cost_value, 2)
+            
+            
+            # calculate this years total
+            energy_year_minfile = file_path_meter_base + str(year) + "/01/" + str(year) + "0101_min.txt"
+            energy_year_min = None
+            if os.path.isfile(energy_year_minfile) and MQTTenabled and publishEnergyHistory:
+                # load energy_year_minfile from file if exists
+                f = open(energy_year_minfile, "r")
+                if f.mode == 'r':
+                    contents = f.read()
+                    f.close()
+                if contents != '':
+                    energy_year_min = float(contents)
+                    energy_year_total = cReading - energy_year_min
+                    if verbose:
+                        print(cName + " - Energy Year min read from file '" + energy_year_minfile + "'")
+                        print("        -> = " + str(energy_year_min))
+                    mqttc.publish(statTopic + "/Year__" + unit, str(round(energy_year_total, digits)), qos=0, retain=False)
+                    if extendJSON: cJson['Year__' + unit] = round(energy_year_total, digits)
+                    if conv_unit and conv_factor is not None:
+                        conv_value = energy_year_total * conv_factor
+                        mqttc.publish(statTopic + "/Year__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                        if extendJSON: cJson['Year__' + conv_unit] = round(conv_value, conv_digits)
+                        
+                        # cost from conversion
+                        if cost_unit and cost_per_unit is not None and cost_from_conv:
+                            cost_value = round(conv_value * cost_per_unit, 2)
+                            mqttc.publish(statTopic + "/Year_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                            if extendJSON: cJson['Year_cost__' + cost_unit] = round(cost_value, 2)
+                    
+                    # cost from base unit, not conversion
+                    if not cost_from_conv:
+                        cost_value = round(energy_year_total * cost_per_unit, 2)                                
+                        mqttc.publish(statTopic + "/Year_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['Year_cost__' + cost_unit] = round(cost_value, 2)
+                        
+            # calculate last years total
+            energy_lastyear_minfile = file_path_meter_base + str(year-1) + "/01/" + str(year-1) + "0101_min.txt"
+            if os.path.isfile(energy_lastyear_minfile) and MQTTenabled and energy_year_min is not None and publishEnergyHistory:
+                # load energy_lastyear_minfile from file if exists
+                f = open(energy_lastyear_minfile, "r")
+                if f.mode == 'r':
+                    contents = f.read()
+                    f.close()
+                if contents != '':
+                    energy_lastyear_min = float(contents)
+                    energy_lastyear_total = energy_year_min - energy_lastyear_min
+                    if verbose:
+                        print(cName + " - Energy LastYear min read from file '" + energy_lastyear_minfile + "'")
+                        print("        -> = " + str(energy_lastyear_min))
+                    mqttc.publish(statTopic + "/LastYear__" + unit, str(round(energy_lastyear_total, digits)), qos=0, retain=False)
+                    if extendJSON: cJson['LastYear__' + unit] = round(energy_lastyear_total, digits)
+                    if conv_unit and conv_factor is not None:
+                        conv_value = energy_lastyear_total * conv_factor
+                        mqttc.publish(statTopic + "/LastYear__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                        if extendJSON: cJson['LastYear__' + conv_unit] = round(conv_value, conv_digits)
+                        
+                        # cost from conversion
+                        if cost_unit and cost_per_unit is not None and cost_from_conv:
+                            cost_value = round(conv_value * cost_per_unit, 2)
+                            mqttc.publish(statTopic + "/LastYear_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                            if extendJSON: cJson['LastYear_cost__' + cost_unit] = round(cost_value, 2)
+                    
+                    # cost from base unit, not conversion
+                    if not cost_from_conv:
+                        cost_value = round(energy_lastyear_total * cost_per_unit, 2)                                
+                        mqttc.publish(statTopic + "/LastYear_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['LastYear_cost__' + cost_unit] = round(cost_value, 2)
+            
+            # calculate total since last billing
+            energy_bill_minfile = file_path_meter_base + str(bill_year) + "/" + str(bill_month) + "/" + str(bill_year) + str(bill_month) + str(bill_day) + "_min.txt"
+            if os.path.isfile(energy_bill_minfile) and MQTTenabled and publishEnergyHistory and billingStartDate is not None:
+                # load energy_bill_minfile from file if exists
+                f = open(energy_bill_minfile, "r")
+                if f.mode == 'r':
+                    contents = f.read()
+                    f.close()
+                if contents != '':
+                    energy_bill_min = float(contents)
+                    energy_bill_total = cReading - energy_bill_min
+                    if verbose:
+                        print(cName + " - Energy Year min read from file '" + energy_bill_minfile + "'")
+                        print("        -> = " + str(energy_bill_min))
+                    mqttc.publish(statTopic + "/SinceLastBill__" + unit, str(round(energy_bill_total, digits)), qos=0, retain=False)
+                    if extendJSON: cJson['SinceLastBill__' + unit] = round(energy_bill_total, digits)
+                    if conv_unit and conv_factor is not None:
+                        conv_value = energy_bill_total * conv_factor
+                        mqttc.publish(statTopic + "/SinceLastBill__" + conv_unit, str(round(conv_value, conv_digits)), qos=0, retain=False)
+                        if extendJSON: cJson['SinceLastBill__' + conv_unit] = round(conv_value, conv_digits)
+                        
+                        # cost from conversion
+                        if cost_unit and cost_per_unit is not None and cost_from_conv:
+                            cost_value = round(conv_value * cost_per_unit, 2)
+                            mqttc.publish(statTopic + "/SinceLastBill_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                            if extendJSON: cJson['SinceLastBill_cost__' + cost_unit] = round(cost_value, 2)
+                    
+                    # cost from base unit, not conversion
+                    if not cost_from_conv:
+                        cost_value = round(energy_bill_total * cost_per_unit, 2)                                
+                        mqttc.publish(statTopic + "/SinceLastBill_cost__" + cost_unit, str(cost_value), qos=0, retain=False)
+                        if extendJSON: cJson['SinceLastBill_cost__' + cost_unit] = round(cost_value, 2)
+                        
                         
         # END file log 
         
@@ -553,7 +935,7 @@ def processMeterData(data):
             else:
                 print("meterData:", json.dumps(cJson))
         
-        if MQTTenabled: 
+        if MQTTenabled and publishJSON: 
             mqttc.publish(statTopic + "/json", json.dumps(cJson), qos=0, retain=False)
 
 def publishStatMsg(data):

+ 15 - 4
S0Meters_py/s0meters.yml

@@ -4,12 +4,15 @@
   unit: "kWh"
   digits: 3
   momType: "power"
-  #momUnit: "kW"
-  #momDigits: 3
-  #momFactor: 1
   momUnit: "W"
   momDigits: 0
   momFactor: 1000
+  #conv_unit: "kWh"
+  #conv_factor: 10.73184
+  #conv_digits: 2
+  cost_unit: "EUR"
+  cost_per_unit: 0.330995
+  cost_from_conv: False
   statTopic: "Powermeter"
   influxInstance_energy: "energymeters"
   influxInstance_mom: "energy_momentary"
@@ -18,6 +21,10 @@
   influxFieldName_energy: "Energy_Total__kWh"
   influxFieldName_mom: "Power__W"
   influxMinWriteInterval_energy: 300
+  MQTTPublishJSON: False
+  historyPublishInterval: 300
+  billingStartDate: '2022-03-03'
+  
 2:
   name: "Gas"
   impPerUnit: 100
@@ -52,4 +59,8 @@
   influxFieldName_mom: "Gas_Usage__m3_h"
   #influxFieldName_mom_conv1: "Gas_Usage__kW"
   #influxFieldName_mom_conv2: "Gas_Usage__l_min"
-  influxMinWriteInterval_energy: 300
+  influxMinWriteInterval_energy: 300
+  MQTTPublishJSON: False
+  historyPublishInterval: 300
+  billingStartDate: '2022-03-02'
+