Browse Source

Added files

Samuel Vestlin 6 years ago
parent
commit
ca8643970e
7 changed files with 247 additions and 0 deletions
  1. 4 0
      CHANGELOG.md
  2. 10 0
      SDM120.yml
  3. 23 0
      SDM630.yml
  4. 5 0
      influx_config.yml
  5. 17 0
      meters.yml
  6. 148 0
      read_energy_meter.py
  7. 40 0
      setup.py

+ 4 - 0
CHANGELOG.md

@@ -0,0 +1,4 @@
+## Change Log
+
+## [0.1] - 2017-11-09
+* Read registers of RS485 modbus based energy meter 

+ 10 - 0
SDM120.yml

@@ -0,0 +1,10 @@
+Voltage Phase 1 : 0
+Current Phase 1 : 6
+Active power Phase 1 : 12
+Apparent power Phase 1 : 18
+Reactive power Phase 1 : 24
+Power factor Phase 1 : 30
+Frequency : 70
+Import active energy : 72
+Export active energy : 74
+Total active energy : 86

+ 23 - 0
SDM630.yml

@@ -0,0 +1,23 @@
+Voltage Phase 1 : 0
+Voltage Phase 2 : 2
+Voltage Phase 2 : 4
+Current Phase 1 : 6
+Current Phase 2 : 8
+Current Phase 3 : 10
+Active power Phase 1 : 12
+Active power Phase 2 : 14
+Active power Phase 3 : 16
+Apparent power Phase 1 : 18
+Apparent power Phase 2 : 20
+Apparent power Phase 3 : 22
+Reactive power Phase 1 : 24
+Reactive power Phase 2 : 26
+Reactive power Phase 3 : 28
+Power factor Phase 1 : 30
+Power factor Phase 2 : 32
+Power factor Phase 3 : 34
+Sum of line currents : 48
+Frequency : 70
+Import active energy : 72
+Export active energy : 74
+Total active energy : 86

+ 5 - 0
influx_config.yml

@@ -0,0 +1,5 @@
+host : 'localhost'
+port : 8086
+user : 'root'
+password : 'root'
+dbname : 'db_meters'

+ 17 - 0
meters.yml

@@ -0,0 +1,17 @@
+meters:
+    - name : Meter Group 1
+      type : SDM120.yml
+      id : 1     # this is the slave address number
+      baudrate : 9600   # Baud
+      bytesize : 8
+      parity : even # none | odd | even
+      stopbits : 1
+      timeout  : 0.5   # seconds
+    - name : Meter Group 2
+      type : SDM630.yml
+      id : 13     # this is the slave address number
+      baudrate : 9600   # Baud
+      bytesize : 8
+      parity : none # none | odd | even
+      stopbits : 1
+      timeout  : 0.5   # seconds

+ 148 - 0
read_energy_meter.py

