support for multiple weathersensors
This commit is contained in:
parent
df322b5291
commit
6eb9898778
7 changed files with 69 additions and 36 deletions
|
@ -57,7 +57,7 @@ func (api *weatherRestApi) getData(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *weatherRestApi) randomWeatherHandler(w http.ResponseWriter, r *http.Request) {
|
func (api *weatherRestApi) randomWeatherHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
datapoint := storage.NewRandomWeatherData("swablab")
|
datapoint := storage.NewRandomWeatherData(uuid.Nil)
|
||||||
|
|
||||||
w.Header().Add("content-type", "application/json")
|
w.Header().Add("content-type", "application/json")
|
||||||
json.NewEncoder(w).Encode(datapoint)
|
json.NewEncoder(w).Encode(datapoint)
|
||||||
|
@ -66,7 +66,7 @@ func (api *weatherRestApi) randomWeatherHandler(w http.ResponseWriter, r *http.R
|
||||||
func (api *weatherRestApi) randomWeatherListHandler(w http.ResponseWriter, r *http.Request) {
|
func (api *weatherRestApi) randomWeatherListHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var datapoints = make([]storage.WeatherData, 0)
|
var datapoints = make([]storage.WeatherData, 0)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
datapoints = append(datapoints, storage.NewRandomWeatherData("swablab"))
|
datapoints = append(datapoints, storage.NewRandomWeatherData(uuid.Nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("content-type", "application/json")
|
w.Header().Add("content-type", "application/json")
|
||||||
|
|
|
@ -41,10 +41,6 @@ func GetMqttTopic() string {
|
||||||
return getVariableWithDefault("WEATHER-API-MQTT_TOPIC", mqttTopic)
|
return getVariableWithDefault("WEATHER-API-MQTT_TOPIC", mqttTopic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMqttLocation() string {
|
|
||||||
return getVariableWithDefault("WEATHER-API-MQTT_LOCATION", defaultLocation)
|
|
||||||
}
|
|
||||||
|
|
||||||
//helper
|
//helper
|
||||||
func getVariableWithDefault(variableKey, defaultValue string) string {
|
func getVariableWithDefault(variableKey, defaultValue string) string {
|
||||||
variable := os.Getenv(variableKey)
|
variable := os.Getenv(variableKey)
|
||||||
|
|
3
main.go
3
main.go
|
@ -31,8 +31,7 @@ func main() {
|
||||||
var weatherSource weathersource.WeatherSource
|
var weatherSource weathersource.WeatherSource
|
||||||
weatherSource, err = weathersource.NewMqttSource(
|
weatherSource, err = weathersource.NewMqttSource(
|
||||||
config.GetMqttUrl(),
|
config.GetMqttUrl(),
|
||||||
config.GetMqttTopic(),
|
config.GetMqttTopic())
|
||||||
config.GetMqttLocation())
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -9,7 +9,6 @@ Set-Item -Path "Env:WEATHER-API-INFLUX_BUCKET" -Value "default-bucket"
|
||||||
|
|
||||||
Set-Item -Path "Env:WEATHER-API-MQTT_URL" -Value "tcp://default-address.com:1883"
|
Set-Item -Path "Env:WEATHER-API-MQTT_URL" -Value "tcp://default-address.com:1883"
|
||||||
Set-Item -Path "Env:WEATHER-API-MQTT_TOPIC" -Value "sensor/#"
|
Set-Item -Path "Env:WEATHER-API-MQTT_TOPIC" -Value "sensor/#"
|
||||||
Set-Item -Path "Env:WEATHER-API-MQTT_LOCATION" -Value "default-location"
|
|
||||||
|
|
||||||
#start application
|
#start application
|
||||||
Start-Process "main.exe" -Wait -NoNewWindow
|
Start-Process "main.exe" -Wait -NoNewWindow
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ func NewInfluxStorage(token, bucket, organization, url string) (*influxStorage,
|
||||||
//Save WeatherData to InfluxDB
|
//Save WeatherData to InfluxDB
|
||||||
func (storage *influxStorage) Save(data WeatherData) error {
|
func (storage *influxStorage) Save(data WeatherData) error {
|
||||||
tags := map[string]string{
|
tags := map[string]string{
|
||||||
"location": data.Location}
|
"sensorId": data.SensorId.String()}
|
||||||
|
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"temperature": data.Temperature,
|
"temperature": data.Temperature,
|
||||||
|
@ -73,12 +74,17 @@ func (storage *influxStorage) executeFluxQuery(query string) ([]*WeatherData, er
|
||||||
|
|
||||||
for result.Next() {
|
for result.Next() {
|
||||||
if result.Err() != nil {
|
if result.Err() != nil {
|
||||||
|
return nil, result.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := result.Record().Time()
|
||||||
|
sensorId, err := uuid.Parse(result.Record().ValueByKey("sensorId").(string))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
location := result.Record().ValueByKey("location").(string)
|
|
||||||
timestamp := result.Record().Time()
|
|
||||||
|
|
||||||
data, contained := containsWeatherData(queryResults, location, timestamp)
|
data, contained := containsWeatherData(queryResults, sensorId, timestamp)
|
||||||
|
|
||||||
if result.Record().Field() == "temperature" {
|
if result.Record().Field() == "temperature" {
|
||||||
data.Temperature = result.Record().Value().(float64)
|
data.Temperature = result.Record().Value().(float64)
|
||||||
|
@ -94,7 +100,7 @@ func (storage *influxStorage) executeFluxQuery(query string) ([]*WeatherData, er
|
||||||
}
|
}
|
||||||
|
|
||||||
if !contained {
|
if !contained {
|
||||||
data.Location = location
|
data.SensorId = sensorId
|
||||||
data.TimeStamp = timestamp
|
data.TimeStamp = timestamp
|
||||||
queryResults = append(queryResults, data)
|
queryResults = append(queryResults, data)
|
||||||
}
|
}
|
||||||
|
@ -103,9 +109,9 @@ func (storage *influxStorage) executeFluxQuery(query string) ([]*WeatherData, er
|
||||||
return queryResults, nil
|
return queryResults, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsWeatherData(weatherData []*WeatherData, location string, timestamp time.Time) (*WeatherData, bool) {
|
func containsWeatherData(weatherData []*WeatherData, sensorId uuid.UUID, timestamp time.Time) (*WeatherData, bool) {
|
||||||
for _, val := range weatherData {
|
for _, val := range weatherData {
|
||||||
if val.Location == location && val.TimeStamp == timestamp {
|
if val.SensorId == sensorId && val.TimeStamp == timestamp {
|
||||||
return val, true
|
return val, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package storage
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
//WeatherStorage interface for different storage-implementations of weather data
|
//WeatherStorage interface for different storage-implementations of weather data
|
||||||
|
@ -18,19 +20,19 @@ type WeatherData struct {
|
||||||
Pressure float64 `json:"airPressure"`
|
Pressure float64 `json:"airPressure"`
|
||||||
Temperature float64 `json:"temperature"`
|
Temperature float64 `json:"temperature"`
|
||||||
CO2Level float64 `json:"co2level"`
|
CO2Level float64 `json:"co2level"`
|
||||||
Location string `json:"location"`
|
SensorId uuid.UUID `json:"SensorId"`
|
||||||
TimeStamp time.Time `json:"timestamp"`
|
TimeStamp time.Time `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewRandomWeatherData creates random WeatherData with given Location
|
//NewRandomWeatherData creates random WeatherData with given Location
|
||||||
func NewRandomWeatherData(location string) WeatherData {
|
func NewRandomWeatherData(sensorId uuid.UUID) WeatherData {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
var data WeatherData
|
var data WeatherData
|
||||||
data.Humidity = rand.Float64() * 100
|
data.Humidity = rand.Float64() * 100
|
||||||
data.Pressure = rand.Float64()*80 + 960
|
data.Pressure = rand.Float64()*80 + 960
|
||||||
data.Temperature = rand.Float64()*40 - 5
|
data.Temperature = rand.Float64()*40 - 5
|
||||||
data.CO2Level = rand.Float64()*50 + 375
|
data.CO2Level = rand.Float64()*50 + 375
|
||||||
data.Location = location
|
data.SensorId = sensorId
|
||||||
data.TimeStamp = time.Now()
|
data.TimeStamp = time.Now()
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
package weathersource
|
package weathersource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"weather-data/storage"
|
"weather-data/storage"
|
||||||
|
|
||||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var uuidRegexPattern = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
||||||
|
var mqttTopicRegexPattern = "^sensor/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/(temp|pressure|humidity|co2level)$"
|
||||||
|
|
||||||
|
var regexTopic *regexp.Regexp = regexp.MustCompile(mqttTopicRegexPattern)
|
||||||
|
var regexUuid *regexp.Regexp = regexp.MustCompile(uuidRegexPattern)
|
||||||
|
|
||||||
type mqttWeatherSource struct {
|
type mqttWeatherSource struct {
|
||||||
url string
|
url string
|
||||||
topic string
|
topic string
|
||||||
mqttClient mqtt.Client
|
mqttClient mqtt.Client
|
||||||
lastData storage.WeatherData
|
lastWeatherDataPoints []*storage.WeatherData
|
||||||
weatherSource WeatherSourceBase
|
weatherSource WeatherSourceBase
|
||||||
}
|
}
|
||||||
|
|
||||||
//Close mqtt client
|
//Close mqtt client
|
||||||
|
@ -23,7 +31,7 @@ func (source *mqttWeatherSource) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewMqttSource Factory function for mqttWeatherSource
|
//NewMqttSource Factory function for mqttWeatherSource
|
||||||
func NewMqttSource(url, topic, defaultLocation string) (*mqttWeatherSource, error) {
|
func NewMqttSource(url, topic string) (*mqttWeatherSource, error) {
|
||||||
source := new(mqttWeatherSource)
|
source := new(mqttWeatherSource)
|
||||||
source.url = url
|
source.url = url
|
||||||
|
|
||||||
|
@ -35,7 +43,6 @@ func NewMqttSource(url, topic, defaultLocation string) (*mqttWeatherSource, erro
|
||||||
opts.SetPingTimeout(1 * time.Second)
|
opts.SetPingTimeout(1 * time.Second)
|
||||||
|
|
||||||
source.mqttClient = mqtt.NewClient(opts)
|
source.mqttClient = mqtt.NewClient(opts)
|
||||||
source.lastData.Location = defaultLocation
|
|
||||||
|
|
||||||
if token := source.mqttClient.Connect(); token.Wait() && token.Error() != nil {
|
if token := source.mqttClient.Connect(); token.Wait() && token.Error() != nil {
|
||||||
return nil, token.Error()
|
return nil, token.Error()
|
||||||
|
@ -52,31 +59,55 @@ func NewMqttSource(url, topic, defaultLocation string) (*mqttWeatherSource, erro
|
||||||
func (source *mqttWeatherSource) mqttMessageHandler() mqtt.MessageHandler {
|
func (source *mqttWeatherSource) mqttMessageHandler() mqtt.MessageHandler {
|
||||||
|
|
||||||
return func(client mqtt.Client, msg mqtt.Message) {
|
return func(client mqtt.Client, msg mqtt.Message) {
|
||||||
|
if !regexTopic.MatchString(msg.Topic()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
diff := time.Now().Sub(source.lastData.TimeStamp)
|
sensorId, err := uuid.Parse(regexUuid.FindAllString(msg.Topic(), 1)[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastWeatherData, found := source.getLastWeatherData(sensorId)
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
lastWeatherData = new(storage.WeatherData)
|
||||||
|
lastWeatherData.SensorId = sensorId
|
||||||
|
source.lastWeatherDataPoints = append(source.lastWeatherDataPoints, lastWeatherData)
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := time.Now().Sub(lastWeatherData.TimeStamp)
|
||||||
if diff >= time.Second && diff < time.Hour*6 {
|
if diff >= time.Second && diff < time.Hour*6 {
|
||||||
source.newWeatherData(source.lastData)
|
source.newWeatherData(*lastWeatherData)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(msg.Topic(), "pressure") {
|
if strings.HasSuffix(msg.Topic(), "pressure") {
|
||||||
source.lastData.Pressure, _ = strconv.ParseFloat(string(msg.Payload()), 64)
|
lastWeatherData.Pressure, _ = strconv.ParseFloat(string(msg.Payload()), 64)
|
||||||
source.lastData.TimeStamp = time.Now()
|
lastWeatherData.TimeStamp = time.Now()
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(msg.Topic(), "temp") {
|
if strings.HasSuffix(msg.Topic(), "temp") {
|
||||||
source.lastData.Temperature, _ = strconv.ParseFloat(string(msg.Payload()), 64)
|
lastWeatherData.Temperature, _ = strconv.ParseFloat(string(msg.Payload()), 64)
|
||||||
source.lastData.TimeStamp = time.Now()
|
lastWeatherData.TimeStamp = time.Now()
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(msg.Topic(), "humidity") {
|
if strings.HasSuffix(msg.Topic(), "humidity") {
|
||||||
source.lastData.Temperature, _ = strconv.ParseFloat(string(msg.Payload()), 64)
|
lastWeatherData.Temperature, _ = strconv.ParseFloat(string(msg.Payload()), 64)
|
||||||
source.lastData.TimeStamp = time.Now()
|
lastWeatherData.TimeStamp = time.Now()
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(msg.Topic(), "co2level") {
|
if strings.HasSuffix(msg.Topic(), "co2level") {
|
||||||
source.lastData.CO2Level, _ = strconv.ParseFloat(string(msg.Payload()), 64)
|
lastWeatherData.CO2Level, _ = strconv.ParseFloat(string(msg.Payload()), 64)
|
||||||
source.lastData.TimeStamp = time.Now()
|
lastWeatherData.TimeStamp = time.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (source *mqttWeatherSource) getLastWeatherData(sensorId uuid.UUID) (*storage.WeatherData, bool) {
|
||||||
|
for _, data := range source.lastWeatherDataPoints {
|
||||||
|
if data.SensorId == sensorId {
|
||||||
|
return data, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
//AddNewWeatherDataCallback adds a new callbackMethod for incoming weather data
|
//AddNewWeatherDataCallback adds a new callbackMethod for incoming weather data
|
||||||
func (source *mqttWeatherSource) AddNewWeatherDataCallback(callback NewWeatherDataCallbackFunc) {
|
func (source *mqttWeatherSource) AddNewWeatherDataCallback(callback NewWeatherDataCallbackFunc) {
|
||||||
source.weatherSource.AddNewWeatherDataCallback(callback)
|
source.weatherSource.AddNewWeatherDataCallback(callback)
|
||||||
|
|
Loading…
Add table
Reference in a new issue