Compare commits
4 commits
69211935ba
...
366a50cc84
Author | SHA1 | Date | |
---|---|---|---|
Felix Niederwanger | 366a50cc84 | ||
Felix Niederwanger | 159bcb15dc | ||
a8ca515cc4 | |||
Felix Niederwanger | cc649462ea |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -34,6 +34,7 @@
|
|||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
__debug_bin
|
||||
|
||||
# GoLand
|
||||
.idea
|
||||
|
@ -44,8 +45,9 @@
|
|||
# Build files
|
||||
build
|
||||
build/
|
||||
/meteo-influx-gateway
|
||||
/meteod
|
||||
|
||||
## Custom config files
|
||||
*.conf
|
||||
*.toml
|
||||
*.ini
|
||||
|
|
4
Makefile
4
Makefile
|
@ -1,11 +1,11 @@
|
|||
default: all
|
||||
all: meteo-influx-gateway
|
||||
all: meteod
|
||||
|
||||
install: all
|
||||
|
||||
## === Builds =============================================================== ##
|
||||
|
||||
meteo-influx-gateway: cmd/meteo-influx-gateway/meteo-mqtt-influxdb.go cmd/meteo-influx-gateway/influxdb.go cmd/meteo-influx-gateway/mqtt.go
|
||||
meteod: cmd/meteod/*.go
|
||||
go build -o $@ $^
|
||||
|
||||
## === Tests ================================================================ ##
|
||||
|
|
63
README.md
63
README.md
|
@ -1,50 +1,61 @@
|
|||
# meteo
|
||||
|
||||
[![Build Status](https://travis-ci.org/grisu48/meteo.svg?branch=master)](https://travis-ci.org/grisu48/meteo)
|
||||
meteo is a lightweight environmental monitoring solution.
|
||||
The project aims to provide an easy environmental monitoring system for different sensors. Sensors publish their data via [MQTT](https://mqtt.org/).
|
||||
|
||||
Lightweight environmental monitoring solution
|
||||
|
||||
This project aims to provide a centralized environmental and room monitoring system for different sensors using mqtt. Data storage is supported via a `meteo-influxdb` gateway.
|
||||
Sensor data measurements can be stored in an InfluxDB database using the `meteod` server.
|
||||
|
||||
# Nodes/Sensors
|
||||
|
||||
Current `meteo` sensors are [ESP32](https://www.espressif.com/en/products/hardware/esp32/overview) based nodes with a [BME280](https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/) sensor, but in principal every node that is able to push data via `MQTT` can be attached to the server.
|
||||
meteo sensors can be anything that publish data via MQTT in a certain data format (see below).
|
||||
|
||||
See the [EPS32](Sensors/ESP32) folder in Sensors for the current supported `node`
|
||||
Currently some sensor nodes are provided by the project in the [Sensors](Sensors) folder.
|
||||
|
||||
Che recommended hardware basis for meteo sensor nodes is [ESP32](https://www.espressif.com/en/products/hardware/esp32/overview), however everything that works with MQTT will work.
|
||||
|
||||
## MQTT packets
|
||||
|
||||
MQTT is considered as trusted network in `meteod`. New station will be automatically added. If you want to have better access control, please use http and disable `MQTT` in your config file.
|
||||
|
||||
Every node that publishes `MQTT` packets in the given format is accepted.
|
||||
|
||||
# Node ID: 1, replace in topic and payload
|
||||
|
||||
## meteo packets
|
||||
TOPIC: meteo/1
|
||||
PAYLOAD: {"id":1,"name":"Node","t":23.36,"hum":44.56,"p":99720.89}
|
||||
|
||||
## lightning packet
|
||||
TOPIC: meteo/lightning/1
|
||||
PAYLOAD: {"node":1,"timestamp":0,"distance":12.1}
|
||||
# if the timestamp is 0, the server replaces it with it's current time
|
||||
PAYLOAD: {"id":1,"name":"Node","values":{"t":23.36,"hum":44.56,"p":99720.89}}
|
||||
|
||||
# meteo-influx-gateway
|
||||
A more verbose example of the payload is the following json packet:
|
||||
|
||||
The provided `meteo-influx-gateway` is a program to collect meteo data points from mqtt and push them to a influxdb database. This gateway is written in go and needs to be build
|
||||
```json
|
||||
{"id":12,"name":"attic","tsp":123456789,"measurement":"meteo","values":{"t":18.12,"hum":20.48,"p":1024539.17}}
|
||||
```
|
||||
|
||||
The MQTT topic can be `meteo/1` where 1 is the NodeID, or simply `meteo`. In the latter case, the `"id"` field is obligatory, otherwise the packet is invalid.
|
||||
|
||||
# meteod - meteo Server
|
||||
|
||||
`meteod` is a server program to collect meteo data points from mqtt and push them to a InfluxDB database (Version 2). This program is written in go and needs to be build
|
||||
|
||||
go build ./...
|
||||
|
||||
The gateway is configured via a [simple INI file](meteo-influx-gateway.ini.example):
|
||||
The gateway is configured via a [meteod.conf](meteod.conf.example) file.
|
||||
|
||||
```ini
|
||||
[mqtt]
|
||||
remote = "127.0.0.1"
|
||||
Usage:
|
||||
|
||||
./meteod -c meteod.conf
|
||||
|
||||
Use `./meteod -h` to display available options
|
||||
|
||||
[influxdb]
|
||||
remote = "http://127.0.0.1:8086"
|
||||
username = "meteo"
|
||||
password = "meteo"
|
||||
database = "meteo"
|
||||
```
|
||||
|
||||
Usage: ./meteod [OPTIONS]
|
||||
OPTIONS
|
||||
-h,--help Display this help message
|
||||
-c,--config FILE Load config file
|
||||
-v, --verbose Verbose mode
|
||||
--mqtt MQTT Set mqtt server
|
||||
--mqtt-client-id ID Set mqtt client id
|
||||
--influx HOST Set influxdb hostname
|
||||
--org ORGANIZATION Set influxdb organization
|
||||
--bucket BUCKET Set influxdb bucket
|
||||
--token TOKEN Set influxdb token
|
||||
--http BINDADDR Enable http server on the given address
|
||||
```
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
client "github.com/influxdata/influxdb1-client"
|
||||
)
|
||||
|
||||
type InfluxDB struct {
|
||||
database string
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func ConnectInfluxDB(remote string, username string, password string, database string) (InfluxDB, error) {
|
||||
var influx InfluxDB
|
||||
influx.database = database
|
||||
|
||||
host, err := url.Parse(remote)
|
||||
if err != nil {
|
||||
return influx, err
|
||||
}
|
||||
conf := client.Config{
|
||||
URL: *host,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
influx.client, err = client.NewClient(conf)
|
||||
return influx, err
|
||||
}
|
||||
|
||||
// Ping the InfluxDB server
|
||||
func (influx *InfluxDB) Ping() (time.Duration, string, error) {
|
||||
return influx.client.Ping()
|
||||
}
|
||||
|
||||
// Write a measurement into the database
|
||||
func (influx *InfluxDB) Write(measurement string, tags map[string]string, fields map[string]interface{}) error {
|
||||
point := client.Point{
|
||||
Measurement: measurement,
|
||||
Tags: tags,
|
||||
Fields: fields,
|
||||
//Time: time.Now(),
|
||||
Precision: "s",
|
||||
}
|
||||
pts := make([]client.Point, 0)
|
||||
pts = append(pts, point)
|
||||
bps := client.BatchPoints{
|
||||
Points: pts,
|
||||
Database: influx.database,
|
||||
//RetentionPolicy: "default",
|
||||
}
|
||||
_, err := influx.client.Write(bps)
|
||||
return err
|
||||
}
|
|
@ -1,279 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// Config is the internal singleton configuration for this program
|
||||
type Config struct {
|
||||
MqttHost string `ini:"mqtt,remote"`
|
||||
MqttClientID string `ini:"mqtt,clientid"`
|
||||
InfluxHost string `ini:"influxdb,remote"`
|
||||
InfluxUsername string `ini:"influxdb,username"`
|
||||
InfluxPassword string `ini:"influxdb,password"`
|
||||
InfluxDatabase string `ini:"influxdb,database"`
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func (c *Config) loadIni(filename string) error {
|
||||
cfg, err := ini.Load(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mqtt := cfg.Section("mqtt")
|
||||
mqtthost := mqtt.Key("remote").String()
|
||||
if mqtthost != "" {
|
||||
c.MqttHost = mqtthost
|
||||
}
|
||||
mqttclientid := mqtt.Key("clientid").String()
|
||||
if mqttclientid != "" {
|
||||
c.MqttClientID = mqttclientid
|
||||
}
|
||||
influx := cfg.Section("influxdb")
|
||||
influxhost := influx.Key("remote").String()
|
||||
if influxhost != "" {
|
||||
c.InfluxHost = influxhost
|
||||
}
|
||||
influxuser := influx.Key("username").String()
|
||||
if influxuser != "" {
|
||||
c.InfluxUsername = influxuser
|
||||
}
|
||||
influxpass := influx.Key("password").String()
|
||||
if influxpass != "" {
|
||||
c.InfluxPassword = influxpass
|
||||
}
|
||||
influxdb := influx.Key("database").String()
|
||||
if influxdb != "" {
|
||||
c.InfluxDatabase = influxdb
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var config Config
|
||||
|
||||
var influx InfluxDB
|
||||
|
||||
func assembleJson(node int, data map[string]interface{}) string {
|
||||
ret := fmt.Sprintf("node %d: {", node)
|
||||
first := true
|
||||
for k, v := range data {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
ret += ", "
|
||||
}
|
||||
ret += fmt.Sprintf("\"%s\":%f", k, v)
|
||||
}
|
||||
ret += "}"
|
||||
return ret
|
||||
}
|
||||
|
||||
func received(msg mqtt.Message) {
|
||||
data := make(map[string]interface{}, 0)
|
||||
if err := json.Unmarshal(msg.Payload(), &data); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "json unmarshall error: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// We don't log the name, remove it, if present
|
||||
if _, ok := data["name"]; ok {
|
||||
delete(data, "name")
|
||||
}
|
||||
// ID is taken from the topic
|
||||
if _, ok := data["id"]; ok {
|
||||
delete(data, "id")
|
||||
}
|
||||
|
||||
// Parse node ID from topic
|
||||
nodeID, err := strconv.Atoi(msg.Topic()[6:])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "invalid meteo id\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Write to InfluxDB
|
||||
for k, v := range data {
|
||||
tags := map[string]string{"node": fmt.Sprintf("%d", nodeID)}
|
||||
f, err := strconv.ParseFloat(fmt.Sprintf("%f", v), 32)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "non-float value received: %s\n", err)
|
||||
return
|
||||
}
|
||||
fields := map[string]interface{}{"value": f}
|
||||
if err := influx.Write(k, tags, fields); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot write to influxdb: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// OK
|
||||
if config.Verbose {
|
||||
fmt.Println(assembleJson(nodeID, data))
|
||||
}
|
||||
}
|
||||
|
||||
// awaits SIGINT or SIGTERM
|
||||
func awaitTerminationSignal() {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
done := make(chan bool, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-sigs
|
||||
fmt.Println(sig)
|
||||
done <- true
|
||||
}()
|
||||
<-done
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("meteo-influxdb-gateway")
|
||||
fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0])
|
||||
fmt.Println("OPTIONS")
|
||||
fmt.Println(" -h,--help Display this help message")
|
||||
fmt.Println(" -c,--config FILE Load config file")
|
||||
fmt.Println(" -v, --verbose Verbose mode")
|
||||
fmt.Println(" --mqtt MQTT Set mqtt server")
|
||||
fmt.Println(" --mqtt-client-id ID Set mqtt client id")
|
||||
fmt.Println(" --influx HOST Set influxdb hostname")
|
||||
fmt.Println(" --username USER Set influxdb username")
|
||||
fmt.Println(" --password PASS Set influxdb password")
|
||||
fmt.Println(" --database DB Set influxdb database")
|
||||
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
// Default settings
|
||||
config.MqttHost = "127.0.0.1"
|
||||
config.InfluxHost = "http://127.0.0.1:8086"
|
||||
config.InfluxUsername = "meteo"
|
||||
config.InfluxPassword = ""
|
||||
config.InfluxDatabase = "meteo"
|
||||
config.Verbose = false
|
||||
|
||||
configFile := "/etc/meteo/meteo-influx-gateway.ini"
|
||||
if fileExists(configFile) {
|
||||
if err := config.loadIni(configFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error loading ini file %s: %s\n", configFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
args := os.Args[1:]
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := strings.TrimSpace(args[i])
|
||||
if arg == "" {
|
||||
continue
|
||||
} else if arg[0] == '-' {
|
||||
last := i >= len(args)-1
|
||||
if arg == "-h" || arg == "--help" {
|
||||
printUsage()
|
||||
return
|
||||
} else if arg == "-c" || arg == "--config" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: config file\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
configFile = args[i]
|
||||
if err := config.loadIni(configFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error loading ini file %s: %s\n", configFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if arg == "-v" || arg == "--verbose" {
|
||||
config.Verbose = true
|
||||
} else if arg == "--mqtt" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: mqtt remote\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.MqttHost = args[i]
|
||||
} else if arg == "--mqtt-client-id" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: mqtt-client-id remote\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.MqttClientID = args[i]
|
||||
} else if arg == "--influx" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: influx remote\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.InfluxHost = args[i]
|
||||
} else if arg == "--username" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: influx username\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.InfluxUsername = args[i]
|
||||
} else if arg == "--password" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: influx password\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.InfluxPassword = args[i]
|
||||
} else if arg == "--database" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: influx database\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.InfluxDatabase = args[i]
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Invalid argument: %s\n", arg)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect InfluxDB
|
||||
influx, err = ConnectInfluxDB(config.InfluxHost, config.InfluxUsername, config.InfluxPassword, config.InfluxDatabase)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot connect to influxdb: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if ping, version, err := influx.Ping(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot ping influxdb: %s\n", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
if config.Verbose {
|
||||
fmt.Printf("influxdb connected: v%s (Ping: %d ms)\n", version, ping.Milliseconds())
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to mqtt server
|
||||
mqtt := MQTT{}
|
||||
mqtt.ClientID = config.MqttClientID
|
||||
mqtt.Verbose = config.Verbose
|
||||
mqtt.Callback = received
|
||||
if err := mqtt.Connect(config.MqttHost, 1883); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mqtt error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("meteo-mqtt-influx gateway is up and running")
|
||||
|
||||
awaitTerminationSignal()
|
||||
}
|
48
cmd/meteod/influxdb.go
Normal file
48
cmd/meteod/influxdb.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
)
|
||||
|
||||
type InfluxDB struct {
|
||||
client influxdb2.Client
|
||||
}
|
||||
|
||||
func ConnectInfluxDB(remote string, token string) (InfluxDB, error) {
|
||||
var influx InfluxDB
|
||||
influx.client = influxdb2.NewClient(remote, token)
|
||||
return influx, nil
|
||||
}
|
||||
|
||||
func (influx *InfluxDB) Ping(timeout time.Duration) (time.Duration, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
now := time.Now()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// timeout
|
||||
return time.Duration(0), fmt.Errorf("timeout")
|
||||
default:
|
||||
succ, err := influx.client.Ping(ctx)
|
||||
delta := time.Now().Sub(now)
|
||||
if err != nil {
|
||||
return delta, err
|
||||
}
|
||||
if !succ {
|
||||
return delta, fmt.Errorf("no reply from server")
|
||||
}
|
||||
return delta, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Write a measurement into the database
|
||||
func (influx *InfluxDB) Write(org, bucket, measurement string, tags map[string]string, fields map[string]interface{}) error {
|
||||
writeAPI := influx.client.WriteAPIBlocking(org, bucket)
|
||||
p := influxdb2.NewPoint(measurement, tags, fields, time.Now())
|
||||
err := writeAPI.WritePoint(context.Background(), p)
|
||||
return err
|
||||
}
|
408
cmd/meteod/meteod.go
Normal file
408
cmd/meteod/meteod.go
Normal file
|
@ -0,0 +1,408 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// Config is the internal singleton configuration for this program
|
||||
type Config struct {
|
||||
MqttHost string
|
||||
MqttClientID string
|
||||
InfluxHost string
|
||||
InfluxBucket string
|
||||
InfluxOrg string
|
||||
InfluxToken string
|
||||
HttpBindAddr string
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
type Measurement struct {
|
||||
Node int `json:"id"` // NodeID
|
||||
NodeName string `json:"name"` // Name of the measurement node
|
||||
Timestamp int64 `json:"tsp"` // Timestamp when the measurement was taken
|
||||
Measurement string `json:"measurement"` // Measurement name
|
||||
Values map[string]float32 `json:"values"` // Measurement values
|
||||
}
|
||||
|
||||
func (mst *Measurement) json() string {
|
||||
ret := fmt.Sprintf("node %d: {", mst.Node)
|
||||
first := true
|
||||
for k, v := range mst.Values {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
ret += ", "
|
||||
}
|
||||
ret += fmt.Sprintf("\"%s\":%f", k, v)
|
||||
}
|
||||
ret += "}"
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Config) loadIni(filename string) error {
|
||||
cfg, err := ini.Load(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mqtt := cfg.Section("mqtt")
|
||||
if mqtthost := mqtt.Key("remote").String(); mqtthost != "" {
|
||||
c.MqttHost = mqtthost
|
||||
}
|
||||
if mqttclientid := mqtt.Key("clientid").String(); mqttclientid != "" {
|
||||
c.MqttClientID = mqttclientid
|
||||
}
|
||||
|
||||
influx := cfg.Section("influxdb")
|
||||
if influxhost := influx.Key("remote").String(); influxhost != "" {
|
||||
c.InfluxHost = influxhost
|
||||
}
|
||||
if influxOrg := influx.Key("organization").String(); influxOrg != "" {
|
||||
c.InfluxOrg = influxOrg
|
||||
}
|
||||
if influxToken := influx.Key("token").String(); influxToken != "" {
|
||||
c.InfluxToken = influxToken
|
||||
}
|
||||
if influxdb := influx.Key("bucket").String(); influxdb != "" {
|
||||
c.InfluxBucket = influxdb
|
||||
}
|
||||
|
||||
http := cfg.Section("http")
|
||||
if bindAddr := http.Key("bind_addr").String(); bindAddr != "" {
|
||||
c.HttpBindAddr = bindAddr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var config Config
|
||||
var influx InfluxDB
|
||||
var nodes map[int]Measurement
|
||||
|
||||
func receiveMeasurement(msg mqtt.Message) (Measurement, error) {
|
||||
var mst Measurement
|
||||
|
||||
payload := msg.Payload()
|
||||
mst.Timestamp = time.Now().Unix()
|
||||
mst.Measurement = "meteo"
|
||||
|
||||
// Parse node ID from topic, if present
|
||||
topic := msg.Topic()
|
||||
if len(topic) > 6 {
|
||||
var err error
|
||||
mst.Node, err = strconv.Atoi(msg.Topic()[6:])
|
||||
if err != nil {
|
||||
return mst, fmt.Errorf("invalid meteo id")
|
||||
}
|
||||
}
|
||||
|
||||
// Try to parse the new data field version
|
||||
// The measurement is considered as valid, if there is values defined
|
||||
// otherwise we continue with the old data parsing
|
||||
if err := json.Unmarshal(payload, &mst); err == nil && mst.Values != nil {
|
||||
return mst, nil
|
||||
}
|
||||
|
||||
// Proceed with the old data parsing
|
||||
data := make(map[string]interface{}, 0)
|
||||
if err := json.Unmarshal(msg.Payload(), &data); err != nil {
|
||||
return mst, err
|
||||
}
|
||||
|
||||
// remove unnecessary fields
|
||||
delete(data, "id")
|
||||
delete(data, "name")
|
||||
delete(data, "measurement")
|
||||
|
||||
// Parse fields
|
||||
mst.Values = make(map[string]float32)
|
||||
for k, v := range data {
|
||||
f, err := strconv.ParseFloat(fmt.Sprintf("%f", v), 32)
|
||||
if err != nil {
|
||||
return mst, fmt.Errorf("invalid measurement value for %s", k)
|
||||
}
|
||||
mst.Values[k] = float32(f)
|
||||
}
|
||||
return mst, nil
|
||||
}
|
||||
|
||||
func received(msg mqtt.Message) {
|
||||
mst, err := receiveMeasurement(msg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error receiving measurement: %s\n", err)
|
||||
return
|
||||
}
|
||||
nodes[mst.Node] = mst
|
||||
|
||||
tags := map[string]string{"node": fmt.Sprintf("%d", mst.Node)}
|
||||
// Write measurement as a whole
|
||||
|
||||
// Write each value as own measurement
|
||||
fields := make(map[string]interface{}, 0)
|
||||
for k, v := range mst.Values {
|
||||
fields[k] = v
|
||||
}
|
||||
if err := influx.Write(config.InfluxOrg, config.InfluxBucket, mst.Measurement, tags, fields); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot write to influxdb: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if config.Verbose {
|
||||
fmt.Println(mst.json())
|
||||
}
|
||||
}
|
||||
|
||||
// awaits SIGINT or SIGTERM
|
||||
func awaitTerminationSignal() {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
done := make(chan bool, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-sigs
|
||||
fmt.Println(sig)
|
||||
done <- true
|
||||
}()
|
||||
<-done
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("meteod")
|
||||
fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0])
|
||||
fmt.Println("OPTIONS")
|
||||
fmt.Println(" -h,--help Display this help message")
|
||||
fmt.Println(" -c,--config FILE Load config file")
|
||||
fmt.Println(" -v, --verbose Verbose mode")
|
||||
fmt.Println(" --mqtt MQTT Set mqtt server")
|
||||
fmt.Println(" --mqtt-client-id ID Set mqtt client id")
|
||||
fmt.Println(" --influx HOST Set influxdb hostname")
|
||||
fmt.Println(" --org ORGANIZATION Set influxdb organization")
|
||||
fmt.Println(" --bucket BUCKET Set influxdb bucket")
|
||||
fmt.Println(" --token TOKEN Set influxdb token")
|
||||
fmt.Println(" --http BINDADDR Enable http server on the given address")
|
||||
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
nodes = make(map[int]Measurement)
|
||||
|
||||
// Default settings
|
||||
config.MqttHost = "127.0.0.1"
|
||||
config.InfluxHost = "http://127.0.0.1:8086"
|
||||
config.InfluxBucket = "meteo"
|
||||
config.InfluxToken = ""
|
||||
config.Verbose = false
|
||||
|
||||
configFile := "/etc/meteo/meteod.conf"
|
||||
if fileExists(configFile) {
|
||||
if err := config.loadIni(configFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error loading config file %s: %s\n", configFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
args := os.Args[1:]
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := strings.TrimSpace(args[i])
|
||||
if arg == "" {
|
||||
continue
|
||||
} else if arg[0] == '-' {
|
||||
last := i >= len(args)-1
|
||||
if arg == "-h" || arg == "--help" {
|
||||
printUsage()
|
||||
return
|
||||
} else if arg == "-c" || arg == "--config" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: config file\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
configFile = args[i]
|
||||
if err := config.loadIni(configFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error loading ini file %s: %s\n", configFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if arg == "-v" || arg == "--verbose" {
|
||||
config.Verbose = true
|
||||
} else if arg == "--mqtt" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: mqtt remote\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.MqttHost = args[i]
|
||||
} else if arg == "--mqtt-client-id" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: mqtt-client-id remote\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.MqttClientID = args[i]
|
||||
} else if arg == "--influx" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: influx remote\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.InfluxHost = args[i]
|
||||
} else if arg == "--token" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: influx token\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.InfluxToken = args[i]
|
||||
} else if arg == "--bucket" || arg == "--database" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: influx bucket\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.InfluxBucket = args[i]
|
||||
} else if arg == "--org" || arg == "--organization" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: influx organization\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.InfluxOrg = args[i]
|
||||
} else if arg == "--http" {
|
||||
if last {
|
||||
fmt.Fprintf(os.Stderr, "Missing argument: http bind address\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
i++
|
||||
config.HttpBindAddr = args[i]
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Invalid argument: %s\n", arg)
|
||||
os.Exit(1)
|
||||
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Invalid argument: %s\n", arg)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect InfluxDB
|
||||
influx, err = ConnectInfluxDB(config.InfluxHost, config.InfluxToken)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot connect to influxdb: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if ping, err := influx.Ping(5 * time.Second); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot ping influxdb: %s\n", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
if config.Verbose {
|
||||
fmt.Printf("influxdb connected (Ping: %d ms)\n", ping.Milliseconds())
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to mqtt server
|
||||
mqtt := MQTT{}
|
||||
mqtt.ClientID = config.MqttClientID
|
||||
mqtt.Verbose = config.Verbose
|
||||
mqtt.Callback = received
|
||||
if err := mqtt.Connect(config.MqttHost, 1883); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "mqtt error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Enable http server
|
||||
if config.HttpBindAddr != "" {
|
||||
http.HandleFunc("/", handlerDefault)
|
||||
http.HandleFunc("/health", handlerHealth)
|
||||
http.HandleFunc("/health.json", handlerHealth)
|
||||
|
||||
/* API Version 1 */
|
||||
http.HandleFunc("/api", handlerAPIDefault)
|
||||
http.HandleFunc("/api/v1/health", handlerHealth)
|
||||
http.HandleFunc("/api/v1/nodes", handlerNodes)
|
||||
http.HandleFunc("/api/v1/measurements", handlerMeasurements)
|
||||
|
||||
// Listen on http server
|
||||
go func() {
|
||||
log.Fatal(http.ListenAndServe(config.HttpBindAddr, nil))
|
||||
}()
|
||||
|
||||
if config.Verbose {
|
||||
fmt.Printf("webserver enabled on http://%s\n", config.HttpBindAddr)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("meteod is running")
|
||||
awaitTerminationSignal()
|
||||
}
|
||||
|
||||
func replyJson(w http.ResponseWriter, obj interface{}) {
|
||||
buf, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "json marshall error: %s\n", err)
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("server error"))
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.Header().Add("Content-Length", fmt.Sprintf("%d", len(buf)))
|
||||
w.WriteHeader(200)
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func handlerDefault(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("meteod"))
|
||||
}
|
||||
|
||||
func handlerAPIDefault(w http.ResponseWriter, r *http.Request) {
|
||||
content := []byte("<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head><title>meteod api</title></head>\n<body>\n<h3>meteod api</h3>\n<p>Available routes</p>\n<ul>\n<li><a href=\"api/v1/health\">/api/v1/health</a></li>\n<li><a href=\"api/v1/nodes\">/api/v1/nodes</a></li>\n<li><a href=\"api/v1/measurements\">/api/v1/measurements</a></li></ul>\n</body>\n</html>")
|
||||
|
||||
w.Header().Add("Content-Type", "text/html")
|
||||
w.Header().Add("Content-Length", fmt.Sprintf("%d", len(content)))
|
||||
w.Write(content)
|
||||
}
|
||||
|
||||
func handlerMeasurements(w http.ResponseWriter, r *http.Request) {
|
||||
// Post array of current measurements
|
||||
mst := make([]Measurement, 0)
|
||||
for _, m := range nodes {
|
||||
mst = append(mst, m)
|
||||
}
|
||||
replyJson(w, mst)
|
||||
}
|
||||
|
||||
func handlerNodes(w http.ResponseWriter, r *http.Request) {
|
||||
// Post array of current nodes
|
||||
type Node struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
ret := make([]Node, 0)
|
||||
for _, m := range nodes {
|
||||
ret = append(ret, Node{ID: m.Node, Name: m.NodeName})
|
||||
}
|
||||
replyJson(w, ret)
|
||||
}
|
||||
|
||||
func handlerHealth(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "{\"status\":\"ok\"}")
|
||||
}
|
2
go.mod
2
go.mod
|
@ -4,7 +4,7 @@ go 1.14
|
|||
|
||||
require (
|
||||
github.com/eclipse/paho.mqtt.golang v1.3.5
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.12.1
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
)
|
||||
|
|
88
go.sum
88
go.sum
|
@ -1,24 +1,106 @@
|
|||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
|
||||
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y=
|
||||
github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
|
||||
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.12.1 h1:RrjoDNyBGFYvjKfjmtIyYAn6GY/SrtocSo4RPlt+Lng=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.12.1/go.mod h1:YteV91FiQxRdccyJ2cHvj2f/5sq4y4Njqu1fQzsQCOU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
[mqtt]
|
||||
remote = "127.0.0.1"
|
||||
clientid = "meteo-influx-gateway"
|
||||
|
||||
[influxdb]
|
||||
remote = "http://127.0.0.1:8086"
|
||||
username = "meteo"
|
||||
password = "meteo"
|
||||
database = "meteo"
|
12
meteod.conf.example
Normal file
12
meteod.conf.example
Normal file
|
@ -0,0 +1,12 @@
|
|||
[mqtt]
|
||||
remote = "127.0.0.1"
|
||||
clientid = "meteod"
|
||||
|
||||
[influxdb]
|
||||
remote = "http://127.0.0.1:8086"
|
||||
organization = "meteo"
|
||||
bucket = "meteo"
|
||||
token = ""
|
||||
|
||||
[http]
|
||||
bind_addr = "127.0.0.1:4042"
|
Loading…
Reference in a new issue