@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+
+from influxdb import InfluxDBClient
+from datetime import datetime, timedelta
+from os import path
+import minimalmodbus
+import time
+import yaml
+
+
+class DataCollector:
+    def __init__(self, influx_client, meter_yaml):
+        self.influx_client = influx_client
+        self.meter_yaml = meter_yaml
+        self.max_iterations = None  # run indefinitely by default
+        self.meter_map = None
+        self.meter_map_last_change = -1
+        print 'Meters:'
+        for meter in sorted(self.get_meters()):
+            print '\t', meter['id'], '<-->', meter['name']
+
+    def get_meters(self):
+        assert path.exists(self.meter_yaml), 'Meter map not found: %s' % self.meter_yaml
+        if path.getmtime(self.meter_yaml) != self.meter_map_last_change:
+            try:
+                print('Reloading meter map as file changed')
+                new_map = yaml.load(open(self.meter_yaml))
+                self.meter_map = new_map['meters']
+                self.meter_map_last_change = path.getmtime(self.meter_yaml)
+            except Exception as e:
+                print('Failed to re-load meter map, going on with the old one. Error:')
+                print(e)
+        return self.meter_map
+
+    def collect_and_store(self):
+        meters = self.get_meters()
+        t_utc = datetime.utcnow()
+        t_str = t_utc.isoformat() + 'Z'
+
+        instrument = minimalmodbus.Instrument('/dev/ttyAMA0', 1) # port name, slave address (in decimal)
+        instrument.mode = minimalmodbus.MODE_RTU   # rtu or ascii mode
+        #instrument.debug = True
+        datas = dict()
+        meter_id_name = dict() # mapping id to name
+
+        for meter in meters:
+            meter_id_name[meter['id']] = meter['name']
+            instrument.serial.baudrate = meter['baudrate']
+            instrument.serial.bytesize = meter['bytesize']
+            if meter['parity'] == 'none':
+                instrument.serial.parity = minimalmodbus.serial.PARITY_NONE
+            elif meter['parity'] == 'odd':
+                instrument.serial.parity = minimalmodbus.serial.PARITY_ODD
+            elif meter['parity'] == 'even':
+                instrument.serial.parity = minimalmodbus.serial.PARITY_EVEN
+            else:
+                print('Error! No parity specified')
+                raise
+            instrument.serial.stopbits = meter['stopbits']
+            instrument.serial.timeout  = meter['timeout']    # seconds
+            instrument.address = meter['id']    # this is the slave address number
+
+            #print 'Reading meter %s (%s).' % (meters[meter_id], meter_id)
+            start_time = time.time()
+            parameters = yaml.load(open(meter['type']))
+            datas[meter['id']] = dict()
+
+            for parameter in parameters:
+                try:
+                    datas[meter['id']][parameter] = instrument.read_float(parameters[parameter], 4, 2)
+                    pass
+                except Exception as e:
+                    print('Reading register %i from meter %i. Error:' % parameters[parameter], meter['id'])
+                    print(e)
+                    raise
+
+            datas[meter['id']]['Time to read'] =  time.time() - start_time
+
+        json_body = [
+            {
+                'measurement': 'energy',
+                'tags': {
+                    'id': meter_id,
+                    'meter': meter['name'],
+                },
+                'time': t_str,
+                'fields': datas[meter_id]
+            }
+            for meter_id in datas
+        ]
+        if len(json_body) > 0:
+            try:
+                self.influx_client.write_points(json_body)
+                print(t_str + ' Data written for %d meters.' % len(json_body))
+            except Exception as e:
+                print('Data not written! Error:')
+                print(e)
+                raise
+        else:
+            print(t_str, 'No data sent.')
+
+
+def repeat(interval_sec, max_iter, func, *args, **kwargs):
+    from itertools import count
+    import time
+    starttime = time.time()
+    for i in count():
+        retry = False # Reset retry flag
+        if i % 1000 == 0:
+            print('Collected %d readouts' % i)
+        try:
+            func(*args, **kwargs)
+        except Exception as ex:
+            print('Error!')
+            print(ex)
+            retry = True # Force imidiate retry, skip sleep
+        if (retry == False) & (interval_sec > 0):
+            time.sleep(interval_sec - ((time.time() - starttime) % interval_sec))
+        if max_iter and i >= max_iter:
+            return
+
+
+if __name__ == '__main__':
+
+    import argparse
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--interval', default=60,
+                        help='Meter readout interval (seconds), default 60')
+    parser.add_argument('--meters', default='meters.yml',
+                        help='YAML file containing Meter ID, name, type etc. Default "meters.yml"')
+    args = parser.parse_args()
+    interval = int(args.interval)
+
+    # Create the InfluxDB object
+    influx_config = yaml.load(open('influx_config.yml'))
+    client = InfluxDBClient(influx_config['host'],
+                            influx_config['port'],
+                            influx_config['user'],
+                            influx_config['password'],
+                            influx_config['dbname'])
+
+    collector = DataCollector(influx_client=client,
+                              meter_yaml='meters.yml')
+
+    repeat(interval,
+           max_iter=collector.max_iterations,
+           func=lambda: collector.collect_and_store())

+ 40 - 0
setup.py

@@ -0,0 +1,40 @@
+import sys
+from setuptools import setup
+
+import ruuvitag_sensor
+
+try:
+    import pypandoc
+    readme = pypandoc.convert('README.md', 'rst')
+    readme = readme.replace("\r", "")
+except ImportError:
+    import io
+    with io.open('README.md', encoding="utf-8") as f:
+        readme = f.read()
+
+setup(name='energy_meter_logger',
+      version=0.1,
+      description='Read Energy Meter data using RS485 Modbus '+
+      'and store in local database.',
+      long_description=readme,
+      url='https://github.com/samuelphy/energy-meter-logger',
+      download_url='',
+      author='Samuel Vestlin',
+      author_email='samuel@elphy.se',
+      platforms='Raspberry Pi',
+      classifiers=[
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'License :: MIT License',
+        'Operating System :: Raspbian',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3'
+      ],
+      keywords='Energy Meter RS485 Modbus',
+      install_requires=[]+(['pyserial','minimalmodbus', 'influxdb', 'pyyaml'] if "linux" in sys.platform else []),
+      license='MIT',
+      packages=[],
+      include_package_data=True,
+      tests_require=[],
+      test_suite='',
+      zip_safe=True)