From 91d682cd97d09dd19eb2e804c670939fe0b29163 Mon Sep 17 00:00:00 2001
From: Thomas Kolb <cfr34k-github@tkolb.de>
Date: Sun, 19 Jan 2020 20:10:07 +0100
Subject: [PATCH] First version of collectd plugin

---
 README.md                    |  48 +++++++++++++++-
 collectd/collectd_sem6000.py | 108 +++++++++++++++++++++++++++++++++++
 2 files changed, 155 insertions(+), 1 deletion(-)
 create mode 100755 collectd/collectd_sem6000.py

diff --git a/README.md b/README.md
index 9929704..5333571 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,11 @@
+# SEM6000 Python Library
+
+SEM6000 is a energy meter and power switch with Bluetooth 4.0.
+
+This library provides a Python module for these devices
+
+## Run the example code
+
 ```
 $ git clone … sem6000
 $ cd sem6000
@@ -5,4 +13,42 @@ $ virtualenv -p python3 python3_venv
 $ . ./python3_venv/bin/activate
 $ pip3 install -r requirements.txt
 $ python3 example.py
-```
\ No newline at end of file
+```
+
+## Collectd Plugin
+
+You can find a Plugin for [collectd](https://collectd.org) in the `collectd`
+subdirectory.
+
+Installation procedure (the target directory may be changed of course):
+
+```shell
+# mkdir -p /usr/local/lib/collectd/python
+# cp collectd/collectd_sem6000.py /usr/local/lib/collectd/python
+# cp sem6000.py /usr/local/lib/collectd/python
+```
+
+Add or adjust the configuration for your collectd’s Python plugin as follows:
+
+```
+<Plugin python>
+  ModulePath "/usr/local/share/collectd/python"
+  LogTraces true
+  Interactive false
+  Import "collectd_sem6000"
+
+  <Module collectd_sem6000>
+    Address "12:34:56:78:90:ab"
+    SocketName "FirstSocket"
+  </Module>
+  <Module collectd_sem6000>
+    Address "ab:cd:ef:13:37:42"
+    SocketName "ASecondSocket"
+  </Module>
+  # ...
+</Plugin>
+```
+
+Make sure that everything listed in `requirements.txt` is available to the user
+running collectd.
+
diff --git a/collectd/collectd_sem6000.py b/collectd/collectd_sem6000.py
new file mode 100755
index 0000000..76f19bb
--- /dev/null
+++ b/collectd/collectd_sem6000.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+# coding: utf-8
+# vim: noet ts=2 sw=2 sts=2
+
+import os
+import collectd
+
+from sem6000 import SEMSocket
+import bluepy
+
+instances = []
+
+def init_func():
+	pass
+
+def config_func(cfg):
+	global instances
+
+	config = {}
+
+	for node in cfg.children:
+		key = node.key.lower()
+		value = node.values[0]
+
+		if key in ['address', 'socketname']:
+			config[key] = value
+
+	if 'address' not in config.keys():
+		collectd.error('sem6000: address must be set')
+		return
+
+	if 'socketname' not in config.keys():
+		config['socketname'] = config['address'].replace(':', '')
+
+	instances.append( {'config': config, 'socket': None} )
+
+def read_func():
+	global instances
+
+	for inst in instances:
+		config = inst['config']
+
+		try:
+			if inst['socket'] == None:
+				collectd.info("sem6000: Connecting to {}...".format(config['address']))
+
+				inst['socket'] = SEMSocket(config['address'])
+				collectd.info("sem6000: Connected.")
+
+			inst['socket'].getStatus()
+		except (SEMSocket.NotConnectedException, bluepy.btle.BTLEDisconnectError, BrokenPipeError) as e:
+			collectd.warning("sem6000: Exception caught: {}".format(e))
+			collectd.warning("sem6000: Restarting on next cycle...")
+
+			if inst['socket'] != None:
+				inst['socket'].disconnect()
+				inst['socket'] = None
+
+		socket = inst['socket']
+
+		if socket != None and socket.voltage != 0:
+			collectd.debug("Uploading values for {}".format(socket.mac_address))
+
+			val = collectd.Values(plugin = 'sem6000-{}'.format(config['socketname']))
+
+			val.type = 'voltage'
+			val.type_instance = 'grid'
+			val.values = [ socket.voltage ]
+			val.dispatch()
+
+			val.type = 'current'
+			val.type_instance = 'load'
+			val.values = [ socket.current ]
+			val.dispatch()
+
+			val.type = 'power'
+			val.type_instance = 'real_power'
+			val.values = [ socket.power ]
+			val.dispatch()
+
+			val.type = 'gauge'
+			val.type_instance = 'power_factor'
+			val.values = [ socket.power_factor ]
+			val.dispatch()
+
+			val.type = 'gauge'
+			val.type_instance = 'load_on'
+			val.values = [ socket.powered ]
+			val.dispatch()
+
+			val.type = 'frequency'
+			val.type_instance = 'grid'
+			val.values = [ socket.frequency ]
+			val.dispatch()
+
+def shutdown_func():
+	global instances
+
+	for inst in instances:
+		if inst['socket'] != None:
+			inst['socket'].disconnect()
+
+	instances = []
+
+collectd.register_config(config_func)
+collectd.register_init(init_func)
+collectd.register_read(read_func)
+collectd.register_shutdown(shutdown_func)