mirror of
https://github.com/Luzifer/mqtt2influx.git
synced 2024-12-20 09:31:23 +00:00
Initial version
This commit is contained in:
commit
6f59fa0b0a
10 changed files with 234 additions and 0 deletions
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.env
|
||||||
|
.venv
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
config.yml
|
||||||
|
.env
|
||||||
|
.venv
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
FROM python:alpine
|
||||||
|
|
||||||
|
COPY requirements.txt /src/requirements.txt
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
RUN set -ex \
|
||||||
|
&& pip install -r /src/requirements.txt
|
||||||
|
|
||||||
|
VOLUME ["/config"]
|
||||||
|
ENV CONFIG_PATH=/config/config.yml
|
||||||
|
|
||||||
|
COPY . /src
|
||||||
|
CMD ["/usr/local/bin/python3", "main.py"]
|
11
Makefile
Normal file
11
Makefile
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
default:
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf .venv
|
||||||
|
|
||||||
|
freeze: testenv
|
||||||
|
./.venv/bin/pip freeze >requirements.txt
|
||||||
|
|
||||||
|
testenv: clean
|
||||||
|
python -m venv .venv
|
||||||
|
./.venv/bin/pip install -r requirements.txt
|
21
config_sample.yml
Normal file
21
config_sample.yml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
influx_db: 'mqtt_map'
|
||||||
|
|
||||||
|
subscriptions:
|
||||||
|
|
||||||
|
tele/kettle/SENSOR:
|
||||||
|
- metric: power
|
||||||
|
tags:
|
||||||
|
device: kettle
|
||||||
|
transform: !lambda "x: json.loads(x)['ENERGY']['Power']"
|
||||||
|
- metric: current
|
||||||
|
tags:
|
||||||
|
device: kettle
|
||||||
|
transform: !lambda "x: json.loads(x)['ENERGY']['Current']"
|
||||||
|
- metric: voltage
|
||||||
|
tags:
|
||||||
|
device: kettle
|
||||||
|
transform: !lambda "x: json.loads(x)['ENERGY']['Voltage']"
|
||||||
|
|
||||||
|
...
|
33
influx.py
Normal file
33
influx.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from influxdb import InfluxDBClient
|
||||||
|
import vault
|
||||||
|
|
||||||
|
|
||||||
|
class Influx():
|
||||||
|
|
||||||
|
def __init__(self, database):
|
||||||
|
cfg = vault.read_data('secret/mqtt2influx/influxdb')
|
||||||
|
|
||||||
|
self.database = database
|
||||||
|
self.client = InfluxDBClient(cfg['host'], cfg['port'],
|
||||||
|
cfg['user'], cfg['pass'], self.database)
|
||||||
|
|
||||||
|
def submit(self, body):
|
||||||
|
"""
|
||||||
|
submit("mydatabase", [
|
||||||
|
{
|
||||||
|
"measurement": "cpu_load_short",
|
||||||
|
"tags": {
|
||||||
|
"host": "server01",
|
||||||
|
"region": "us-west"
|
||||||
|
},
|
||||||
|
"time": "2009-11-10T23:00:00Z",
|
||||||
|
"fields": {
|
||||||
|
"Float_value": 0.64,
|
||||||
|
"Int_value": 3,
|
||||||
|
"String_value": "Text",
|
||||||
|
"Bool_value": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
"""
|
||||||
|
self.client.write_points(body, database=self.database)
|
104
main.py
Normal file
104
main.py
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from yaml_lambda import *
|
||||||
|
from influx import Influx
|
||||||
|
import vault
|
||||||
|
|
||||||
|
|
||||||
|
class MQTT2InfluxDB():
|
||||||
|
|
||||||
|
def __init__(self, config_path='config.yml'):
|
||||||
|
with open(config_path) as cfg_file:
|
||||||
|
self.config = yaml.safe_load(cfg_file)
|
||||||
|
|
||||||
|
self.influx = Influx(self.obj_get(
|
||||||
|
self.config, 'influx_db', 'mqtt2influxdb'))
|
||||||
|
|
||||||
|
def obj_get(self, obj, key, default=None):
|
||||||
|
if key in obj:
|
||||||
|
return obj[key]
|
||||||
|
return default
|
||||||
|
|
||||||
|
def on_connect(self, client, userdata, flags, rc):
|
||||||
|
topics = [(topic, 1) for topic in self.config['subscriptions'].keys()]
|
||||||
|
result, _ = client.subscribe(topics)
|
||||||
|
|
||||||
|
if result != mqtt.MQTT_ERR_SUCCESS:
|
||||||
|
raise(Exception('MQTT subscribe failed: {}'.format(result)))
|
||||||
|
|
||||||
|
logging.info('MQTT connected and subscribed')
|
||||||
|
|
||||||
|
def on_message(self, client, userdata, msg):
|
||||||
|
points = []
|
||||||
|
|
||||||
|
for processor in self.obj_get(self.config['subscriptions'], msg.topic, []):
|
||||||
|
tffn = self.obj_get(processor, 'transform',
|
||||||
|
YAMLLambda('x: float(x)'))
|
||||||
|
value = tffn.run(msg.payload)
|
||||||
|
|
||||||
|
points.append({
|
||||||
|
'measurement': processor['metric'],
|
||||||
|
'tags': self.obj_get(processor, 'tags', {}),
|
||||||
|
'fields': {
|
||||||
|
'value': value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
logging.debug(
|
||||||
|
'MQTT Message received: topic={topic} metric={metric} value={value}'.format(
|
||||||
|
topic=msg.topic,
|
||||||
|
metric=processor['metric'],
|
||||||
|
value=value,
|
||||||
|
))
|
||||||
|
|
||||||
|
if len(points) > 0:
|
||||||
|
self.influx.submit(points)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
client = mqtt.Client()
|
||||||
|
client.enable_logger()
|
||||||
|
client.on_connect = self.on_connect
|
||||||
|
client.on_message = self.on_message
|
||||||
|
|
||||||
|
mqtt_config = vault.read_data('secret/mqtt2influx/mqtt')
|
||||||
|
|
||||||
|
client.username_pw_set(
|
||||||
|
self.obj_get(mqtt_config, 'user', self.obj_get(
|
||||||
|
os.environ, 'MQTT_USER')),
|
||||||
|
self.obj_get(mqtt_config, 'pass', self.obj_get(
|
||||||
|
os.environ, 'MQTT_PASS')),
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.debug('Connecting to MQTT broker...')
|
||||||
|
|
||||||
|
client.connect(
|
||||||
|
self.obj_get(mqtt_config, 'host', self.obj_get(
|
||||||
|
os.environ, 'MQTT_HOST')),
|
||||||
|
port=self.obj_get(mqtt_config, 'port', self.obj_get(
|
||||||
|
os.environ, 'MQTT_PORT', 1883)),
|
||||||
|
keepalive=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
client.loop_forever()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loglevel = logging.INFO
|
||||||
|
if 'LOG_LEVEL' in os.environ and os.environ['LOG_LEVEL'] == 'DEBUG':
|
||||||
|
loglevel = logging.DEBUG
|
||||||
|
|
||||||
|
configpath = 'config.yml'
|
||||||
|
if 'CONFIG_PATH' in os.environ:
|
||||||
|
configpath = os.environ['CONFIG_PATH']
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
datefmt='%m/%d/%Y %I:%M:%S %p',
|
||||||
|
format='[%(asctime)s][%(levelname)s] %(message)s',
|
||||||
|
level=loglevel,
|
||||||
|
)
|
||||||
|
|
||||||
|
inst = MQTT2InfluxDB(configpath)
|
||||||
|
inst.run()
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
paho-mqtt
|
||||||
|
PyYAML
|
||||||
|
hvac
|
||||||
|
influxdb
|
19
vault.py
Normal file
19
vault.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import hvac
|
||||||
|
import os
|
||||||
|
|
||||||
|
if not 'VAULT_ADDR' in os.environ or not 'VAULT_ROLE_ID' in os.environ:
|
||||||
|
raise Exception('VAULT_ADDR or VAULT_ROLE_ID are missing')
|
||||||
|
|
||||||
|
vault = hvac.Client(os.environ['VAULT_ADDR'])
|
||||||
|
auth = vault.auth_approle(os.environ['VAULT_ROLE_ID'])
|
||||||
|
if 'auth' in auth and 'client_token' in auth['auth']:
|
||||||
|
vault.token = auth['auth']['client_token']
|
||||||
|
else:
|
||||||
|
raise Exception('Authorization to Vault failed!')
|
||||||
|
|
||||||
|
|
||||||
|
def read_data(key):
|
||||||
|
resp = vault.read(key)
|
||||||
|
if 'data' not in resp:
|
||||||
|
raise Exception('Unable to read configuration')
|
||||||
|
return resp['data']
|
24
yaml_lambda.py
Normal file
24
yaml_lambda.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
class YAMLLambda(yaml.YAMLObject):
|
||||||
|
yaml_tag = '!lambda'
|
||||||
|
|
||||||
|
def __init__(self, lambda_code):
|
||||||
|
self.code = lambda_code
|
||||||
|
|
||||||
|
def run(self, in_value):
|
||||||
|
return eval('lambda {}'.format(self.code))(in_value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(cls, loader, node):
|
||||||
|
return YAMLLambda(node.value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(cls, dumper, data):
|
||||||
|
return dumper.represent_scalar(cls.yaml_tag, data.code)
|
||||||
|
|
||||||
|
|
||||||
|
yaml.SafeLoader.add_constructor('!lambda', YAMLLambda.from_yaml)
|
||||||
|
yaml.SafeDumper.add_multi_representer(YAMLLambda, YAMLLambda.to_yaml)
|
Loading…
Reference in a new issue