fix: new implementation
changes: - Remove cli Some cli commands are not complete tested and are deprecated. - Daemon - Old version has a very bad implementation of how to verify, if the device or the sensors are in the database insert. The current implementation can be improved but this one is betten then the old one. - Remove complete the cache store implementation. Use a normal array and query the length and capacity to determine how the array cache must be cleaned. - Type Remove unused types and functions
This commit is contained in:
parent
84d052184e
commit
fb8d4dd5eb
15
.env_dev
15
.env_dev
@ -1,15 +0,0 @@
|
||||
SERVER_PORT=80
|
||||
DATABASE_DRIVER=postgres
|
||||
DATABASE_NAME=postgres
|
||||
DATABASE_SCHEMA=public
|
||||
DATABASE_USER=postgres
|
||||
DATABASE_PASSWORD=postgres
|
||||
|
||||
PG_HOST=flucky_db
|
||||
PG_INTERN_PORT=5432
|
||||
PG_EXTERN_PORT=5433
|
||||
PG_NAME=public
|
||||
PG_USER=postgres
|
||||
PG_PASSWORD=postgres
|
||||
|
||||
TZ=Europe/Berlin
|
18
Makefile
18
Makefile
@ -173,17 +173,13 @@ bin/tmp/${EXECUTABLE}: bindata
|
||||
# GO-BINDATA
|
||||
# ==============================================================================
|
||||
BINDATA_TARGETS := \
|
||||
pkg/storage/db/bindataSQL.go \
|
||||
test/goldenfiles/bindata.go
|
||||
pkg/storage/bindataSQL.go \
|
||||
|
||||
PHONY+=bindata
|
||||
bindata: ${BINDATA_TARGETS}
|
||||
bindata: clean ${BINDATA_TARGETS}
|
||||
|
||||
pkg/storage/db/bindataSQL.go:
|
||||
go-bindata -pkg db -o ./pkg/storage/db/bindataSQL.go ./pkg/storage/db/sql/*** ./pkg/storage/db/sql/psql/schema/***
|
||||
|
||||
test/goldenfiles/bindata.go:
|
||||
go-bindata -pkg goldenfiles -ignore ".*\.go" -o ./test/goldenfiles/bindata.go ./test/goldenfiles/***
|
||||
pkg/storage/bindataSQL.go:
|
||||
go-bindata -pkg storage -o ./pkg/storage/bindataSQL.go pkg/storage/postgres/***
|
||||
|
||||
# TEST
|
||||
# ==============================================================================
|
||||
@ -288,9 +284,9 @@ container-run:
|
||||
PHONY+=${FLUCKY_REMOTE:%=remote/%}
|
||||
remote/${FLUCKY_REMOTE}: bin/linux/arm/7/${EXECUTABLE}
|
||||
scp bin/linux/arm/7/${EXECUTABLE} root@${FLUCKY_REMOTE}:/usr/local/bin/${EXECUTABLE}
|
||||
ssh root@${FLUCKY_REMOTE} 'mkdir --parent /etc/bash_completion.d || true'
|
||||
ssh root@${FLUCKY_REMOTE} 'flucky completion bash > /etc/bash_completion.d/flucky.sh && chmod +x /etc/bash_completion.d/flucky.sh'
|
||||
ssh root@${FLUCKY_REMOTE} 'flucky completion zsh > /etc/bash_completion.d/flucky.zsh && chmod +x /etc/bash_completion.d/flucky.zsh'
|
||||
# ssh root@${FLUCKY_REMOTE} 'mkdir --parent /etc/bash_completion.d || true'
|
||||
# ssh root@${FLUCKY_REMOTE} 'flucky completion bash > /etc/bash_completion.d/flucky.sh && chmod +x /etc/bash_completion.d/flucky.sh'
|
||||
# ssh root@${FLUCKY_REMOTE} 'flucky completion zsh > /etc/bash_completion.d/flucky.zsh && chmod +x /etc/bash_completion.d/flucky.zsh'
|
||||
ssh root@${FLUCKY_REMOTE} 'chmod +x /usr/local/bin/${EXECUTABLE}'
|
||||
|
||||
# PHONY
|
||||
|
@ -1,42 +0,0 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// InitCmd initialize all completion subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
|
||||
completionCmd := &cobra.Command{
|
||||
Use: "completion",
|
||||
Short: "Generates a shell completion file",
|
||||
}
|
||||
|
||||
bashCompletionCmd := &cobra.Command{
|
||||
Use: "bash",
|
||||
Short: "Generates a bash completion file",
|
||||
Args: cobra.NoArgs,
|
||||
Example: "flucky completion bash",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cmd.GenBashCompletion(os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
zshCompletionCmd := &cobra.Command{
|
||||
Use: "zsh",
|
||||
Short: "Generate a zsh completion file",
|
||||
Args: cobra.NoArgs,
|
||||
Example: "flucky completion zsh",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return cmd.GenZshCompletion(os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
completionCmd.AddCommand(bashCompletionCmd)
|
||||
completionCmd.AddCommand(zshCompletionCmd)
|
||||
cmd.AddCommand(completionCmd)
|
||||
|
||||
return nil
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package compression
|
||||
|
||||
import (
|
||||
"github.com/volker-raschek/flucky/pkg/storage"
|
||||
"github.com/volker-raschek/flucky/pkg/storage/logfile"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
round float64
|
||||
)
|
||||
|
||||
// InitCmd initialize all compression subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
|
||||
compressionCmd := &cobra.Command{
|
||||
Use: "compression",
|
||||
Short: "Compress a logfile",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: "flucky compression /var/log/flucky/logfile.csv",
|
||||
RunE: run,
|
||||
}
|
||||
|
||||
compressionCmd.Flags().Float64Var(&round, "round", 0, "Round values. The value 0 deactivates the function")
|
||||
cmd.AddCommand(compressionCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) error {
|
||||
logfileInput := logfile.New(args[0])
|
||||
measuredValues, err := logfileInput.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if round != 0 {
|
||||
storage.Round(measuredValues, round)
|
||||
}
|
||||
|
||||
storage.Compression(measuredValues)
|
||||
|
||||
err = logfileInput.Write(measuredValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"github.com/volker-raschek/flucky/pkg/storage"
|
||||
"github.com/volker-raschek/flucky/pkg/storage/logfile"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
compression bool
|
||||
round float64
|
||||
)
|
||||
|
||||
// InitCmd initialize all convert subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
|
||||
convertCmd := &cobra.Command{
|
||||
Use: "convert",
|
||||
Short: "Convert logfiles into other markup language",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: "flucky convert /var/log/flucky/logfile.json /var/log/flucky/logfile.csv",
|
||||
RunE: run,
|
||||
}
|
||||
|
||||
convertCmd.Flags().BoolVar(&compression, "compression", false, "Compress measured values")
|
||||
convertCmd.Flags().Float64Var(&round, "round", 0, "Round values. The value 0 deactivates the function")
|
||||
cmd.AddCommand(convertCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) error {
|
||||
logfileInput := logfile.New(args[0])
|
||||
measuredValues, err := logfileInput.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if round != 0 {
|
||||
storage.Round(measuredValues, round)
|
||||
}
|
||||
|
||||
if compression {
|
||||
measuredValues = storage.Compression(measuredValues)
|
||||
}
|
||||
|
||||
logfileOutput := logfile.New(args[1])
|
||||
err = logfileOutput.Write(measuredValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/volker-raschek/flucky/cmd/internal"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/daemon"
|
||||
)
|
||||
|
||||
var (
|
||||
cachedMeasuredValues uint
|
||||
compression bool
|
||||
round float64
|
||||
temperatureUnit string
|
||||
)
|
||||
|
||||
// InitCmd initialize all daemon subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
daemonCmd := &cobra.Command{
|
||||
Use: "daemon",
|
||||
Short: "Read continuously data from all enabled sensors",
|
||||
Example: "flucky daemon",
|
||||
RunE: run,
|
||||
}
|
||||
|
||||
daemonCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured values")
|
||||
daemonCmd.Flags().UintVar(&cachedMeasuredValues, "cached-values", 500, "Number of cached values before saveing into the storage endpoint")
|
||||
daemonCmd.Flags().Float64Var(&round, "round", 0.5, "Round values. The value 0 deactivates the function")
|
||||
cmd.AddCommand(daemonCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) error {
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined: %v", err)
|
||||
}
|
||||
|
||||
logLevel, err := cmd.Flags().GetString("loglevel")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No loglevel defined: %v", err)
|
||||
}
|
||||
|
||||
flogger := internal.InitializeLogger(logLevel)
|
||||
daemon.SetLogger(flogger)
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return daemon.Start(cnf, cachedMeasuredValues, compression, round)
|
||||
}
|
106
cmd/db/db.go
106
cmd/db/db.go
@ -1,106 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/volker-raschek/flucky/cmd/internal"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/storage/db"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
semanticVersion *semver.Version
|
||||
)
|
||||
|
||||
// InitCmd initialize all convert subcommands
|
||||
func InitCmd(cmd *cobra.Command, semver *semver.Version) error {
|
||||
|
||||
semanticVersion = semver
|
||||
|
||||
databaseCmd := &cobra.Command{
|
||||
Use: "db",
|
||||
Short: "Interagte with the database configured as storage endpoint",
|
||||
}
|
||||
|
||||
updateDatabaseCmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize the database scheme for the version of the flucky binary",
|
||||
Args: cobra.NoArgs,
|
||||
Example: "flucky db update",
|
||||
RunE: initializeDatabase,
|
||||
}
|
||||
|
||||
databaseCmd.AddCommand(updateDatabaseCmd)
|
||||
cmd.AddCommand(databaseCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeDatabase(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storageEndpointURL, err := url.Parse(cnf.StorageEndpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse storage endpoint: %v", err)
|
||||
}
|
||||
|
||||
logLevel, err := cmd.Flags().GetString("loglevel")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No loglevel defined: %v", err)
|
||||
}
|
||||
|
||||
flogger := internal.InitializeLogger(logLevel)
|
||||
|
||||
database, err := db.New(storageEndpointURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
flogger.Debug("Initialize database scheme")
|
||||
err = database.Schema(ctx, semanticVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = database.SelectDeviceByID(ctx, cnf.Device.ID)
|
||||
if err != nil {
|
||||
flogger.Debug("Add device into database")
|
||||
devices := []*types.Device{cnf.Device}
|
||||
if err := database.InsertDevices(ctx, devices); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
flogger.Debug("Device already availabe in the database")
|
||||
}
|
||||
|
||||
for _, sensor := range cnf.Sensors {
|
||||
_, err = database.SelectSensorByID(ctx, sensor.ID)
|
||||
if err != nil {
|
||||
flogger.Debug("Add sensor %v into the database", sensor.FullName())
|
||||
sensors := []*types.Sensor{sensor}
|
||||
if err := database.InsertSensors(ctx, sensors); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
flogger.Debug("Sensor %v already available in the database", sensor.FullName())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
package humidity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/cli"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/sensor"
|
||||
"github.com/volker-raschek/flucky/pkg/storage"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
compression bool
|
||||
configFile string
|
||||
save bool
|
||||
round float64
|
||||
)
|
||||
|
||||
// InitCmd initialize all humidity subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
humidityCmd := &cobra.Command{
|
||||
Use: "humidity",
|
||||
Short: "Operates with humidity values",
|
||||
}
|
||||
|
||||
listHumiditiesCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List humidity values from specified or all sensors",
|
||||
Example: fmt.Sprintf("flucky humidity list"),
|
||||
RunE: listHumidities,
|
||||
}
|
||||
|
||||
readHumiditiesCmd := &cobra.Command{
|
||||
Use: "read",
|
||||
Short: "Read humidity values from specified or all sensors",
|
||||
Example: fmt.Sprintf("flucky humidity read"),
|
||||
RunE: readHumidities,
|
||||
}
|
||||
readHumiditiesCmd.Flags().BoolVar(&save, "save", true, "Save humidities")
|
||||
readHumiditiesCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured with logged temperatures")
|
||||
readHumiditiesCmd.Flags().Float64VarP(&round, "round", "r", 0.25, "Round values. The value 0 deactivates the function")
|
||||
|
||||
humidityCmd.AddCommand(listHumiditiesCmd)
|
||||
humidityCmd.AddCommand(readHumiditiesCmd)
|
||||
cmd.AddCommand(humidityCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listHumidities(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
// read configuration
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
storageEndpoint, err := cnf.GetStorageEndpointURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues, err := storage.Read(ctx, storageEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypeHumidity, measuredValues)
|
||||
|
||||
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readHumidities(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch all temperature sensors or sensors by args
|
||||
sensors := make([]sensor.Sensor, 0)
|
||||
if len(args) == 0 {
|
||||
sensors = cnf.GetHumiditySensors(config.ENABLED)
|
||||
} else {
|
||||
sensors = cnf.GetHumiditySensorsByName(args)
|
||||
}
|
||||
|
||||
if len(sensors) == 0 {
|
||||
return fmt.Errorf("No sensors matched")
|
||||
}
|
||||
|
||||
measuredValues, err := sensor.Read(sensors, types.MeasuredValueTypeHumidity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypeHumidity, measuredValues)
|
||||
|
||||
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
|
||||
|
||||
if save {
|
||||
storageEndpoint, err := cnf.GetStorageEndpointURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err = storage.Write(ctx, measuredValues, storageEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package internal
|
||||
|
||||
import "github.com/volker-raschek/go-logger/pkg/logger"
|
||||
|
||||
func InitializeLogger(logLevel string) logger.Logger {
|
||||
switch logLevel {
|
||||
case "debug", "DEBUG":
|
||||
return logger.NewDefaultLogger(logger.LogLevelDebug)
|
||||
case "info", "INFO":
|
||||
return logger.NewDefaultLogger(logger.LogLevelInfo)
|
||||
case "warn", "WARN":
|
||||
return logger.NewDefaultLogger(logger.LogLevelWarn)
|
||||
case "error", "ERROR":
|
||||
return logger.NewDefaultLogger(logger.LogLevelError)
|
||||
case "fatal", "FATAL":
|
||||
return logger.NewDefaultLogger(logger.LogLevelFatal)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
package pressure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/cli"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/sensor"
|
||||
"github.com/volker-raschek/flucky/pkg/storage"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
compression bool
|
||||
configFile string
|
||||
save bool
|
||||
round float64
|
||||
)
|
||||
|
||||
// InitCmd initialize all pressure subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
|
||||
pressureCmd := &cobra.Command{
|
||||
Use: "pressure",
|
||||
Short: "Operates with pressure values",
|
||||
}
|
||||
|
||||
listPressuresCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List pressure values from specified or all sensors",
|
||||
Example: fmt.Sprintf("flucky pressure list"),
|
||||
RunE: listHumidities,
|
||||
}
|
||||
|
||||
readPressuresCmd := &cobra.Command{
|
||||
Use: "read",
|
||||
Short: "Read pressure values from specified or all sensors",
|
||||
Example: fmt.Sprintf("flucky pressure read"),
|
||||
RunE: readPressure,
|
||||
}
|
||||
readPressuresCmd.Flags().BoolVar(&save, "save", true, "Save humidities")
|
||||
readPressuresCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured with logged temperatures")
|
||||
readPressuresCmd.Flags().Float64VarP(&round, "round", "r", 0.25, "Round values. The value 0 deactivates the function")
|
||||
|
||||
pressureCmd.AddCommand(listPressuresCmd)
|
||||
pressureCmd.AddCommand(readPressuresCmd)
|
||||
cmd.AddCommand(pressureCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listHumidities(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
storageEndpoint, err := cnf.GetStorageEndpointURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues, err := storage.Read(ctx, storageEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypePressure, measuredValues)
|
||||
|
||||
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readPressure(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch all temperature sensors or sensors by args
|
||||
sensors := make([]sensor.Sensor, 0)
|
||||
if len(args) == 0 {
|
||||
sensors = cnf.GetPressureSensors(config.ENABLED)
|
||||
} else {
|
||||
sensors = cnf.GetPressureSensorsByName(args)
|
||||
}
|
||||
|
||||
if len(sensors) == 0 {
|
||||
return fmt.Errorf("No sensors matched")
|
||||
}
|
||||
|
||||
measuredValues, err := sensor.Read(sensors, types.MeasuredValueTypePressure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypePressure, measuredValues)
|
||||
|
||||
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
|
||||
|
||||
if save {
|
||||
storageEndpoint, err := cnf.GetStorageEndpointURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err = storage.Write(ctx, measuredValues, storageEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
@ -1,347 +0,0 @@
|
||||
package rgbled
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/volker-raschek/flucky/pkg/cli"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/rgbled"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
enabled bool
|
||||
location string
|
||||
version *semver.Version
|
||||
)
|
||||
|
||||
// InitCmd initialize all rgb-led subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
|
||||
rgbLedCmd := &cobra.Command{
|
||||
Use: "rgb-led",
|
||||
Short: "Manage RGB-LEDs",
|
||||
}
|
||||
|
||||
addRGBLEDCmd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add a RGB-LED",
|
||||
Aliases: []string{"append"},
|
||||
Args: cobra.ExactArgs(4),
|
||||
Example: fmt.Sprintf(`flucky rgb-led add <name> <gpio-for-blue> <gpio-for-green> <gpio-for-red>
|
||||
flucky rgb-led add my-led GPIO13 GPIO17 GPIO26`),
|
||||
RunE: addRGBLED,
|
||||
}
|
||||
addRGBLEDCmd.Flags().BoolVarP(&enabled, "enabled", "e", true, "Enable Sensor")
|
||||
addRGBLEDCmd.Flags().StringVarP(&location, "location", "l", "", "Sensor location")
|
||||
|
||||
disableRGBLEDCmd := &cobra.Command{
|
||||
Use: "disable",
|
||||
Short: "Disable a RGB-LED",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: `flucky rgb-led disable <name/uuid>
|
||||
flucky rgb-led disable my-led
|
||||
flucky rgb-led disable 9f8abfc5-91f3-480c-a42d-b990b6f89e5d`,
|
||||
RunE: disableRGBLED,
|
||||
}
|
||||
|
||||
enableRGBLEDCmd := &cobra.Command{
|
||||
Use: "enable",
|
||||
Short: "Enable a RGB-LED",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: `flucky rgb-led enable <name/uuid>
|
||||
flucky rgb-led enable my-led
|
||||
flucky rgb-led enable 9f8abfc5-91f3-480c-a42d-b990b6f89e5d`,
|
||||
RunE: enableRGBLED,
|
||||
}
|
||||
|
||||
listRGBLEDCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List RGB-LEDs",
|
||||
Aliases: []string{"ls"},
|
||||
RunE: listRGBLED,
|
||||
}
|
||||
|
||||
turnOffRGBLEDCmd := &cobra.Command{
|
||||
Use: "off",
|
||||
Short: "Turn a RGB-LED color off",
|
||||
Example: `flucky rgb-led off <name/uuid> <blue>
|
||||
flucky rgb-led off my-led`,
|
||||
RunE: turnOffRGBLED,
|
||||
}
|
||||
|
||||
turnOnRGBLEDCmd := &cobra.Command{
|
||||
Use: "on",
|
||||
Short: "Turn a RGB-LED color on",
|
||||
Example: `flucky rgb-led on <names/uuids> <blue/green/purple/red/turquoise/white/yellow>
|
||||
flucky rgb-led on my-led blue
|
||||
flucky rgb-led on my-led my-sweet-led white
|
||||
flucky rgb-led on 1c5b9424-f6e9-4a37-be5c-77e531e94aab red`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: turnOnRGBLED,
|
||||
}
|
||||
|
||||
removeRGBLEDCmd := &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Remove a RGB-LED",
|
||||
Example: `flucky rgb-led remove <name/uuid>
|
||||
flucky rgb-led remove my-led
|
||||
flucky rgb-led remove 9f8abfc5-91f3-480c-a42d-b990b6f89e5d`,
|
||||
Aliases: []string{"rm"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: removeRGBLED,
|
||||
}
|
||||
|
||||
renameRGBLEDCmd := &cobra.Command{
|
||||
Use: "rename",
|
||||
Short: "Rename a RGB-LED",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: `flucky rgb-led disable <name/uuid> <new-name>
|
||||
flucky rgb-led disable my-led my-sweet-led
|
||||
flucky rgb-led disable 9f8abfc5-91f3-480c-a42d-b990b6f89e5d my-sweet-led`,
|
||||
RunE: renameRGBLED,
|
||||
}
|
||||
|
||||
rgbLedCmd.AddCommand(addRGBLEDCmd)
|
||||
rgbLedCmd.AddCommand(disableRGBLEDCmd)
|
||||
rgbLedCmd.AddCommand(enableRGBLEDCmd)
|
||||
rgbLedCmd.AddCommand(listRGBLEDCmd)
|
||||
rgbLedCmd.AddCommand(turnOffRGBLEDCmd)
|
||||
rgbLedCmd.AddCommand(turnOnRGBLEDCmd)
|
||||
rgbLedCmd.AddCommand(removeRGBLEDCmd)
|
||||
rgbLedCmd.AddCommand(renameRGBLEDCmd)
|
||||
cmd.AddCommand(rgbLedCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addRGBLED(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gpioBlue, err := types.StringToGPIO(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gpioGreen, err := types.StringToGPIO(args[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gpioRed, err := types.StringToGPIO(args[3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rgbLED := &types.RGBLED{
|
||||
RGBLEDName: args[0],
|
||||
RGBLEDLocation: location,
|
||||
RGBLEDEnabled: enabled,
|
||||
ActionMapping: types.DefaultActionMapping,
|
||||
BaseColorsToGPIO: map[types.BaseColor]*types.GPIO{
|
||||
types.BaseColorBlue: &gpioBlue,
|
||||
types.BaseColorGreen: &gpioGreen,
|
||||
types.BaseColorRed: &gpioRed,
|
||||
},
|
||||
}
|
||||
|
||||
err = cnf.AddRGBLED(rgbLED)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func disableRGBLED(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cnf.DisableRGBLED(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableRGBLED(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cnf.EnableRGBLED(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listRGBLED(cmd *cobra.Command, args []string) error {
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// print sensors on stdout
|
||||
cli.PrintRGBLEDs(cnf, os.Stdout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func turnOffRGBLED(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rgbLEDs := make([]rgbled.RGBLED, 0)
|
||||
if len(args) != 0 {
|
||||
rgbLEDs = cnf.GetRGBLEDsByName(args)
|
||||
} else {
|
||||
rgbLEDs = cnf.GetRGBLEDs(config.ENABLED)
|
||||
}
|
||||
|
||||
err = rgbled.Off(rgbLEDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func turnOnRGBLED(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rgbLEDs := make([]rgbled.RGBLED, 0)
|
||||
if len(args) > 1 {
|
||||
rgbLEDs = cnf.GetRGBLEDsByName(args[0 : len(args)-1])
|
||||
} else {
|
||||
rgbLEDs = cnf.GetRGBLEDs(config.ENABLED)
|
||||
}
|
||||
|
||||
color, err := types.StringToLEDColor(args[len(args)-1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rgbled.CustomColor(rgbLEDs, color)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeRGBLED(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cnf.RemoveRGBLED(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameRGBLED(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cnf.RenameRGBLED(args[0], args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
130
cmd/root.go
130
cmd/root.go
@ -1,130 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/volker-raschek/flucky/cmd/completion"
|
||||
"github.com/volker-raschek/flucky/cmd/compression"
|
||||
"github.com/volker-raschek/flucky/cmd/convert"
|
||||
"github.com/volker-raschek/flucky/cmd/daemon"
|
||||
"github.com/volker-raschek/flucky/cmd/db"
|
||||
"github.com/volker-raschek/flucky/cmd/humidity"
|
||||
"github.com/volker-raschek/flucky/cmd/pressure"
|
||||
"github.com/volker-raschek/flucky/cmd/rgbled"
|
||||
"github.com/volker-raschek/flucky/cmd/sensor"
|
||||
"github.com/volker-raschek/flucky/cmd/temperature"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/volker-raschek/go-logger/pkg/logger"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
configFile string
|
||||
)
|
||||
|
||||
// Execute a
|
||||
func Execute(version *semver.Version) error {
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "flucky",
|
||||
Short: "flucky - operate with differen sensors, his values and remote servers to synchronize measured values",
|
||||
PersistentPreRunE: preRunError,
|
||||
Version: version.String(),
|
||||
}
|
||||
|
||||
logLevel := ""
|
||||
rootCmd.PersistentFlags().StringVar(&configFile, "config", "/etc/flucky/config.json", "Config file")
|
||||
rootCmd.PersistentFlags().StringVar(&logLevel, "loglevel", "info", "Set the Loglevel. Possible values: debug, info, warn, error, fatal")
|
||||
|
||||
subCommands := []func(cmd *cobra.Command) error{
|
||||
completion.InitCmd,
|
||||
compression.InitCmd,
|
||||
convert.InitCmd,
|
||||
daemon.InitCmd,
|
||||
humidity.InitCmd,
|
||||
pressure.InitCmd,
|
||||
rgbled.InitCmd,
|
||||
sensor.InitCmd,
|
||||
temperature.InitCmd,
|
||||
}
|
||||
|
||||
for _, subCommand := range subCommands {
|
||||
if err := subCommand(rootCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := db.InitCmd(rootCmd, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rootCmd.Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseLogger(logLevel string) logger.Logger {
|
||||
log.Println(logLevel)
|
||||
switch logLevel {
|
||||
case "debug", "DEBUG":
|
||||
return logger.NewDefaultLogger(logger.LogLevelDebug)
|
||||
case "info", "INFO":
|
||||
return logger.NewDefaultLogger(logger.LogLevelInfo)
|
||||
case "warn", "WARN":
|
||||
return logger.NewDefaultLogger(logger.LogLevelWarn)
|
||||
case "error", "ERROR":
|
||||
return logger.NewDefaultLogger(logger.LogLevelError)
|
||||
case "fatal", "FATAL":
|
||||
return logger.NewDefaultLogger(logger.LogLevelFatal)
|
||||
default:
|
||||
return logger.NewDefaultLogger(logger.LogLevelInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func preRunError(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// check if config file exists
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not locate the hostname: %v", err)
|
||||
}
|
||||
|
||||
// Time must be truncted for postgres. Postgres currently does not support
|
||||
// nanoseconds which is automatically include into the go time object
|
||||
postgresTimeStamp := time.Now()
|
||||
location, err := time.LoadLocation("Europe/Berlin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
postgresTimeStamp = time.Date(postgresTimeStamp.Year(), postgresTimeStamp.Month(), postgresTimeStamp.Day(), postgresTimeStamp.Hour(), postgresTimeStamp.Minute(), postgresTimeStamp.Second(), int(math.Round(float64(postgresTimeStamp.Nanosecond())/1000000)*1000000), location)
|
||||
|
||||
// Default configuration
|
||||
cnf := config.Configuration{
|
||||
Device: &types.Device{
|
||||
ID: uuid.NewV4().String(),
|
||||
Name: hostname,
|
||||
CreationDate: postgresTimeStamp,
|
||||
},
|
||||
StorageEndpoint: "file:///var/log/flucky/logfile.csv",
|
||||
}
|
||||
|
||||
err = config.Write(&cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,291 +0,0 @@
|
||||
package sensor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/cli"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
enabled bool
|
||||
gpioIn string
|
||||
i2cAddress uint8
|
||||
i2cBus int
|
||||
location string
|
||||
tickDuration string
|
||||
wireID string
|
||||
)
|
||||
|
||||
// InitCmd initialize all sensor subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
sensorCmd := &cobra.Command{
|
||||
Use: "sensor",
|
||||
Short: "Manage Sensors",
|
||||
}
|
||||
|
||||
addSensorCmd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add Sensor",
|
||||
Aliases: []string{"append"},
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: `flucky sensor add --gpio GPIO14 indoor DHT11
|
||||
flucky sensor add --wire-id 28-011432f0bb3d outdoor DS18B20
|
||||
flucky sensor add --i2c-bus 1 --i2c-address 0x76 wetter-station BME280`,
|
||||
RunE: addSensor,
|
||||
}
|
||||
addSensorCmd.Flags().BoolVar(&enabled, "enabled", true, "Enable new sensor")
|
||||
addSensorCmd.Flags().StringVar(&gpioIn, "gpio", "", "Defines the GPIO port")
|
||||
addSensorCmd.Flags().Uint8Var(&i2cAddress, "i2c-address", 0, "Defines the I2C address on the I2C bus")
|
||||
addSensorCmd.Flags().IntVar(&i2cBus, "i2c-bus", 0, "Defines the I2C bus")
|
||||
addSensorCmd.Flags().StringVar(&location, "location", "", "Location of the sensor")
|
||||
addSensorCmd.Flags().StringVar(&tickDuration, "tick-duration", "1m", "Controls how often values should be read from the sensor when running flucky in daemon mode")
|
||||
addSensorCmd.Flags().StringVar(&wireID, "wire-id", "", "Defines the Wire-ID")
|
||||
|
||||
disableSensorCmd := &cobra.Command{
|
||||
Use: "disable",
|
||||
Short: "Disable Sensor",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: "flucky sensor disable outdoor",
|
||||
RunE: deleteSensor,
|
||||
}
|
||||
|
||||
enableSensorCmd := &cobra.Command{
|
||||
Use: "enable",
|
||||
Short: "Enable Sensor",
|
||||
Example: "flucky sensor enable outdoor",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: enableSensor,
|
||||
}
|
||||
|
||||
listSensorCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List Sensors",
|
||||
Aliases: []string{"ls"},
|
||||
RunE: listSensors,
|
||||
}
|
||||
|
||||
removeSensorCmd := &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Remove Sensor",
|
||||
Aliases: []string{"rm"},
|
||||
Example: "flucky sensor remove outdoor",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: removeSensor,
|
||||
}
|
||||
|
||||
renameSensorCmd := &cobra.Command{
|
||||
Use: "rename",
|
||||
Short: "Rename Sensor",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: `flucky sensor rename indoor outdoor
|
||||
flucky sensor rename f98b00ea-a9b2-4e00-924f-113859d0af2d outdoor`,
|
||||
RunE: renameSensor,
|
||||
}
|
||||
|
||||
sensorCmd.AddCommand(addSensorCmd)
|
||||
sensorCmd.AddCommand(disableSensorCmd)
|
||||
sensorCmd.AddCommand(enableSensorCmd)
|
||||
sensorCmd.AddCommand(listSensorCmd)
|
||||
sensorCmd.AddCommand(removeSensorCmd)
|
||||
sensorCmd.AddCommand(renameSensorCmd)
|
||||
cmd.AddCommand(sensorCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addSensor(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sensorModel, err := types.SelectSensorModel(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sensor := &types.Sensor{
|
||||
Name: args[0],
|
||||
Model: sensorModel,
|
||||
Location: location,
|
||||
Enabled: enabled,
|
||||
TickDuration: tickDuration,
|
||||
}
|
||||
|
||||
// determine gpio port if set
|
||||
if gpioIn != "" &&
|
||||
i2cAddress == 0 &&
|
||||
i2cBus == 0 &&
|
||||
wireID == "" {
|
||||
gpio, err := types.StringToGPIO(gpioIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sensor.GPIONumber = &gpio
|
||||
}
|
||||
|
||||
// set i2c connection settings
|
||||
if gpioIn == "" &&
|
||||
i2cAddress != 0 &&
|
||||
i2cBus != 0 &&
|
||||
wireID == "" {
|
||||
sensor.I2CAddress = &i2cAddress
|
||||
sensor.I2CBus = &i2cBus
|
||||
}
|
||||
|
||||
// set wire connection settings
|
||||
if gpioIn == "" &&
|
||||
i2cAddress == 0 &&
|
||||
i2cBus == 0 &&
|
||||
wireID != "" {
|
||||
sensor.WireID = &wireID
|
||||
}
|
||||
|
||||
// add sensor entry to list
|
||||
err = cnf.AddSensor(sensor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save new configuration
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteSensor(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cnf.DisableSensor(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableSensor(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cnf.EnableSensor(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func listSensors(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cli.PrintSensors(cnf, os.Stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeSensor(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cnf.RemoveSensor(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameSensor(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cnf.RenameSensor(args[0], args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Write(cnf, configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package temperature
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/cli"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/sensor"
|
||||
"github.com/volker-raschek/flucky/pkg/storage"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
compression bool
|
||||
save bool
|
||||
round float64
|
||||
)
|
||||
|
||||
// InitCmd initialize all temperature subcommands
|
||||
func InitCmd(cmd *cobra.Command) error {
|
||||
|
||||
temperatureCmd := &cobra.Command{
|
||||
Use: "temperature",
|
||||
Short: "Operates with temperature values",
|
||||
}
|
||||
|
||||
listTemperaturesCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List temperature values from specified or all sensors",
|
||||
Example: fmt.Sprintf("flucky temperature list"),
|
||||
RunE: listTemperatures,
|
||||
}
|
||||
|
||||
readTemperatures := &cobra.Command{
|
||||
Use: "read",
|
||||
Short: "Read temperature values from specified or all sensors",
|
||||
Example: fmt.Sprintf("flucky temperature read"),
|
||||
RunE: readTemperature,
|
||||
}
|
||||
readTemperatures.Flags().BoolVar(&save, "save", true, "Save humidities")
|
||||
readTemperatures.Flags().BoolVar(&compression, "compression", true, "Compress measured with logged temperatures")
|
||||
readTemperatures.Flags().Float64VarP(&round, "round", "r", 0.25, "Round values. The value 0 deactivates the function")
|
||||
|
||||
temperatureCmd.AddCommand(listTemperaturesCmd)
|
||||
temperatureCmd.AddCommand(readTemperatures)
|
||||
cmd.AddCommand(temperatureCmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listTemperatures(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
storageEndpoint, err := cnf.GetStorageEndpointURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues, err := storage.Read(ctx, storageEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypeTemperature, measuredValues)
|
||||
|
||||
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readTemperature(cmd *cobra.Command, args []string) error {
|
||||
|
||||
configFile, err := cmd.Flags().GetString("config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("No config file defined")
|
||||
}
|
||||
|
||||
cnf, err := config.Read(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sensors := make([]sensor.Sensor, 0)
|
||||
if len(args) == 0 {
|
||||
sensors = cnf.GetTemperatureSensors(config.ENABLED)
|
||||
} else {
|
||||
sensors = cnf.GetTemperatureSensorsByName(args)
|
||||
}
|
||||
|
||||
if len(sensors) == 0 {
|
||||
return fmt.Errorf("No sensors matched")
|
||||
}
|
||||
|
||||
measuredValues, err := sensor.Read(sensors, types.MeasuredValueTypeTemperature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
measuredValues = types.SelectMeasuredValues(types.MeasuredValueTypeTemperature, measuredValues)
|
||||
|
||||
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
|
||||
|
||||
if save {
|
||||
storageEndpoint, err := cnf.GetStorageEndpointURL()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err = storage.Write(ctx, measuredValues, storageEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -3,11 +3,9 @@ services:
|
||||
flucky-db:
|
||||
container_name: postgres
|
||||
environment:
|
||||
- PGTZ=${TZ}
|
||||
- POSTGRES_DB=${POSTGRES_DB_NAME}
|
||||
- POSTGRES_USER=${POSTGRES_DB_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_DB_PASS}
|
||||
- TZ=${TZ}
|
||||
- PGTZ=Europe/Berlin
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- TZ=Europe/Berlin
|
||||
image: postgres:11.5-alpine
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
5
go.mod
5
go.mod
@ -3,16 +3,13 @@ module github.com/volker-raschek/flucky
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.4.2
|
||||
github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375
|
||||
github.com/d2r2/go-i2c v0.0.0-20181113114621-14f8dd4e89ce
|
||||
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e
|
||||
github.com/go-flucky/flucky v0.0.0-20190714170626-0dd156f480be
|
||||
github.com/go-flucky/go-dht v0.1.1
|
||||
github.com/lib/pq v1.2.0
|
||||
github.com/lib/pq v1.4.0
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/stianeikeland/go-rpio v4.2.0+incompatible
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/volker-raschek/go-logger v0.0.0-20190924132822-0c50a64d7f26
|
||||
)
|
||||
|
6
go.sum
6
go.sum
@ -1,5 +1,3 @@
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375 h1:vdUOwcZdV+bBfGUUh5oPPWSzw9p+lBnNSuGgQwGpCH4=
|
||||
github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375/go.mod h1:3iz1WHlYJU9b4NJei+Q8G7DN3K05arcCMlOQ+qNCDjo=
|
||||
github.com/d2r2/go-i2c v0.0.0-20181113114621-14f8dd4e89ce h1:Dog7PLNz1fPaXqHPOHonpERqsF57Oh4X76pM80T1GDY=
|
||||
@ -19,8 +17,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
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/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.4.0 h1:TmtCFbH+Aw0AixwyttznSMQDgbR5Yed/Gg6S8Funrhc=
|
||||
github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
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/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
|
80
main.go
80
main.go
@ -2,10 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/volker-raschek/flucky/cmd"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/daemon"
|
||||
"github.com/volker-raschek/go-logger/pkg/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -13,13 +14,78 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
sversion, err := semver.NewVersion(version)
|
||||
// sversion, err := semver.NewVersion(version)
|
||||
// if err != nil {
|
||||
// log.Printf("The sematic versioning is invalid: %v", version)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
cnfString := `{
|
||||
"device": {
|
||||
"id": "d0c600bc-6eab-4b9b-b93f-dc0477b4658f",
|
||||
"name": "poseidon",
|
||||
"location": null,
|
||||
"last_contact": null,
|
||||
"creation_date": "2020-04-23T22:09:20.932+02:00"
|
||||
},
|
||||
"storage_endpoint": "postgres://postgres:postgres@markus-pc.trier.cryptic.systems:5432/postgres?sslmode=disable",
|
||||
"sensors": [
|
||||
{
|
||||
"id": "1c180121-9e98-4053-a6f6-4ed7fad3935b",
|
||||
"name": "bme280",
|
||||
"location": "",
|
||||
"wire_id": null,
|
||||
"i2c_bus": 1,
|
||||
"i2c_address": 118,
|
||||
"gpio_number": null,
|
||||
"model": "BME280",
|
||||
"enabled": true,
|
||||
"last_contact": null,
|
||||
"tick_duration": "5s",
|
||||
"device_id": "d0c600bc-6eab-4b9b-b93f-dc0477b4658f",
|
||||
"creation_date": "2020-04-23T22:09:20.934+02:00"
|
||||
},
|
||||
{
|
||||
"id": "5cebd127-8976-4b66-b1bb-e551f26c4af4",
|
||||
"name": "ds18b20_2",
|
||||
"location": "",
|
||||
"wire_id": "28-02131dbffeaa",
|
||||
"i2c_bus": null,
|
||||
"i2c_address": null,
|
||||
"gpio_number": null,
|
||||
"model": "DS18B20",
|
||||
"enabled": true,
|
||||
"last_contact": null,
|
||||
"tick_duration": "1m",
|
||||
"device_id": "d0c600bc-6eab-4b9b-b93f-dc0477b4658f",
|
||||
"creation_date": "2020-04-23T22:09:27.901+02:00"
|
||||
},
|
||||
{
|
||||
"id": "579628cc-b667-4b56-b305-73256c0c9dce",
|
||||
"name": "ds18b20",
|
||||
"location": "",
|
||||
"wire_id": "28-01131646f11d",
|
||||
"i2c_bus": null,
|
||||
"i2c_address": null,
|
||||
"gpio_number": null,
|
||||
"model": "DS18B20",
|
||||
"enabled": true,
|
||||
"last_contact": null,
|
||||
"tick_duration": "1m",
|
||||
"device_id": "d0c600bc-6eab-4b9b-b93f-dc0477b4658f",
|
||||
"creation_date": "2020-04-23T22:09:56.468+02:00"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
cnf, err := config.Decode(strings.NewReader(cnfString))
|
||||
if err != nil {
|
||||
log.Printf("The sematic versioning is invalid: %v", version)
|
||||
os.Exit(1)
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = cmd.Execute(sversion)
|
||||
flogger := logger.NewDefaultLogger(logger.LogLevelDebug)
|
||||
|
||||
err = daemon.Start(cnf, flogger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
158
pkg/cli/cli.go
158
pkg/cli/cli.go
@ -1,158 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
errorMeasuredValueIDNotValid = errors.New("Measured value id is not a valid uuid")
|
||||
errorSensorIDNotValid = errors.New("Sensor id is not a valid uuid")
|
||||
|
||||
validUUID = regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
|
||||
)
|
||||
|
||||
// GetSensorIDsByMeasuredValues returns commulated list of sensors by measured values
|
||||
func GetSensorIDsByMeasuredValues(measuredValues []*types.MeasuredValue, cnf *config.Configuration) (map[string]*types.Sensor, error) {
|
||||
sensors := make(map[string]*types.Sensor)
|
||||
for _, measuredValue := range measuredValues {
|
||||
|
||||
// check if sensor id is valid
|
||||
if !validUUID.MatchString(measuredValue.ID) {
|
||||
return nil, errorMeasuredValueIDNotValid
|
||||
}
|
||||
|
||||
// check if sensor id is valid
|
||||
if !validUUID.MatchString(measuredValue.SensorID) {
|
||||
return nil, errorSensorIDNotValid
|
||||
}
|
||||
|
||||
if _, ok := sensors[measuredValue.SensorID]; !ok {
|
||||
sensors[measuredValue.SensorID] = cnf.GetSensorByID(measuredValue.SensorID)
|
||||
}
|
||||
}
|
||||
return sensors, nil
|
||||
}
|
||||
|
||||
// PrintRGBLEDs displays a list with all configured RGBLEDs
|
||||
func PrintRGBLEDs(cnf *config.Configuration, w io.Writer) {
|
||||
|
||||
// declare tabwriter
|
||||
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
|
||||
|
||||
// headline
|
||||
fmt.Fprintln(tw, "name\tlocation\tblue\tgreen\tred\tenabled\taction")
|
||||
|
||||
for _, rgbled := range cnf.RGBLEDs {
|
||||
fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%v\t", rgbled.RGBLEDName, rgbled.RGBLEDLocation, *rgbled.BaseColorsToGPIO[types.BaseColorBlue], *rgbled.BaseColorsToGPIO[types.BaseColorGreen], *rgbled.BaseColorsToGPIO[types.BaseColorRed], rgbled.RGBLEDEnabled)
|
||||
|
||||
for action, color := range rgbled.ActionMapping {
|
||||
fmt.Fprintf(tw, "%v=%v,", action, color)
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "\n")
|
||||
}
|
||||
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
// PrintSensors displays a list with all configured sensors
|
||||
func PrintSensors(cnf *config.Configuration, w io.Writer) error {
|
||||
|
||||
// declar tabwriter
|
||||
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
|
||||
|
||||
fmt.Fprint(tw, "name\tlocation\ttype\twire-id\ti2c-bus\ti2c-address\tgpio\ttick-duration\tenabled\n")
|
||||
|
||||
for _, sensor := range cnf.Sensors {
|
||||
fmt.Fprintf(tw, "%v\t%v\t%v\t", sensor.Name, sensor.Location, sensor.Model)
|
||||
|
||||
if sensor.WireID != nil {
|
||||
fmt.Fprintf(tw, "%v\t", *sensor.WireID)
|
||||
} else {
|
||||
fmt.Fprintf(tw, "\t")
|
||||
}
|
||||
|
||||
if sensor.I2CBus != nil {
|
||||
fmt.Fprintf(tw, "%v\t", *sensor.I2CBus)
|
||||
} else {
|
||||
fmt.Fprintf(tw, "\t")
|
||||
}
|
||||
|
||||
if sensor.I2CAddress != nil {
|
||||
fmt.Fprintf(tw, "%#v\t", *sensor.I2CAddress)
|
||||
} else {
|
||||
fmt.Fprintf(tw, "\t")
|
||||
}
|
||||
|
||||
if sensor.GPIONumber != nil {
|
||||
fmt.Fprintf(tw, "%v\t", *sensor.GPIONumber)
|
||||
} else {
|
||||
fmt.Fprintf(tw, "\t")
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "%v\t%v\n", sensor.TickDuration, sensor.Enabled)
|
||||
}
|
||||
|
||||
tw.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintMeasuredValues displays a list of measured values
|
||||
func PrintMeasuredValues(measuredValues []*types.MeasuredValue, cnf *config.Configuration, w io.Writer) error {
|
||||
|
||||
sensors, err := GetSensorIDsByMeasuredValues(measuredValues, cnf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sort measured values for every sensor
|
||||
orderedMeasuredValues := make(map[string][]*types.MeasuredValue)
|
||||
for _, measuredValue := range measuredValues {
|
||||
orderedMeasuredValues[measuredValue.SensorID] = append(orderedMeasuredValues[measuredValue.SensorID], measuredValue)
|
||||
}
|
||||
|
||||
// declare tabwriter
|
||||
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
|
||||
|
||||
// headlines
|
||||
i := 0
|
||||
for _, sensor := range sensors {
|
||||
fmt.Fprintf(tw, "%v\t", sensor.FullName())
|
||||
if i == len(sensors)-1 {
|
||||
fmt.Fprintf(tw, "\n")
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// find sensor with the most measured values
|
||||
maxLength := 0
|
||||
for _, orderedMeasuredValue := range orderedMeasuredValues {
|
||||
if len(orderedMeasuredValue) > maxLength {
|
||||
maxLength = len(orderedMeasuredValue)
|
||||
}
|
||||
}
|
||||
|
||||
// body
|
||||
for i := 0; i < maxLength; i++ {
|
||||
for _, sensor := range sensors {
|
||||
if len(orderedMeasuredValues[sensor.ID]) > i {
|
||||
fmt.Fprintf(tw, "%3.3f\t", orderedMeasuredValues[sensor.ID][i].Value)
|
||||
} else {
|
||||
fmt.Fprint(tw, "\t")
|
||||
}
|
||||
|
||||
}
|
||||
fmt.Fprint(tw, "\n")
|
||||
}
|
||||
tw.Flush()
|
||||
|
||||
return err
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
goldenConfig *config.Configuration
|
||||
goldenMeasuredValues []*types.MeasuredValue
|
||||
expectedSensors map[string]*types.Sensor
|
||||
expectedError error
|
||||
}
|
||||
|
||||
var (
|
||||
goldenSensors = []*types.Sensor{
|
||||
&types.Sensor{
|
||||
ID: "5b7c08c8-ee2a-4f88-b796-83658caae09d",
|
||||
},
|
||||
}
|
||||
|
||||
goldenSensorMap = map[string]*types.Sensor{
|
||||
"5b7c08c8-ee2a-4f88-b796-83658caae09d": goldenSensors[0],
|
||||
}
|
||||
|
||||
goldenConfig = &config.Configuration{
|
||||
Sensors: goldenSensors,
|
||||
}
|
||||
|
||||
goldenMeasuredValues = []*types.MeasuredValue{
|
||||
&types.MeasuredValue{
|
||||
ID: "4887b7cc-6b40-4293-bcc1-db1b5035a711",
|
||||
SensorID: "5b7c08c8-ee2a-4f88-b796-83658caae09d",
|
||||
},
|
||||
}
|
||||
|
||||
measuredValuesWrongID = []*types.MeasuredValue{
|
||||
&types.MeasuredValue{
|
||||
ID: "b2784dfb-c1a4-428d-8897-e09a18e7d94d",
|
||||
SensorID: "5b7c08c8-ee2a-4f88-b796-83658caae09d",
|
||||
},
|
||||
&types.MeasuredValue{
|
||||
ID: "81ca1a60-dfea-42f3-a7",
|
||||
SensorID: "5b7c08c8-ee2a-4f88-b796-83658caae09d",
|
||||
},
|
||||
}
|
||||
|
||||
measuredValuesWrongSensorID = []*types.MeasuredValue{
|
||||
&types.MeasuredValue{
|
||||
ID: "b2784dfb-c1a4-428d-8897-e09a18e7d94d",
|
||||
SensorID: "5b7c08c8-ee2a-4f88-b796-83658caae09d",
|
||||
},
|
||||
&types.MeasuredValue{
|
||||
ID: "b2784dfb-c1a4-428d-8897-e09a18e7d94d",
|
||||
SensorID: "5b7c08c8-ee2a-4f88-b79",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestGetSensorIDsByMeasuredValues(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
testCases := []*testCase{
|
||||
&testCase{
|
||||
goldenConfig: goldenConfig,
|
||||
goldenMeasuredValues: goldenMeasuredValues,
|
||||
expectedSensors: goldenSensorMap,
|
||||
expectedError: nil,
|
||||
},
|
||||
&testCase{
|
||||
goldenConfig: goldenConfig,
|
||||
goldenMeasuredValues: measuredValuesWrongID,
|
||||
expectedSensors: nil,
|
||||
expectedError: errorMeasuredValueIDNotValid,
|
||||
},
|
||||
&testCase{
|
||||
goldenConfig: goldenConfig,
|
||||
goldenMeasuredValues: measuredValuesWrongSensorID,
|
||||
expectedSensors: nil,
|
||||
expectedError: errorSensorIDNotValid,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
actualSensors, err := GetSensorIDsByMeasuredValues(testCase.goldenMeasuredValues, testCase.goldenConfig)
|
||||
require.EqualValues(testCase.expectedError, err, "Not the expected error")
|
||||
require.EqualValues(testCase.expectedSensors, actualSensors, "Returned sensor list not expected")
|
||||
}
|
||||
}
|
@ -2,141 +2,92 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/rgbled"
|
||||
"github.com/volker-raschek/flucky/pkg/sensor"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
// Configuration of flucky
|
||||
type Configuration struct {
|
||||
var (
|
||||
validUUID = regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
|
||||
)
|
||||
|
||||
// Config represent the configuration
|
||||
type Config struct {
|
||||
Device *types.Device `json:"device"`
|
||||
StorageEndpoint string `json:"storage_endpoint"`
|
||||
RGBLEDs []*types.RGBLED `json:"rgb_leds"`
|
||||
Sensors []*types.Sensor `json:"sensors"`
|
||||
}
|
||||
|
||||
// AddRGBLED add a new RGBLED
|
||||
func (c *Configuration) AddRGBLED(rgbLED *types.RGBLED) error {
|
||||
|
||||
// check if RGBLEDID is a valid UUID string
|
||||
if !validUUID.MatchString(rgbLED.RGBLEDID) {
|
||||
rgbLED.RGBLEDID = uuid.NewV4().String()
|
||||
}
|
||||
|
||||
// check if sensor name and sensor uuid already exists
|
||||
for _, l := range c.RGBLEDs {
|
||||
if l.RGBLEDName == rgbLED.RGBLEDName {
|
||||
return fmt.Errorf("RGBLED %v already exists", rgbLED.RGBLEDName)
|
||||
}
|
||||
|
||||
if l.RGBLEDID == rgbLED.RGBLEDID {
|
||||
return fmt.Errorf("RGBLED %v with UUID %v already exists", rgbLED.RGBLEDName, rgbLED.RGBLEDID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check if sensor has a valid device id
|
||||
if rgbLED.DeviceID != c.Device.ID {
|
||||
rgbLED.DeviceID = c.Device.ID
|
||||
}
|
||||
|
||||
// overwrite creation date
|
||||
rgbLED.CreationDate = time.Now()
|
||||
|
||||
// check
|
||||
c.RGBLEDs = append(c.RGBLEDs, rgbLED)
|
||||
|
||||
return nil
|
||||
StorageEndpoint string `json:"storage_endpoint"`
|
||||
}
|
||||
|
||||
// AddSensor add a new sensor
|
||||
func (c *Configuration) AddSensor(sensor *types.Sensor) error {
|
||||
func (cnf *Config) AddSensor(sensor *types.Sensor) error {
|
||||
|
||||
// check if ID is a valid UUID string
|
||||
// Verify that a device is configured
|
||||
if cnf.Device == nil {
|
||||
return fmt.Errorf("No device configured")
|
||||
}
|
||||
|
||||
// Define a new UUID if the current UUID is invalid
|
||||
if !validUUID.MatchString(sensor.ID) {
|
||||
sensor.ID = uuid.NewV4().String()
|
||||
}
|
||||
|
||||
// Verify that the sensor has a valid name
|
||||
if len(sensor.Name) <= 0 {
|
||||
return fmt.Errorf("No sensor name defined")
|
||||
}
|
||||
|
||||
// check if sensor name and sensor uuid already exists
|
||||
for _, s := range c.Sensors {
|
||||
if s.Name == sensor.Name {
|
||||
return fmt.Errorf("Sensor %v already exists", s.Name)
|
||||
for _, cnfSensor := range cnf.Sensors {
|
||||
if cnfSensor.Name == sensor.Name ||
|
||||
cnfSensor.ID == sensor.ID {
|
||||
return fmt.Errorf("Sensor with same name or id already exist")
|
||||
}
|
||||
|
||||
if s.ID == sensor.ID {
|
||||
return fmt.Errorf("Sensor %v with UUID %v already exists", s.Name, s.ID)
|
||||
}
|
||||
|
||||
if s.WireID != nil && sensor.WireID != nil {
|
||||
if *s.WireID == *sensor.WireID {
|
||||
return fmt.Errorf("Sensor with 1wire-id %v already exists as %v", *s.WireID, s.Name)
|
||||
}
|
||||
if cnfSensor.WireID != nil &&
|
||||
sensor.WireID != nil &&
|
||||
*cnfSensor.WireID == *sensor.WireID {
|
||||
return fmt.Errorf("Sensor with same wire id already exist")
|
||||
}
|
||||
}
|
||||
|
||||
// check if sensor has a valid tick time
|
||||
if _, err := time.ParseDuration(sensor.TickDuration); err != nil {
|
||||
return fmt.Errorf("Can not parse tick duration: %v", sensor.TickDuration)
|
||||
return fmt.Errorf("Failed to parse tick duration: %v", err)
|
||||
}
|
||||
|
||||
// check if sensor has a valid device id
|
||||
if sensor.DeviceID != c.Device.ID {
|
||||
sensor.DeviceID = c.Device.ID
|
||||
if sensor.DeviceID != cnf.Device.ID {
|
||||
sensor.DeviceID = cnf.Device.ID
|
||||
}
|
||||
|
||||
// overwrite creation date
|
||||
sensor.CreationDate = format.FormatedTime()
|
||||
|
||||
//TODO: check if wire sensor exists in /dev/bus/w1/devices
|
||||
|
||||
// check
|
||||
c.Sensors = append(c.Sensors, sensor)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableRGBLED enables a rgb led by its name or its unique UUID
|
||||
func (c *Configuration) DisableRGBLED(name string) error {
|
||||
found := false
|
||||
|
||||
for _, rgbled := range c.RGBLEDs {
|
||||
|
||||
// disable sensor matched after name
|
||||
if !validUUID.MatchString(name) &&
|
||||
rgbled.RGBLEDName == name {
|
||||
rgbled.RGBLEDEnabled = false
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
// disable sensor matched by uuid
|
||||
if validUUID.MatchString(name) &&
|
||||
rgbled.RGBLEDID == name {
|
||||
rgbled.RGBLEDEnabled = false
|
||||
found = true
|
||||
break
|
||||
// check if wire socket is available
|
||||
if sensor.WireID != nil {
|
||||
socketPath := filepath.Join("/sys/bus/w1/devices", *sensor.WireID, "/w1_slave")
|
||||
if _, err := os.Stat(socketPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("Wire socket not found: %v", socketPath)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Can not found RGB-LED %v", name)
|
||||
}
|
||||
cnf.Sensors = append(cnf.Sensors, sensor)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableSensor disables a sensor by its name or its unique UUID
|
||||
func (c *Configuration) DisableSensor(name string) error {
|
||||
func (cnf *Config) DisableSensor(name string) error {
|
||||
found := false
|
||||
|
||||
for _, sensor := range c.Sensors {
|
||||
for _, sensor := range cnf.Sensors {
|
||||
|
||||
// disable sensor matched after name
|
||||
if !validUUID.MatchString(name) &&
|
||||
@ -162,41 +113,11 @@ func (c *Configuration) DisableSensor(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableRGBLED enables a rgb led by its name or its unique UUID
|
||||
func (c *Configuration) EnableRGBLED(name string) error {
|
||||
found := false
|
||||
|
||||
for _, rgbled := range c.RGBLEDs {
|
||||
|
||||
// disable sensor matched after name
|
||||
if !validUUID.MatchString(name) &&
|
||||
rgbled.RGBLEDName == name {
|
||||
rgbled.RGBLEDEnabled = true
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
// disable sensor matched by uuid
|
||||
if validUUID.MatchString(name) &&
|
||||
rgbled.RGBLEDID == name {
|
||||
rgbled.RGBLEDEnabled = true
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Can not found RGB-LED %v", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableSensor enables a sensor by its name or its unique UUID
|
||||
func (c *Configuration) EnableSensor(name string) error {
|
||||
func (cnf *Config) EnableSensor(name string) error {
|
||||
found := false
|
||||
|
||||
for _, sensor := range c.Sensors {
|
||||
for _, sensor := range cnf.Sensors {
|
||||
|
||||
// disable sensor matched after name
|
||||
if !validUUID.MatchString(name) &&
|
||||
@ -222,155 +143,10 @@ func (c *Configuration) EnableSensor(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHumiditySensors returns a list of humidity sensors
|
||||
func (c *Configuration) GetHumiditySensors(option Option) []sensor.Sensor {
|
||||
sensors := c.getHumiditySensors()
|
||||
|
||||
cachedSensors := make([]*types.Sensor, 0)
|
||||
|
||||
switch option {
|
||||
case ENABLED:
|
||||
for _, sensor := range sensors {
|
||||
if sensor.Enabled {
|
||||
cachedSensors = append(cachedSensors, sensor)
|
||||
}
|
||||
}
|
||||
return c.convertSensors(cachedSensors)
|
||||
case DISABLED:
|
||||
for _, sensor := range sensors {
|
||||
if !sensor.Enabled {
|
||||
cachedSensors = append(cachedSensors, sensor)
|
||||
}
|
||||
}
|
||||
return c.convertSensors(cachedSensors)
|
||||
default:
|
||||
return c.convertSensors(cachedSensors)
|
||||
}
|
||||
}
|
||||
|
||||
// GetHumiditySensorsByName returns a list of humidity sensors by name,
|
||||
// uuid or wire-id
|
||||
func (c *Configuration) GetHumiditySensorsByName(names []string) []sensor.Sensor {
|
||||
configHumiditySensors := make(map[string]*types.Sensor, 0)
|
||||
|
||||
for _, name := range names {
|
||||
for _, s := range c.getHumiditySensors() {
|
||||
switch name {
|
||||
case s.ID:
|
||||
configHumiditySensors[s.ID] = s
|
||||
case s.Name:
|
||||
configHumiditySensors[s.ID] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
humiditySensors := make([]*types.Sensor, 0)
|
||||
for _, cs := range configHumiditySensors {
|
||||
humiditySensors = append(humiditySensors, cs)
|
||||
}
|
||||
|
||||
return c.convertSensors(humiditySensors)
|
||||
}
|
||||
|
||||
// GetPressureSensors returns a list of pressure sensors
|
||||
func (c *Configuration) GetPressureSensors(option Option) []sensor.Sensor {
|
||||
sensors := c.getPressureSensors()
|
||||
|
||||
cachedSensors := make([]*types.Sensor, 0)
|
||||
|
||||
switch option {
|
||||
case ENABLED:
|
||||
for _, sensor := range sensors {
|
||||
if sensor.Enabled {
|
||||
cachedSensors = append(cachedSensors, sensor)
|
||||
}
|
||||
}
|
||||
return c.convertSensors(cachedSensors)
|
||||
case DISABLED:
|
||||
for _, sensor := range sensors {
|
||||
if !sensor.Enabled {
|
||||
cachedSensors = append(cachedSensors, sensor)
|
||||
}
|
||||
}
|
||||
return c.convertSensors(cachedSensors)
|
||||
default:
|
||||
return c.convertSensors(cachedSensors)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPressureSensorsByName returns a list of pressure sensors by name,
|
||||
// uuid or wire-id
|
||||
func (c *Configuration) GetPressureSensorsByName(names []string) []sensor.Sensor {
|
||||
configPressureSensors := make(map[string]*types.Sensor, 0)
|
||||
|
||||
for _, name := range names {
|
||||
for _, s := range c.getPressureSensors() {
|
||||
switch name {
|
||||
case s.ID:
|
||||
configPressureSensors[s.ID] = s
|
||||
case s.Name:
|
||||
configPressureSensors[s.ID] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pressureSensors := make([]*types.Sensor, 0)
|
||||
for _, cs := range configPressureSensors {
|
||||
pressureSensors = append(pressureSensors, cs)
|
||||
}
|
||||
|
||||
return c.convertSensors(pressureSensors)
|
||||
}
|
||||
|
||||
func (c *Configuration) GetRGBLEDs(option Option) []rgbled.RGBLED {
|
||||
rgbLEDs := c.RGBLEDs
|
||||
|
||||
switch option {
|
||||
case ENABLED:
|
||||
for i, rgbLED := range c.RGBLEDs {
|
||||
if !rgbLED.RGBLEDEnabled {
|
||||
rgbLEDs = append(rgbLEDs[:i], rgbLEDs[i+1:]...)
|
||||
}
|
||||
}
|
||||
return c.convertRGBLEDs(rgbLEDs)
|
||||
case DISABLED:
|
||||
for i, rgbLED := range c.RGBLEDs {
|
||||
if rgbLED.RGBLEDEnabled {
|
||||
rgbLEDs = append(rgbLEDs[:i], rgbLEDs[i+1:]...)
|
||||
}
|
||||
}
|
||||
return c.convertRGBLEDs(rgbLEDs)
|
||||
default:
|
||||
return c.convertRGBLEDs(rgbLEDs)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Configuration) GetRGBLEDsByName(names []string) []rgbled.RGBLED {
|
||||
configRGBLEDs := make(map[string]*types.RGBLED, 0)
|
||||
|
||||
for _, name := range names {
|
||||
for _, led := range c.RGBLEDs {
|
||||
switch name {
|
||||
case led.RGBLEDID:
|
||||
configRGBLEDs[led.RGBLEDID] = led
|
||||
case led.RGBLEDName:
|
||||
configRGBLEDs[led.RGBLEDID] = led
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rgbLEDs := make([]*types.RGBLED, 0)
|
||||
for _, rgbLED := range configRGBLEDs {
|
||||
rgbLEDs = append(rgbLEDs, rgbLED)
|
||||
}
|
||||
|
||||
return c.convertRGBLEDs(rgbLEDs)
|
||||
}
|
||||
|
||||
// GetSensorByID returns a sensor matched by his id. If no sensor has this id,
|
||||
// the function returns nil
|
||||
func (c *Configuration) GetSensorByID(id string) *types.Sensor {
|
||||
for _, sensor := range c.Sensors {
|
||||
func (cnf *Config) GetSensorByID(id string) *types.Sensor {
|
||||
for _, sensor := range cnf.Sensors {
|
||||
if sensor.ID == id {
|
||||
return sensor
|
||||
}
|
||||
@ -378,239 +154,21 @@ func (c *Configuration) GetSensorByID(id string) *types.Sensor {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSensors returns a list of humidity sensors
|
||||
func (c *Configuration) GetSensors(option Option) []sensor.Sensor {
|
||||
cachedSensors := make([]*types.Sensor, 0)
|
||||
|
||||
switch option {
|
||||
case ENABLED:
|
||||
for _, sensor := range c.Sensors {
|
||||
if sensor.Enabled {
|
||||
cachedSensors = append(cachedSensors, sensor)
|
||||
}
|
||||
}
|
||||
return c.convertSensors(cachedSensors)
|
||||
case DISABLED:
|
||||
for _, sensor := range c.Sensors {
|
||||
if !sensor.Enabled {
|
||||
cachedSensors = append(cachedSensors, sensor)
|
||||
}
|
||||
}
|
||||
return c.convertSensors(cachedSensors)
|
||||
default:
|
||||
return c.convertSensors(cachedSensors)
|
||||
}
|
||||
}
|
||||
|
||||
// GetStorageEndpointURL returns a parsed storage endpoint url
|
||||
func (c *Configuration) GetStorageEndpointURL() (*url.URL, error) {
|
||||
storageEndpointURL, err := url.Parse(c.StorageEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can not parse storage endpoint URL")
|
||||
}
|
||||
return storageEndpointURL, nil
|
||||
}
|
||||
|
||||
// GetTemperatureSensors returns a list of temperature sensors
|
||||
func (c *Configuration) GetTemperatureSensors(option Option) []sensor.Sensor {
|
||||
sensors := c.getTemperatureSensors()
|
||||
|
||||
cachedSensors := make([]*types.Sensor, 0)
|
||||
|
||||
switch option {
|
||||
case ENABLED:
|
||||
for _, sensor := range sensors {
|
||||
if sensor.Enabled {
|
||||
cachedSensors = append(cachedSensors, sensor)
|
||||
}
|
||||
}
|
||||
return c.convertSensors(cachedSensors)
|
||||
case DISABLED:
|
||||
for _, sensor := range sensors {
|
||||
if !sensor.Enabled {
|
||||
cachedSensors = append(cachedSensors, sensor)
|
||||
}
|
||||
}
|
||||
return c.convertSensors(cachedSensors)
|
||||
default:
|
||||
return c.convertSensors(cachedSensors)
|
||||
}
|
||||
}
|
||||
|
||||
// GetTemperatureSensorsByName returns a list of temperature sensors by name,
|
||||
// uuid or wire-id
|
||||
func (c *Configuration) GetTemperatureSensorsByName(names []string) []sensor.Sensor {
|
||||
configTemperatureSensors := make(map[string]*types.Sensor, 0)
|
||||
|
||||
for _, name := range names {
|
||||
for _, s := range c.getTemperatureSensors() {
|
||||
switch name {
|
||||
case s.ID:
|
||||
configTemperatureSensors[s.ID] = s
|
||||
case s.Name:
|
||||
configTemperatureSensors[s.ID] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
temperatureSensors := make([]*types.Sensor, 0)
|
||||
for _, cs := range configTemperatureSensors {
|
||||
temperatureSensors = append(temperatureSensors, cs)
|
||||
}
|
||||
|
||||
return c.convertSensors(temperatureSensors)
|
||||
}
|
||||
|
||||
// RemoveRGBLED deletes a LED by its name or its unique UUID
|
||||
func (c *Configuration) RemoveRGBLED(name string) error {
|
||||
for i, rgbLED := range c.RGBLEDs {
|
||||
// remove machted name
|
||||
if !validUUID.MatchString(name) &&
|
||||
rgbLED.RGBLEDName == name {
|
||||
c.RGBLEDs = append(c.RGBLEDs[:i], c.RGBLEDs[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
// remove machted uuid
|
||||
if validUUID.MatchString(name) &&
|
||||
rgbLED.RGBLEDID == name {
|
||||
c.RGBLEDs = append(c.RGBLEDs[:i], c.RGBLEDs[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Can not find RGBLED %v", name)
|
||||
}
|
||||
|
||||
// RemoveSensor deletes a sensor by its name or its unique UUID
|
||||
func (c *Configuration) RemoveSensor(name string) error {
|
||||
for i, sensor := range c.Sensors {
|
||||
func (cnf *Config) RemoveSensor(name string) error {
|
||||
for i, sensor := range cnf.Sensors {
|
||||
// remove machted name
|
||||
if !validUUID.MatchString(name) &&
|
||||
sensor.Name == name {
|
||||
c.Sensors = append(c.Sensors[:i], c.Sensors[i+1:]...)
|
||||
cnf.Sensors = append(cnf.Sensors[:i], cnf.Sensors[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
// remove machted uuid
|
||||
if validUUID.MatchString(name) &&
|
||||
sensor.ID == name {
|
||||
c.Sensors = append(c.Sensors[:i], c.Sensors[i+1:]...)
|
||||
cnf.Sensors = append(cnf.Sensors[:i], cnf.Sensors[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Can not find sensor %v", name)
|
||||
}
|
||||
|
||||
// RenameRGBLED renames a sensor identified by the name or the UUID
|
||||
func (c *Configuration) RenameRGBLED(oldName, newName string) error {
|
||||
for _, rgbled := range c.RGBLEDs {
|
||||
if rgbled.RGBLEDName == oldName ||
|
||||
rgbled.RGBLEDID == oldName {
|
||||
rgbled.RGBLEDName = newName
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Could not find rgb-led %v to replace into with %v", oldName, newName)
|
||||
}
|
||||
|
||||
// RenameSensor renames a sensor identified by the name or the UUID
|
||||
func (c *Configuration) RenameSensor(oldName, newName string) error {
|
||||
for _, sensor := range c.Sensors {
|
||||
if sensor.Name == oldName ||
|
||||
sensor.ID == oldName {
|
||||
sensor.Name = newName
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Could not find remote %v to replace into with %v", oldName, newName)
|
||||
}
|
||||
|
||||
func (c *Configuration) SetStorageEndpoint(storageEndpoint string) error {
|
||||
storageEndpointURL, err := url.Parse(storageEndpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not prase sorage endpoint url: %v", err)
|
||||
}
|
||||
|
||||
supportedStorageEndpoints := []string{"file", "postgres"}
|
||||
|
||||
found := false
|
||||
for _, supportedStorageEndpoint := range supportedStorageEndpoints {
|
||||
if supportedStorageEndpoint == storageEndpointURL.Scheme {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Storage endpoint scheme not supported")
|
||||
}
|
||||
|
||||
c.StorageEndpoint = storageEndpointURL.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Configuration) convertSensors(sensors []*types.Sensor) []sensor.Sensor {
|
||||
cachedSensors := make([]sensor.Sensor, 0)
|
||||
|
||||
for _, s := range sensors {
|
||||
switch s.Model {
|
||||
case types.BME280:
|
||||
cachedSensors = append(cachedSensors, &sensor.BME280{
|
||||
Sensor: s,
|
||||
})
|
||||
case types.DHT11:
|
||||
cachedSensors = append(cachedSensors, &sensor.DHT11{
|
||||
Sensor: s,
|
||||
})
|
||||
case types.DHT22:
|
||||
cachedSensors = append(cachedSensors, &sensor.DHT22{
|
||||
Sensor: s,
|
||||
})
|
||||
case types.DS18B20:
|
||||
cachedSensors = append(cachedSensors, &sensor.DS18B20{
|
||||
Sensor: s,
|
||||
})
|
||||
}
|
||||
}
|
||||
return cachedSensors
|
||||
}
|
||||
|
||||
func (c *Configuration) convertRGBLEDs(rgbLEDs []*types.RGBLED) []rgbled.RGBLED {
|
||||
leds := make([]rgbled.RGBLED, 0)
|
||||
|
||||
for _, rgbLED := range rgbLEDs {
|
||||
leds = append(leds, &rgbled.DefaultRGBLED{
|
||||
RGBLED: rgbLED,
|
||||
})
|
||||
}
|
||||
|
||||
return leds
|
||||
}
|
||||
|
||||
func (c *Configuration) getHumiditySensors() []*types.Sensor {
|
||||
humiditySensors := make([]*types.Sensor, 0)
|
||||
for _, s := range c.Sensors {
|
||||
if _, ok := humiditySensorModels[s.Model]; ok {
|
||||
humiditySensors = append(humiditySensors, s)
|
||||
}
|
||||
}
|
||||
return humiditySensors
|
||||
}
|
||||
|
||||
func (c *Configuration) getPressureSensors() []*types.Sensor {
|
||||
pressureSensors := make([]*types.Sensor, 0)
|
||||
for _, s := range c.Sensors {
|
||||
if _, ok := pressureSensorModels[s.Model]; ok {
|
||||
pressureSensors = append(pressureSensors, s)
|
||||
}
|
||||
}
|
||||
return pressureSensors
|
||||
}
|
||||
|
||||
func (c *Configuration) getTemperatureSensors() []*types.Sensor {
|
||||
temperatureSensors := make([]*types.Sensor, 0)
|
||||
for _, s := range c.Sensors {
|
||||
if _, ok := temperatureSensorModels[s.Model]; ok {
|
||||
temperatureSensors = append(temperatureSensors, s)
|
||||
}
|
||||
}
|
||||
return temperatureSensors
|
||||
}
|
||||
|
40
pkg/config/config_test.go
Normal file
40
pkg/config/config_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
func TestAddRemoveSensor(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Test: No device configured
|
||||
cnf := new(config.Config)
|
||||
err := cnf.AddSensor(&types.Sensor{ID: "1aa32c9a-b505-456d-868b-0403344f4cdf"})
|
||||
require.Error(err)
|
||||
|
||||
// wireID := "sdfsdff"
|
||||
// i2cBus := 1
|
||||
// i2cAddress := 78
|
||||
|
||||
cnf.Device = &types.Device{ID: "d6176a06-2b0b-41af-a85c-913e8f61c35d"}
|
||||
testCases := map[*types.Sensor]error{
|
||||
{ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d"}: fmt.Errorf("No sensor name defined"),
|
||||
{ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01"}: fmt.Errorf("Failed to parse tick duration: time: invalid duration "),
|
||||
{ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: nil,
|
||||
{ID: "1aa32c9a-b505-456d-868b-0403344f4cdf", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: fmt.Errorf("Sensor with same name or id already exist"),
|
||||
// {ID: "f90cfc18-f141-4cfd-a8d2-fb40082de5cc", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test01", TickDuration: "5s"}: fmt.Errorf("Sensor with same name or id already exist"),
|
||||
// {ID: "860a9922-62cb-4c9b-b5af-5fa783cebe9d", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test02", TickDuration: "5s", WireID: &wireID}: fmt.Errorf("Wire socket not found: /sys/bus/w1/devices/sdfsdff/w1_slave"),
|
||||
// {ID: "9be8989c-b2a1-4401-a82f-d6989ec226fe", DeviceID: "d6176a06-2b0b-41af-a85c-913e8f61c35d", Name: "Test02", TickDuration: "5s"}: nil,
|
||||
}
|
||||
|
||||
for sensor, expectedErr := range testCases {
|
||||
err := cnf.AddSensor(sensor)
|
||||
require.Equal(expectedErr, err)
|
||||
}
|
||||
|
||||
}
|
@ -3,57 +3,59 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var validUUID = regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
|
||||
// Decode a configuration from a reader
|
||||
func Decode(r io.Reader) (*Config, error) {
|
||||
cnf := new(Config)
|
||||
jsonDecoder := json.NewDecoder(r)
|
||||
if err := jsonDecoder.Decode(&cnf); err != nil {
|
||||
return nil, fmt.Errorf("Can not unmarshal JSON: %v", err)
|
||||
}
|
||||
return cnf, nil
|
||||
}
|
||||
|
||||
// Encode a configuration to a writer
|
||||
func Encode(cnf *Config, w io.Writer) error {
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetIndent("", " ")
|
||||
err := encoder.Encode(cnf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error encoding config to json: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the configuration file
|
||||
func Read(configFile string) (*Configuration, error) {
|
||||
|
||||
fc := &Configuration{}
|
||||
|
||||
func Read(configFile string) (*Config, error) {
|
||||
f, err := os.Open(configFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can not open file %v: %v", configFile, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
jsonDecoder := json.NewDecoder(f)
|
||||
if err := jsonDecoder.Decode(&fc); err != nil {
|
||||
return nil, fmt.Errorf("Can not unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
return fc, nil
|
||||
return Decode(f)
|
||||
|
||||
}
|
||||
|
||||
// Write the configuration into a file, specified by the configuration filepath
|
||||
func Write(cfg *Configuration, configFile string) error {
|
||||
|
||||
func Write(cnf *Config, configFile string) error {
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
configDir := filepath.Dir(configFile)
|
||||
err := os.MkdirAll(configDir, os.ModeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not create config directory %v: %v", configDir, err)
|
||||
return fmt.Errorf("Failed to create config directory %v: %v", configDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.Create(configFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not write config file: %v", err)
|
||||
return fmt.Errorf("Failed not create config file %v: %v", configFile, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
encoder := json.NewEncoder(f)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error in encoding struct to json: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
return Encode(cnf, f)
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
package config
|
||||
|
||||
type Option int
|
||||
|
||||
const (
|
||||
// ALL specified enabled and disabled items
|
||||
ALL Option = iota + 1
|
||||
|
||||
// ENABLED items
|
||||
ENABLED
|
||||
|
||||
// DISABLED items
|
||||
DISABLED
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
package config
|
||||
|
||||
import "github.com/volker-raschek/flucky/pkg/types"
|
||||
|
||||
var (
|
||||
humiditySensorModels = map[types.SensorModel]types.SensorModel{
|
||||
types.BME280: types.BME280,
|
||||
types.DHT11: types.DHT11,
|
||||
types.DHT22: types.DHT22,
|
||||
}
|
||||
|
||||
pressureSensorModels = map[types.SensorModel]types.SensorModel{
|
||||
types.BME280: types.BME280,
|
||||
}
|
||||
|
||||
temperatureSensorModels = map[types.SensorModel]types.SensorModel{
|
||||
types.BME280: types.BME280,
|
||||
types.DHT11: types.DHT11,
|
||||
types.DHT22: types.DHT22,
|
||||
types.DS18B20: types.DS18B20,
|
||||
}
|
||||
)
|
@ -1,98 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/storage"
|
||||
"github.com/volker-raschek/flucky/pkg/storage/logfile"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
type cacheStore struct {
|
||||
compression bool
|
||||
cache []*types.MeasuredValue
|
||||
mux *sync.Mutex
|
||||
round float64
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
func (cs *cacheStore) Add(measuredValue *types.MeasuredValue) {
|
||||
cs.mux.Lock()
|
||||
defer cs.mux.Unlock()
|
||||
cs.cache = append(cs.cache, measuredValue)
|
||||
}
|
||||
|
||||
func (cs *cacheStore) Flush(measuredValue *types.MeasuredValue) *cacheStore {
|
||||
cs.mux.Lock()
|
||||
defer cs.mux.Unlock()
|
||||
cs.cache = make([]*types.MeasuredValue, 0)
|
||||
return cs
|
||||
}
|
||||
|
||||
func (cs *cacheStore) Get(id string) *types.MeasuredValue {
|
||||
cs.mux.Lock()
|
||||
defer cs.mux.Unlock()
|
||||
for _, measuredValue := range cs.cache {
|
||||
if measuredValue.ID == id {
|
||||
return measuredValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *cacheStore) Size() int {
|
||||
cs.mux.Lock()
|
||||
defer cs.mux.Unlock()
|
||||
return len(cs.cache)
|
||||
}
|
||||
|
||||
func (cs *cacheStore) WriteToEndpoint() error {
|
||||
cs.mux.Lock()
|
||||
defer cs.mux.Unlock()
|
||||
defer func() { cs.cache = make([]*types.MeasuredValue, 0) }()
|
||||
switch cs.URL.Scheme {
|
||||
case "file":
|
||||
return cs.logfile()
|
||||
case "postgres":
|
||||
return cs.postgres()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *cacheStore) logfile() error {
|
||||
|
||||
newMeasuredValues := make([]*types.MeasuredValue, 0)
|
||||
for _, measuredValue := range cs.cache {
|
||||
newMeasuredValues = append(newMeasuredValues, measuredValue)
|
||||
}
|
||||
|
||||
if cs.round != 0 {
|
||||
storage.Round(newMeasuredValues, cs.round)
|
||||
}
|
||||
|
||||
measuredLogfile := logfile.New(cs.URL.Path)
|
||||
|
||||
measuredValues, err := measuredLogfile.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
measuredValues = append(measuredValues, newMeasuredValues...)
|
||||
|
||||
if cs.compression {
|
||||
measuredValues = storage.Compression(measuredValues)
|
||||
}
|
||||
|
||||
err = measuredLogfile.Write(measuredValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *cacheStore) postgres() error {
|
||||
ctx := context.Background()
|
||||
return storage.Write(ctx, cs.cache, cs.URL)
|
||||
}
|
@ -2,81 +2,77 @@ package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/config"
|
||||
"github.com/volker-raschek/flucky/pkg/sensor"
|
||||
"github.com/volker-raschek/flucky/pkg/storage"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/volker-raschek/go-logger/pkg/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
flogger = logger.NewSilentLogger()
|
||||
)
|
||||
func Start(cnf *config.Config, flogger logger.Logger) error {
|
||||
|
||||
func SetLogger(logger logger.Logger) {
|
||||
flogger = logger
|
||||
}
|
||||
sensors := make([]sensor.Sensor, 0)
|
||||
for _, cnfSensor := range cnf.Sensors {
|
||||
if !cnfSensor.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
// Start the daemon
|
||||
func Start(cnf *config.Configuration, cacheSize uint, compression bool, round float64) error {
|
||||
sensor, err := sensor.New(cnfSensor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sensors = append(sensors, sensor)
|
||||
}
|
||||
|
||||
storageEndpointURL, err := cnf.GetStorageEndpointURL()
|
||||
measuredValueChannel := make(chan *types.MeasuredValue, 0)
|
||||
|
||||
// load storage endpoint
|
||||
storageEndpoint, err := storage.New(cnf.StorageEndpoint, flogger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cache := &cacheStore{
|
||||
compression: compression,
|
||||
cache: make([]*types.MeasuredValue, 0),
|
||||
mux: new(sync.Mutex),
|
||||
round: round,
|
||||
URL: storageEndpointURL,
|
||||
interruptChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(interruptChannel, os.Kill, syscall.SIGTERM)
|
||||
|
||||
// Collection
|
||||
parentCtx := context.Background()
|
||||
|
||||
// Insert device if not exist
|
||||
device, _ := storageEndpoint.SelectDevice(parentCtx, cnf.Device.ID)
|
||||
if device == nil {
|
||||
if err := storageEndpoint.InsertDevice(parentCtx, cnf.Device); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert sensors if not exist
|
||||
for _, cnfSensor := range cnf.Sensors {
|
||||
sensor, _ := storageEndpoint.SelectSensor(parentCtx, cnfSensor.ID)
|
||||
if sensor == nil {
|
||||
if err := storageEndpoint.InsertSensor(parentCtx, cnfSensor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context
|
||||
parentCtx := context.Background()
|
||||
ctx, cancel := context.WithCancel(parentCtx)
|
||||
|
||||
// channels
|
||||
debugChannel := make(chan string, 0)
|
||||
infoChannel := make(chan string, 0)
|
||||
warnChannel := make(chan string, 0)
|
||||
errorChannel := make(chan error, 0)
|
||||
fatalChannel := make(chan error, 1)
|
||||
interruptChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(interruptChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||
measuredValueChannel := make(chan *types.MeasuredValue, 0)
|
||||
|
||||
// Info
|
||||
flogger.Debug("Use compression: %v", compression)
|
||||
flogger.Debug("Round values to: %v", round)
|
||||
|
||||
// Init semaphoreChannel
|
||||
semaphoreChannels := make(map[string]chan struct{})
|
||||
for _, sensor := range cnf.GetSensors(config.ENABLED) {
|
||||
semaphoreChannels[sensor.GetID()] = make(chan struct{}, 1)
|
||||
}
|
||||
|
||||
// Start producers
|
||||
for _, s := range cnf.GetSensors(config.ENABLED) {
|
||||
|
||||
// start go routine for each sensor
|
||||
for _, s := range sensors {
|
||||
go func(sensor sensor.Sensor) {
|
||||
// run forever
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-semaphoreChannels[sensor.GetID()]:
|
||||
case <-sensor.GetTicker().C:
|
||||
measuredValues, err := sensor.Read()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
return
|
||||
flogger.Error("%v", err)
|
||||
continue
|
||||
}
|
||||
for _, measuredValue := range measuredValues {
|
||||
measuredValueChannel <- measuredValue
|
||||
@ -84,98 +80,36 @@ func Start(cnf *config.Configuration, cacheSize uint, compression bool, round fl
|
||||
}
|
||||
}
|
||||
}(s)
|
||||
|
||||
// start ticker for each sensor
|
||||
go func(sensor sensor.Sensor) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-sensor.GetTicker().C:
|
||||
semaphoreChannels[sensor.GetID()] <- struct{}{}
|
||||
}
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
debugChannel <- fmt.Sprintf("Stop consumer of measured values: Closed context: %v", ctx.Err().Error())
|
||||
return
|
||||
case measuredValue := <-measuredValueChannel:
|
||||
cache.Add(measuredValue)
|
||||
debugChannel <- fmt.Sprintf("CacheStore ID: %v, Sensor ID: %v, Type: %v, Value: %v", cache.Size(), measuredValue.SensorID, measuredValue.ValueType, measuredValue.Value)
|
||||
if cache.Size() >= int(cacheSize) {
|
||||
debugChannel <- fmt.Sprint("Write cache into storage endpoint")
|
||||
err := cache.WriteToEndpoint()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
measuredValues := make([]*types.MeasuredValue, 0, 10)
|
||||
for {
|
||||
select {
|
||||
case debug, _ := <-debugChannel:
|
||||
flogger.Debug("%v", debug)
|
||||
case info, _ := <-infoChannel:
|
||||
flogger.Info("%v", info)
|
||||
case warn, _ := <-warnChannel:
|
||||
flogger.Warn("%v", warn)
|
||||
case err, _ := <-errorChannel:
|
||||
flogger.Error("%v", err)
|
||||
case fatal, _ := <-fatalChannel:
|
||||
flogger.Fatal("Received a fatal error: %v", fatal)
|
||||
case measuredValue := <-measuredValueChannel:
|
||||
flogger.Debug("%v\t%v\t%v", measuredValue.ID, measuredValue.ValueType, measuredValue.Value)
|
||||
measuredValues = append(measuredValues, measuredValue)
|
||||
|
||||
if cap(measuredValues) == len(measuredValues) {
|
||||
flogger.Debug("Flush cache")
|
||||
err := storageEndpoint.InsertMeasuredValues(ctx, measuredValues)
|
||||
if err != nil {
|
||||
flogger.Error("%v", err)
|
||||
}
|
||||
measuredValues = make([]*types.MeasuredValue, 0, 10)
|
||||
}
|
||||
|
||||
case <-interruptChannel:
|
||||
flogger.Debug("Write %v cached values into storage endpoint", cache.Size())
|
||||
err := cache.WriteToEndpoint()
|
||||
cancel()
|
||||
close(measuredValueChannel)
|
||||
|
||||
err := storageEndpoint.InsertMeasuredValues(ctx, measuredValues)
|
||||
if err != nil {
|
||||
flogger.Error("%v", err)
|
||||
}
|
||||
flogger.Debug("Close context")
|
||||
cancel()
|
||||
flogger.Debug("Close channels")
|
||||
close(debugChannel)
|
||||
close(infoChannel)
|
||||
close(warnChannel)
|
||||
close(errorChannel)
|
||||
close(interruptChannel)
|
||||
return nil
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// func checkDeviceInDatabase(ctx context.Context, device *types.Device, database db.Database) {
|
||||
// _, err := database.SelectDeviceByID(ctx, device.ID)
|
||||
// if err != nil {
|
||||
// flogger.Debug("It's seems the current device is not registered in the database. Register the device now")
|
||||
// err2 := database.InsertDevices(ctx, []*types.Device{device})
|
||||
// if err2 != nil {
|
||||
// flogger.Fatal("Can not register device into database: %v", err2)
|
||||
// }
|
||||
// flogger.Debug("Device successfully registered into the database")
|
||||
// return
|
||||
// }
|
||||
// flogger.Debug("Device already registered into the database")
|
||||
// }
|
||||
|
||||
// func checkSensorsInDatabase(ctx context.Context, sensors []*types.Sensor, database db.Database) {
|
||||
// for _, sensor := range sensors {
|
||||
// _, err := database.SelectSensorByID(ctx, sensor.ID)
|
||||
// if err != nil {
|
||||
// flogger.Debug("It's seems the sensor %v is not registered in the database. Register the sensor now", sensor.Name)
|
||||
// err2 := database.InsertSensors(ctx, []*types.Sensor{sensor})
|
||||
// if err2 != nil {
|
||||
// flogger.Fatal("Can not register sensor %v into database: %v", sensor.Name, err2)
|
||||
// }
|
||||
// flogger.Debug("Sensor %v successfully registered into the database", sensor.Name)
|
||||
// continue
|
||||
// }
|
||||
// flogger.Debug("Sensor %v is already registered into the database", sensor.Name)
|
||||
// }
|
||||
// }
|
||||
|
@ -1,16 +0,0 @@
|
||||
package collect
|
||||
|
||||
func Errors(errorChannel <-chan error) []error {
|
||||
errorList := make([]error, 0)
|
||||
for {
|
||||
select {
|
||||
case err, more := <-errorChannel:
|
||||
if more {
|
||||
errorList = append(errorList, err)
|
||||
continue
|
||||
}
|
||||
default:
|
||||
return errorList
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package collect
|
||||
|
||||
import (
|
||||
"github.com/go-flucky/flucky/pkg/types"
|
||||
)
|
||||
|
||||
func MeasuredValues(measuredValueChannel <-chan *types.MeasuredValue) []*types.MeasuredValue {
|
||||
cachedMeasuredValues := make([]*types.MeasuredValue, 0)
|
||||
for {
|
||||
select {
|
||||
case measuredValue, more := <-measuredValueChannel:
|
||||
if more {
|
||||
cachedMeasuredValues = append(cachedMeasuredValues, measuredValue)
|
||||
continue
|
||||
}
|
||||
default:
|
||||
return cachedMeasuredValues
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package distribute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
func MeasuredValues(ctx context.Context, channels int, inputChannel <-chan *types.MeasuredValue) []chan *types.MeasuredValue {
|
||||
outputChannels := make([]chan *types.MeasuredValue, channels)
|
||||
|
||||
for i := 0; i <= channels; i++ {
|
||||
outputChannel := make(chan *types.MeasuredValue)
|
||||
outputChannels = append(outputChannels, outputChannel)
|
||||
}
|
||||
|
||||
go func(ctx context.Context, inputChannel <-chan *types.MeasuredValue, outputChannels []chan *types.MeasuredValue) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case measuredValue, _ := <-inputChannel:
|
||||
for _, outputChannel := range outputChannels {
|
||||
outputChannel <- measuredValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}(ctx, inputChannel, outputChannels)
|
||||
|
||||
return outputChannels
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package prittyprint
|
||||
|
||||
import "fmt"
|
||||
|
||||
func FormatErrors(errors []error) error {
|
||||
if len(errors) > 0 {
|
||||
errMsg := ""
|
||||
for i, err := range errors {
|
||||
if i == 0 {
|
||||
errMsg = fmt.Sprintf("%v", err.Error())
|
||||
} else {
|
||||
errMsg = fmt.Sprintf("%v\n%v", errMsg, err.Error())
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(errMsg)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,239 +0,0 @@
|
||||
package rgbled
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/stianeikeland/go-rpio"
|
||||
)
|
||||
|
||||
// DefaultRGBLED is a RGBLED which implement all functions of the interface
|
||||
// RGBLED
|
||||
type DefaultRGBLED struct {
|
||||
*types.RGBLED
|
||||
}
|
||||
|
||||
// Blue makes the RGBLED shine in blue
|
||||
func (rgbled *DefaultRGBLED) Blue() error {
|
||||
gpios := []*types.GPIO{
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorBlue],
|
||||
}
|
||||
|
||||
if err := rgbled.Off(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rgbled.switchColors(gpios, true); err != nil {
|
||||
return fmt.Errorf("Can not operate with GPIOs: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Green makes the RGBLED shine in green
|
||||
func (rgbled *DefaultRGBLED) Green() error {
|
||||
|
||||
gpios := []*types.GPIO{
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorGreen],
|
||||
}
|
||||
|
||||
if err := rgbled.Off(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rgbled.switchColors(gpios, true); err != nil {
|
||||
return fmt.Errorf("Can not operate with GPIOs: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Off turns on the RGBLED off
|
||||
func (rgbled *DefaultRGBLED) Off() error {
|
||||
|
||||
gpios := []*types.GPIO{
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorBlue],
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorGreen],
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorRed],
|
||||
}
|
||||
|
||||
if err := rgbled.switchColors(gpios, false); err != nil {
|
||||
return fmt.Errorf("Can not turn GPIOs off: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// On turns on the RGBLED
|
||||
func (rgbled *DefaultRGBLED) On() error {
|
||||
return rgbled.White()
|
||||
}
|
||||
|
||||
// Purple makes the RGBLED shine in purple
|
||||
func (rgbled *DefaultRGBLED) Purple() error {
|
||||
gpios := []*types.GPIO{
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorBlue],
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorRed],
|
||||
}
|
||||
|
||||
if err := rgbled.Off(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rgbled.switchColors(gpios, true); err != nil {
|
||||
return fmt.Errorf("Can not operate with GPIOs: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Red makes the RGBLED shine in red
|
||||
func (rgbled *DefaultRGBLED) Red() error {
|
||||
gpios := []*types.GPIO{
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorRed],
|
||||
}
|
||||
|
||||
if err := rgbled.Off(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rgbled.switchColors(gpios, true); err != nil {
|
||||
return fmt.Errorf("Can not operate with GPIOs: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Turquoise makes the RGBLED shine in turquoise
|
||||
func (rgbled *DefaultRGBLED) Turquoise() error {
|
||||
gpios := []*types.GPIO{
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorBlue],
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorGreen],
|
||||
}
|
||||
|
||||
if err := rgbled.Off(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rgbled.switchColors(gpios, true); err != nil {
|
||||
return fmt.Errorf("Can not operate with GPIOs: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// White makes the RGBLED shine in white
|
||||
func (rgbled *DefaultRGBLED) White() error {
|
||||
gpios := []*types.GPIO{
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorBlue],
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorGreen],
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorRed],
|
||||
}
|
||||
|
||||
if err := rgbled.Off(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rgbled.switchColors(gpios, true); err != nil {
|
||||
return fmt.Errorf("Can not operate with GPIOs: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Yellow makes the RGBLED shine in yellow
|
||||
func (rgbled *DefaultRGBLED) Yellow() error {
|
||||
gpios := []*types.GPIO{
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorGreen],
|
||||
rgbled.BaseColorsToGPIO[types.BaseColorRed],
|
||||
}
|
||||
|
||||
if err := rgbled.Off(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := rgbled.switchColors(gpios, true); err != nil {
|
||||
return fmt.Errorf("Can not operate with GPIOs: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Error makes the RGBLED shine in the error specified color
|
||||
func (rgbled *DefaultRGBLED) Error() error {
|
||||
return rgbled.switchColorBasedOnAction(rgbled.ActionMapping[types.LEDActionError])
|
||||
}
|
||||
|
||||
// Logfile makes the RGBLED shine in the logfile specified color
|
||||
func (rgbled *DefaultRGBLED) Logfile() error {
|
||||
return rgbled.switchColorBasedOnAction(rgbled.ActionMapping[types.LEDActionLogfile])
|
||||
}
|
||||
|
||||
// Sync makes the RGBLED shine in the sync specified color
|
||||
func (rgbled *DefaultRGBLED) Sync() error {
|
||||
return rgbled.switchColorBasedOnAction(rgbled.ActionMapping[types.LEDActionSync])
|
||||
}
|
||||
|
||||
// Warn makes the RGBLED shine in the warn specified color
|
||||
func (rgbled *DefaultRGBLED) Warn() error {
|
||||
return rgbled.switchColorBasedOnAction(rgbled.ActionMapping[types.LEDActionWarn])
|
||||
}
|
||||
|
||||
// Run makes the RGBLED shine in the run specified color
|
||||
func (rgbled *DefaultRGBLED) Run() error {
|
||||
return rgbled.switchColorBasedOnAction(rgbled.ActionMapping[types.LEDActionRun])
|
||||
}
|
||||
|
||||
func (rgbled *DefaultRGBLED) switchColors(gpios []*types.GPIO, on bool) error {
|
||||
if err := rpio.Open(); err != nil {
|
||||
return fmt.Errorf("Cam not open rpio connection: %v", err)
|
||||
}
|
||||
defer rpio.Close()
|
||||
|
||||
for _, gpio := range gpios {
|
||||
gpioInt, err := types.GPIOToInt(*gpio)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not determine %v into integer: %v", gpio, err)
|
||||
}
|
||||
|
||||
pin := rpio.Pin(gpioInt)
|
||||
|
||||
// if rpio.DetectEdge(rpio.P rpio.AnyEdge) {
|
||||
// log.Println("Test")
|
||||
// }
|
||||
|
||||
pin.Pull(rpio.PullOff)
|
||||
pin.Output()
|
||||
|
||||
if on {
|
||||
pin.High()
|
||||
} else {
|
||||
pin.Low()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rgbled *DefaultRGBLED) switchColorBasedOnAction(action types.LEDColor) error {
|
||||
switch action {
|
||||
case types.LEDColorBlue:
|
||||
return rgbled.Blue()
|
||||
case types.LEDColorGreen:
|
||||
return rgbled.Green()
|
||||
case types.LEDColorNone:
|
||||
return rgbled.Off()
|
||||
case types.LEDColorPurple:
|
||||
return rgbled.Purple()
|
||||
case types.LEDColorRed:
|
||||
return rgbled.Red()
|
||||
case types.LEDColorTurquoise:
|
||||
return rgbled.Turquoise()
|
||||
case types.LEDColorWhite:
|
||||
return rgbled.White()
|
||||
case types.LEDColorYellow:
|
||||
return rgbled.Yellow()
|
||||
default:
|
||||
return rgbled.Off()
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package rgbled
|
||||
|
||||
// RGBLED is an interface that discribes all needed functions for a RGBLED
|
||||
type RGBLED interface {
|
||||
Blue() error
|
||||
Green() error
|
||||
Purple() error
|
||||
Red() error
|
||||
Turquoise() error
|
||||
White() error
|
||||
Yellow() error
|
||||
|
||||
Error() error
|
||||
Logfile() error
|
||||
Run() error
|
||||
Sync() error
|
||||
Warn() error
|
||||
|
||||
On() error
|
||||
Off() error
|
||||
}
|
@ -1,275 +0,0 @@
|
||||
package rgbled
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/internal/collect"
|
||||
"github.com/volker-raschek/flucky/pkg/internal/prittyprint"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
// Blue makes all RGB-LEDs which are passed as parameters light up blue.
|
||||
func Blue(rgbLEDs []RGBLED) error {
|
||||
color := types.LEDColorBlue
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CustomColor makes all RGB-LEDs which are passed as parameters light up in
|
||||
// custom color.
|
||||
func CustomColor(rgbLEDs []RGBLED, color types.LEDColor) error {
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Green makes all RGB-LEDs which are passed as parameters light up in green.
|
||||
func Green(rgbLEDs []RGBLED) error {
|
||||
color := types.LEDColorGreen
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Off turns all RGB-LEDs which are passes as parameters off.
|
||||
func Off(rgbLEDs []RGBLED) error {
|
||||
color := types.LEDColorNone
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Purple makes all RGB-LEDs which are passed as parameters light up in purple.
|
||||
func Purple(rgbLEDs []RGBLED) error {
|
||||
color := types.LEDColorPurple
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Red makes all RGB-LEDs which are passed as parameters light up in red.
|
||||
func Red(rgbLEDs []RGBLED) error {
|
||||
color := types.LEDColorRed
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Turquoise makes all RGB-LEDs which are passed as parameters light up in
|
||||
// turquoise.
|
||||
func Turquoise(rgbLEDs []RGBLED) error {
|
||||
color := types.LEDColorTurquoise
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// White makes all RGB-LEDs which are passed as parameters light up in white.
|
||||
func White(rgbLEDs []RGBLED) error {
|
||||
color := types.LEDColorWhite
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Yellow makes all RGB-LEDs which are passed as parameters light up in yellow.
|
||||
func Yellow(rgbLEDs []RGBLED) error {
|
||||
color := types.LEDColorYellow
|
||||
if err := operate(rgbLEDs, color); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func operate(rgbLEDs []RGBLED, color types.LEDColor) error {
|
||||
|
||||
errorChannel := make(chan error, len(rgbLEDs))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(rgbLEDs))
|
||||
|
||||
for _, rgbLED := range rgbLEDs {
|
||||
go func(rgbLED RGBLED, color types.LEDColor, errorChannel chan<- error, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
var err error
|
||||
|
||||
switch color {
|
||||
case types.LEDColorBlue:
|
||||
err = rgbLED.Blue()
|
||||
case types.LEDColorGreen:
|
||||
err = rgbLED.Green()
|
||||
case types.LEDColorPurple:
|
||||
err = rgbLED.Purple()
|
||||
case types.LEDColorNone:
|
||||
err = rgbLED.Off()
|
||||
case types.LEDColorRed:
|
||||
err = rgbLED.Red()
|
||||
case types.LEDColorTurquoise:
|
||||
err = rgbLED.Turquoise()
|
||||
case types.LEDColorWhite:
|
||||
err = rgbLED.White()
|
||||
case types.LEDColorYellow:
|
||||
err = rgbLED.Yellow()
|
||||
default:
|
||||
err = rgbLED.Off()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}(rgbLED, color, errorChannel, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
errors := collect.Errors(errorChannel)
|
||||
if len(errors) > 0 {
|
||||
return prittyprint.FormatErrors(errors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Error makes all RGB-LEDs which are passed as parameters light up in their
|
||||
// error specified color.
|
||||
func Error(rgbLEDs []RGBLED) error {
|
||||
errorChannel := make(chan error, len(rgbLEDs))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(rgbLEDs))
|
||||
|
||||
for _, rgbLED := range rgbLEDs {
|
||||
go func(rgbLED RGBLED, errorChannel chan<- error, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
err := rgbLED.Error()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}(rgbLED, errorChannel, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
errors := collect.Errors(errorChannel)
|
||||
if len(errors) > 0 {
|
||||
return prittyprint.FormatErrors(errors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logfile makes all RGB-LEDs which are passed as parameters light up in their
|
||||
// logfile specified color.
|
||||
func Logfile(rgbLEDs []RGBLED) error {
|
||||
errorChannel := make(chan error, len(rgbLEDs))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(rgbLEDs))
|
||||
|
||||
for _, rgbLED := range rgbLEDs {
|
||||
go func(rgbLED RGBLED, errorChannel chan<- error, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
err := rgbLED.Logfile()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}(rgbLED, errorChannel, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
errors := collect.Errors(errorChannel)
|
||||
if len(errors) > 0 {
|
||||
return prittyprint.FormatErrors(errors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run makes all RGB-LEDs which are passed as parameters light up in their run
|
||||
// specified color.
|
||||
func Run(rgbLEDs []RGBLED) error {
|
||||
errorChannel := make(chan error, len(rgbLEDs))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(rgbLEDs))
|
||||
|
||||
for _, rgbLED := range rgbLEDs {
|
||||
go func(rgbLED RGBLED, errorChannel chan<- error, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
err := rgbLED.Run()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}(rgbLED, errorChannel, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
errors := collect.Errors(errorChannel)
|
||||
if len(errors) > 0 {
|
||||
return prittyprint.FormatErrors(errors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sync makes all RGB-LEDs which are passed as parameters light up in their sync
|
||||
// specified color.
|
||||
func Sync(rgbLEDs []RGBLED) error {
|
||||
errorChannel := make(chan error, len(rgbLEDs))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(rgbLEDs))
|
||||
|
||||
for _, rgbLED := range rgbLEDs {
|
||||
go func(rgbLED RGBLED, errorChannel chan<- error, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
err := rgbLED.Sync()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}(rgbLED, errorChannel, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
errors := collect.Errors(errorChannel)
|
||||
if len(errors) > 0 {
|
||||
return prittyprint.FormatErrors(errors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Warn makes all RGB-LEDs which are passed as parameters light up in their
|
||||
// warn specified color.
|
||||
func Warn(rgbLEDs []RGBLED) error {
|
||||
errorChannel := make(chan error, len(rgbLEDs))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(len(rgbLEDs))
|
||||
|
||||
for _, rgbLED := range rgbLEDs {
|
||||
go func(rgbLED RGBLED, errorChannel chan<- error, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
err := rgbLED.Warn()
|
||||
if err != nil {
|
||||
errorChannel <- err
|
||||
}
|
||||
}(rgbLED, errorChannel, wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
errors := collect.Errors(errorChannel)
|
||||
if len(errors) > 0 {
|
||||
return prittyprint.FormatErrors(errors)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,98 +1,91 @@
|
||||
package sensor
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/d2r2/go-bsbmp"
|
||||
"github.com/d2r2/go-i2c"
|
||||
"github.com/d2r2/go-logger"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// BME280 is a sensor to measure humidity and temperature.
|
||||
type BME280 struct {
|
||||
*types.Sensor
|
||||
}
|
||||
|
||||
// GetID returns the sensor id
|
||||
func (s *BME280) GetID() string {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetTicker returns a new ticker, which tick every when the sensor should be read
|
||||
func (s *BME280) GetTicker() *time.Ticker {
|
||||
duration, err := time.ParseDuration(s.TickDuration)
|
||||
if err != nil {
|
||||
duration = time.Minute
|
||||
}
|
||||
return time.NewTicker(duration)
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
// Read measured values
|
||||
func (s *BME280) Read() ([]*types.MeasuredValue, error) {
|
||||
func (bme280 *BME280) Read() ([]*types.MeasuredValue, error) {
|
||||
|
||||
// Lock multiple access
|
||||
bme280.mutex.Lock()
|
||||
defer bme280.mutex.Unlock()
|
||||
|
||||
// Create new connection to i2c-bus on 1 line with address 0x76.
|
||||
// Use i2cdetect utility to find device address over the i2c-bus
|
||||
i2c, err := i2c.NewI2C(*s.I2CAddress, *s.I2CBus)
|
||||
|
||||
i2c, err := i2c.NewI2C(*bme280.I2CAddress, *bme280.I2CBus)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
defer i2c.Close()
|
||||
|
||||
logger.ChangePackageLogLevel("i2c", logger.InfoLevel)
|
||||
// Reduce loglevel
|
||||
for _, pkg := range []string{"bsbmp", "i2c"} {
|
||||
err = logger.ChangePackageLogLevel(pkg, logger.InfoLevel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to change package log level: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sensor, err := bsbmp.NewBMP(bsbmp.BME280, i2c)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.ChangePackageLogLevel("bsbmp", logger.InfoLevel)
|
||||
|
||||
temperatureValue, err := sensor.ReadTemperatureC(bsbmp.ACCURACY_STANDARD)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pressureValue, err := sensor.ReadPressurePa(bsbmp.ACCURACY_STANDARD)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// pressureValueRound := math.Round(float64(pressureValue)/10*0.25) * 10 / 0.25
|
||||
|
||||
_, humidityValue, err := sensor.ReadHumidityRH(bsbmp.ACCURACY_STANDARD)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
measuredValues := []*types.MeasuredValue{
|
||||
&types.MeasuredValue{
|
||||
{
|
||||
ID: uuid.NewV4().String(),
|
||||
Value: float64(humidityValue),
|
||||
ValueType: types.MeasuredValueTypeHumidity,
|
||||
ValueType: "humidity",
|
||||
FromDate: format.FormatedTime(),
|
||||
TillDate: format.FormatedTime(),
|
||||
SensorID: s.ID,
|
||||
SensorID: bme280.ID,
|
||||
},
|
||||
&types.MeasuredValue{
|
||||
{
|
||||
ID: uuid.NewV4().String(),
|
||||
Value: float64(pressureValue),
|
||||
ValueType: types.MeasuredValueTypePressure,
|
||||
ValueType: "pressure",
|
||||
FromDate: format.FormatedTime(),
|
||||
TillDate: format.FormatedTime(),
|
||||
SensorID: s.ID,
|
||||
SensorID: bme280.ID,
|
||||
},
|
||||
&types.MeasuredValue{
|
||||
{
|
||||
ID: uuid.NewV4().String(),
|
||||
Value: float64(temperatureValue),
|
||||
ValueType: types.MeasuredValueTypeTemperature,
|
||||
ValueType: "temperature",
|
||||
FromDate: format.FormatedTime(),
|
||||
TillDate: format.FormatedTime(),
|
||||
SensorID: s.ID,
|
||||
SensorID: bme280.ID,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2,49 +2,35 @@ package sensor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/go-flucky/go-dht"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
// DHT11 is a sensor to measure humidity and temperature.
|
||||
type DHT11 struct {
|
||||
*types.Sensor
|
||||
}
|
||||
|
||||
// GetID returns the sensor id
|
||||
func (s *DHT11) GetID() string {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetTicker returns a new ticker, which tick every when the sensor should be read
|
||||
func (s *DHT11) GetTicker() *time.Ticker {
|
||||
duration, err := time.ParseDuration(s.TickDuration)
|
||||
if err != nil {
|
||||
duration = time.Minute
|
||||
}
|
||||
return time.NewTicker(duration)
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
// Read measured values
|
||||
func (s *DHT11) Read() ([]*types.MeasuredValue, error) {
|
||||
func (dht11 *DHT11) Read() ([]*types.MeasuredValue, error) {
|
||||
|
||||
// Lock multiple access
|
||||
dht11.mutex.Lock()
|
||||
defer dht11.mutex.Unlock()
|
||||
|
||||
err := dht.HostInit()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HostInit error: %v", err)
|
||||
return nil, fmt.Errorf("Failed to initialize periph: %v", err)
|
||||
}
|
||||
|
||||
gpio, err := types.GPIOToString(*s.GPIONumber)
|
||||
dht, err := dht.NewDHT(dht11.GPIONumber, dht.Celsius, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dht, err := dht.NewDHT(gpio, dht.Celsius, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewDHT error: %v", err)
|
||||
return nil, fmt.Errorf("Failed to initialize new DHT11 sensor: %v", err)
|
||||
}
|
||||
|
||||
humidityValue, temperatureValue, err := dht.Read()
|
||||
@ -53,21 +39,21 @@ func (s *DHT11) Read() ([]*types.MeasuredValue, error) {
|
||||
}
|
||||
|
||||
measuredValues := []*types.MeasuredValue{
|
||||
&types.MeasuredValue{
|
||||
{
|
||||
ID: uuid.NewV4().String(),
|
||||
Value: float64(humidityValue),
|
||||
ValueType: types.MeasuredValueTypeHumidity,
|
||||
ValueType: "humidity",
|
||||
FromDate: format.FormatedTime(),
|
||||
TillDate: format.FormatedTime(),
|
||||
SensorID: s.ID,
|
||||
SensorID: dht11.ID,
|
||||
},
|
||||
&types.MeasuredValue{
|
||||
{
|
||||
ID: uuid.NewV4().String(),
|
||||
Value: float64(temperatureValue),
|
||||
ValueType: types.MeasuredValueTypeTemperature,
|
||||
ValueType: "temperature",
|
||||
FromDate: format.FormatedTime(),
|
||||
TillDate: format.FormatedTime(),
|
||||
SensorID: s.ID,
|
||||
SensorID: dht11.ID,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -2,49 +2,35 @@ package sensor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/go-flucky/go-dht"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
// DHT22 is a sensor to measure humidity and temperature.
|
||||
type DHT22 struct {
|
||||
*types.Sensor
|
||||
}
|
||||
|
||||
// GetID returns the sensor id
|
||||
func (s *DHT22) GetID() string {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetTicker returns a new ticker, which tick every when the sensor should be read
|
||||
func (s *DHT22) GetTicker() *time.Ticker {
|
||||
duration, err := time.ParseDuration(s.TickDuration)
|
||||
if err != nil {
|
||||
duration = time.Minute
|
||||
}
|
||||
return time.NewTicker(duration)
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
// Read measured values
|
||||
func (s *DHT22) Read() ([]*types.MeasuredValue, error) {
|
||||
func (dht22 *DHT22) Read() ([]*types.MeasuredValue, error) {
|
||||
|
||||
// Lock multiple access
|
||||
dht22.mutex.Lock()
|
||||
defer dht22.mutex.Unlock()
|
||||
|
||||
err := dht.HostInit()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HostInit error: %v", err)
|
||||
return nil, fmt.Errorf("Failed to initialize periph: %v", err)
|
||||
}
|
||||
|
||||
gpio, err := types.GPIOToString(*s.GPIONumber)
|
||||
dht, err := dht.NewDHT(dht22.GPIONumber, dht.Celsius, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dht, err := dht.NewDHT(gpio, dht.Celsius, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewDHT error: %v", err)
|
||||
return nil, fmt.Errorf("Failed to initialize new DHT22 sensor: %v", err)
|
||||
}
|
||||
|
||||
humidityValue, temperatureValue, err := dht.Read()
|
||||
@ -53,21 +39,21 @@ func (s *DHT22) Read() ([]*types.MeasuredValue, error) {
|
||||
}
|
||||
|
||||
measuredValues := []*types.MeasuredValue{
|
||||
&types.MeasuredValue{
|
||||
{
|
||||
ID: uuid.NewV4().String(),
|
||||
Value: float64(humidityValue),
|
||||
ValueType: types.MeasuredValueTypeHumidity,
|
||||
ValueType: "humidity",
|
||||
FromDate: format.FormatedTime(),
|
||||
TillDate: format.FormatedTime(),
|
||||
SensorID: s.ID,
|
||||
SensorID: dht22.ID,
|
||||
},
|
||||
&types.MeasuredValue{
|
||||
{
|
||||
ID: uuid.NewV4().String(),
|
||||
Value: float64(temperatureValue),
|
||||
ValueType: types.MeasuredValueTypeTemperature,
|
||||
ValueType: "temperature",
|
||||
FromDate: format.FormatedTime(),
|
||||
TillDate: format.FormatedTime(),
|
||||
SensorID: s.ID,
|
||||
SensorID: dht22.ID,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -3,45 +3,42 @@ package sensor
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// DS18B20 is a sensor to measure humidity and temperature.
|
||||
type DS18B20 struct {
|
||||
*types.Sensor
|
||||
}
|
||||
|
||||
// GetID returns the sensor id
|
||||
func (s *DS18B20) GetID() string {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetTicker returns a new ticker, which tick every when the sensor should be read
|
||||
func (s *DS18B20) GetTicker() *time.Ticker {
|
||||
duration, err := time.ParseDuration(s.TickDuration)
|
||||
if err != nil {
|
||||
duration = time.Minute
|
||||
}
|
||||
return time.NewTicker(duration)
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
// Read measured values
|
||||
func (s *DS18B20) Read() ([]*types.MeasuredValue, error) {
|
||||
func (ds18b20 *DS18B20) Read() ([]*types.MeasuredValue, error) {
|
||||
|
||||
if s.WireID == nil {
|
||||
return nil, fmt.Errorf("WireID is not specified for sensor %v", s.Name)
|
||||
// Lock multiple access
|
||||
ds18b20.mutex.Lock()
|
||||
defer ds18b20.mutex.Unlock()
|
||||
|
||||
if ds18b20.WireID == nil {
|
||||
return nil, fmt.Errorf("WireID is not specified")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(filepath.Join("/sys/bus/w1/devices", *s.WireID, "/w1_slave"))
|
||||
socketPath := filepath.Join("/sys/bus/w1/devices", *ds18b20.WireID, "/w1_slave")
|
||||
if _, err := os.Stat(socketPath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("Socket path not found: %v", socketPath)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(socketPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Can not read data from sensor %v", s.Name)
|
||||
return nil, fmt.Errorf("Can not read data from sensor %v", ds18b20.Name)
|
||||
}
|
||||
|
||||
raw := string(data)
|
||||
@ -59,13 +56,13 @@ func (s *DS18B20) Read() ([]*types.MeasuredValue, error) {
|
||||
temperatureValue := c / 1000
|
||||
|
||||
measuredValues := []*types.MeasuredValue{
|
||||
&types.MeasuredValue{
|
||||
{
|
||||
ID: uuid.NewV4().String(),
|
||||
Value: float64(temperatureValue),
|
||||
ValueType: types.MeasuredValueTypeTemperature,
|
||||
ValueType: "temperature",
|
||||
FromDate: format.FormatedTime(),
|
||||
TillDate: format.FormatedTime(),
|
||||
SensorID: s.ID,
|
||||
SensorID: ds18b20.ID,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1,46 +1,40 @@
|
||||
package sensor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
func Read(sensors []Sensor, measuredValueType types.MeasuredValueType) ([]*types.MeasuredValue, error) {
|
||||
var (
|
||||
ErrSensorModelNotMatched = errors.New("Sensor model not matched")
|
||||
)
|
||||
|
||||
type result struct {
|
||||
measuredValues []*types.MeasuredValue
|
||||
err error
|
||||
// New returns a new sensor
|
||||
func New(sensor *types.Sensor) (Sensor, error) {
|
||||
switch sensor.Model {
|
||||
case "BME280":
|
||||
return &BME280{
|
||||
Sensor: sensor,
|
||||
mutex: new(sync.Mutex),
|
||||
}, nil
|
||||
case "DHT11":
|
||||
return &DHT11{
|
||||
Sensor: sensor,
|
||||
mutex: new(sync.Mutex),
|
||||
}, nil
|
||||
case "DHT22":
|
||||
return &DHT22{
|
||||
Sensor: sensor,
|
||||
mutex: new(sync.Mutex),
|
||||
}, nil
|
||||
case "DS18B20":
|
||||
return &DS18B20{
|
||||
Sensor: sensor,
|
||||
mutex: new(sync.Mutex),
|
||||
}, nil
|
||||
default:
|
||||
return nil, ErrSensorModelNotMatched
|
||||
}
|
||||
|
||||
resultChannel := make(chan *result, len(sensors))
|
||||
|
||||
// producers
|
||||
// read measured values
|
||||
for _, s := range sensors {
|
||||
go func(s Sensor) {
|
||||
measuredValues, err := s.Read()
|
||||
resultChannel <- &result{
|
||||
measuredValues: measuredValues,
|
||||
err: err,
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
// consumer
|
||||
measuredValues := make([]*types.MeasuredValue, 0)
|
||||
counter := len(sensors)
|
||||
for {
|
||||
if counter == 0 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case result := <-resultChannel:
|
||||
counter--
|
||||
if result.err != nil {
|
||||
return nil, result.err
|
||||
}
|
||||
measuredValues = append(measuredValues, result.measuredValues...)
|
||||
}
|
||||
}
|
||||
|
||||
return types.SelectMeasuredValues(measuredValueType, measuredValues), nil
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/volker-raschek/go-logger/pkg/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
errorUnsupportedDatabase = errors.New("Unsupported database scheme")
|
||||
flogger = logger.NewSilentLogger()
|
||||
)
|
||||
|
||||
func New(storageEndpoint *url.URL) (Database, error) {
|
||||
switch storageEndpoint.Scheme {
|
||||
case "postgres":
|
||||
newDBO, err := sql.Open(storageEndpoint.Scheme, storageEndpoint.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Postgres{
|
||||
dbo: newDBO,
|
||||
}, nil
|
||||
default:
|
||||
return nil, errorUnsupportedDatabase
|
||||
}
|
||||
}
|
||||
|
||||
func SetLogger(logger logger.Logger) {
|
||||
flogger = logger
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
validStorageEndpoints := []string{
|
||||
"postgres://flucky:flucky@markus-pc.trier.cryptic.systems/postgres?sslmode=disable",
|
||||
}
|
||||
|
||||
unsupportedStorageEndpoints := []string{
|
||||
"html://flucky.cryptic.systems",
|
||||
"oracle://flucky:flucky@example.com/postgres",
|
||||
}
|
||||
|
||||
for _, validStorageEndpoint := range validStorageEndpoints {
|
||||
storageEndpointURL, err := url.Parse(validStorageEndpoint)
|
||||
require.Nil(err)
|
||||
dbo, err := New(storageEndpointURL)
|
||||
require.Nil(err)
|
||||
err = dbo.Close()
|
||||
require.Nil(err)
|
||||
}
|
||||
|
||||
for _, unsupportedStorageEndpoint := range unsupportedStorageEndpoints {
|
||||
storageEndpointURL, err := url.Parse(unsupportedStorageEndpoint)
|
||||
require.Nil(err)
|
||||
_, err = New(storageEndpointURL)
|
||||
require.Equal(errorUnsupportedDatabase, err)
|
||||
}
|
||||
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errorBeginTransaction = errors.New("Failed to start new transaction")
|
||||
errorGetAsset = errors.New("Failed to get asset from go-bindata")
|
||||
errorNoRowsAffected = errors.New("No rows affected")
|
||||
errorRowNotFound = errors.New("Failed to find row by given ID")
|
||||
errorPrepareStatement = errors.New("Failed to prepare sql statement")
|
||||
errorRollbackTransaction = errors.New("Failed to rollback transaction")
|
||||
errorScanRow = errors.New("Failed to scan row")
|
||||
errorStatementExecute = errors.New("Failed to execute statement")
|
||||
errorStatementQuery = errors.New("Failed to query statement")
|
||||
errorUnknownMeasuredValueType = errors.New("Unknown measured value type")
|
||||
)
|
@ -1,48 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
type Database interface {
|
||||
|
||||
// Close DB Connction
|
||||
Close() error
|
||||
|
||||
// Schema
|
||||
Schema(ctx context.Context, version *semver.Version) error
|
||||
|
||||
// Delete
|
||||
DeleteDevices(ctx context.Context, devices []*types.Device) error
|
||||
DeleteInfo(ctx context.Context, key string) error
|
||||
DeleteMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
|
||||
DeleteSensors(ctx context.Context, sensors []*types.Sensor) error
|
||||
|
||||
// Insert
|
||||
InsertDevices(ctx context.Context, devices []*types.Device) error
|
||||
InsertInfo(ctx context.Context, key string, value string) error
|
||||
InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
|
||||
InsertSensors(ctx context.Context, sensors []*types.Sensor) error
|
||||
|
||||
// Select
|
||||
SelectDeviceByID(ctx context.Context, id string) (*types.Device, error)
|
||||
SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error)
|
||||
SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error)
|
||||
SelectInfo(ctx context.Context, key string) (string, error)
|
||||
SelectMeasuredValues(ctx context.Context) ([]*types.MeasuredValue, error)
|
||||
SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error)
|
||||
SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error)
|
||||
SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
|
||||
SelectSensorByID(ctx context.Context, id string) (*types.Sensor, error)
|
||||
SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error)
|
||||
SelectTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
|
||||
|
||||
// Update
|
||||
UpdateDevices(ctx context.Context, devices []*types.Device) error
|
||||
UpdateInfo(ctx context.Context, key string, value string) error
|
||||
UpdateMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
|
||||
UpdateSensors(ctx context.Context, sensots []*types.Sensor) error
|
||||
}
|
@ -1,713 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
|
||||
// PostgreSQL lib
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
var (
|
||||
postgresAssetPath = "pkg/storage/db/sql/psql"
|
||||
)
|
||||
|
||||
// Postgres provide functions to interact with a postgres database
|
||||
type Postgres struct {
|
||||
dbo *sql.DB
|
||||
}
|
||||
|
||||
// Close the database connection
|
||||
func (p *Postgres) Close() error {
|
||||
return p.dbo.Close()
|
||||
}
|
||||
|
||||
// Schema create or updates the database schema to a given version. Normally the
|
||||
// version is the same as the flucky binary version.
|
||||
func (p *Postgres) Schema(ctx context.Context, version *semver.Version) error {
|
||||
schemaFunc := func(ctx context.Context, fromVersion *semver.Version, toVersion *semver.Version) error {
|
||||
|
||||
assetPath := fmt.Sprintf("%v/schema", postgresAssetPath)
|
||||
sqlAssetFiles, err := AssetDir(assetPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not restore asset directory %v: %v", assetPath, err)
|
||||
}
|
||||
|
||||
postgreSQLVersionChanges := make(map[*semver.Version]string, 0)
|
||||
postgreSQLVersions := make([]*semver.Version, len(sqlAssetFiles))
|
||||
|
||||
for i, sqlAssetFile := range sqlAssetFiles {
|
||||
fileSemVersion, err := semver.NewVersion(strings.ReplaceAll(sqlAssetFile, ".sql", ""))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not create semantic version from file asset %v: %v", sqlAssetFile, err)
|
||||
}
|
||||
postgreSQLVersionChanges[fileSemVersion] = sqlAssetFile
|
||||
postgreSQLVersions[i] = fileSemVersion
|
||||
}
|
||||
sort.Sort(semver.Collection(postgreSQLVersions))
|
||||
|
||||
for i, postgreSQLVersion := range postgreSQLVersions {
|
||||
if fromVersion != nil {
|
||||
if postgreSQLVersion.LessThan(fromVersion) || postgreSQLVersion.Equal(fromVersion) {
|
||||
flogger.Debug("SKIP: PostgreSQL schema version '%v' is less or eqal then the local version changes '%v'", postgreSQLVersion.String(), fromVersion.String())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
asset := postgreSQLVersionChanges[postgreSQLVersion]
|
||||
queryBytes, err := Asset(filepath.Join(assetPath, asset))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not restore asset %v, %v", asset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
if _, err := p.dbo.ExecContext(ctx, query); err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
if err := p.InsertInfo(ctx, "version", postgreSQLVersion.String()); err != nil {
|
||||
return fmt.Errorf("Can not insert version %v into info table: %v", postgreSQLVersion.String(), err)
|
||||
}
|
||||
} else {
|
||||
if err := p.UpdateInfo(ctx, "version", postgreSQLVersion.String()); err != nil {
|
||||
return fmt.Errorf("Can not update version %v into info table: %v", postgreSQLVersion.String(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
dbVersion, err := p.SelectInfo(ctx, "version")
|
||||
if err != nil {
|
||||
// can not select version from database, maybe the schema is not initialize
|
||||
// create db schema for the current flucky version
|
||||
return schemaFunc(ctx, nil, version)
|
||||
} else {
|
||||
fromVersion, err := semver.NewVersion(dbVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not create semantic version from database entry %v: %v", dbVersion, err)
|
||||
}
|
||||
return schemaFunc(ctx, fromVersion, version)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteDevices delete recursively all spicified devices, including sensors and
|
||||
// all measured values
|
||||
func (p *Postgres) DeleteDevices(ctx context.Context, devices []*types.Device) error {
|
||||
asset := fmt.Sprintf("%v/deleteDevice.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, device := range devices {
|
||||
_, err := stmt.ExecContext(ctx, &device.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSensors delete recusively all spicified sensors, including all measured
|
||||
// values
|
||||
func (p *Postgres) DeleteSensors(ctx context.Context, sensors []*types.Sensor) error {
|
||||
asset := fmt.Sprintf("%v/deleteSensor.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, sensor := range sensors {
|
||||
_, err := stmt.ExecContext(ctx, &sensor.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteInfo delete a key with his value
|
||||
func (p *Postgres) DeleteInfo(ctx context.Context, key string) error {
|
||||
asset := fmt.Sprintf("%v/deleteInfo.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.ExecContext(ctx, &key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMeasuredValues delete all spicified measured values
|
||||
func (p *Postgres) DeleteMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
|
||||
|
||||
deleteMeasuredValue := func(ctx context.Context, query string, measuredValues []*types.MeasuredValue) error {
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
_, err := stmt.ExecContext(ctx, &measuredValue.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
sortedMeasuredValueTypes := make(map[types.MeasuredValueType][]*types.MeasuredValue)
|
||||
for _, measuredValue := range measuredValues {
|
||||
if _, ok := sortedMeasuredValueTypes[measuredValue.ValueType]; !ok {
|
||||
sortedMeasuredValueTypes[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
|
||||
}
|
||||
sortedMeasuredValueTypes[measuredValue.ValueType] = append(sortedMeasuredValueTypes[measuredValue.ValueType], measuredValue)
|
||||
}
|
||||
|
||||
assetFunc := func(queryFile string) (string, error) {
|
||||
queryBytes, err := Asset(queryFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
return string(queryBytes), nil
|
||||
}
|
||||
|
||||
for measuredValueType, sortedMeasuredValues := range sortedMeasuredValueTypes {
|
||||
switch measuredValueType {
|
||||
case types.MeasuredValueTypeHumidity:
|
||||
query, err := assetFunc(fmt.Sprintf("%v/deleteHumidity.sql", postgresAssetPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.MeasuredValueTypePressure:
|
||||
query, err := assetFunc(fmt.Sprintf("%v/deletePressure.sql", postgresAssetPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.MeasuredValueTypeTemperature:
|
||||
query, err := assetFunc(fmt.Sprintf("%v/deleteTemperature.sql", postgresAssetPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertDevices insert all specified devices into the database
|
||||
func (p *Postgres) InsertDevices(ctx context.Context, devices []*types.Device) error {
|
||||
asset := fmt.Sprintf("%v/insertDevice.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, device := range devices {
|
||||
_, err := stmt.ExecContext(ctx, &device.ID, &device.Name, &device.Location, &device.CreationDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertInfo insert into the database additional informations, based on a key value syntax
|
||||
func (p *Postgres) InsertInfo(ctx context.Context, key string, value string) error {
|
||||
asset := fmt.Sprintf("%v/insertInfo.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.ExecContext(ctx, key, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertMeasuredValues insert all specified measured values into the database
|
||||
func (p *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
|
||||
|
||||
sortedMeasuredValueTypes := make(map[types.MeasuredValueType][]*types.MeasuredValue)
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
if _, ok := sortedMeasuredValueTypes[measuredValue.ValueType]; !ok {
|
||||
sortedMeasuredValueTypes[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
|
||||
}
|
||||
sortedMeasuredValueTypes[measuredValue.ValueType] = append(sortedMeasuredValueTypes[measuredValue.ValueType], measuredValue)
|
||||
}
|
||||
|
||||
for measuredValueType, sortedMeasuredValues := range sortedMeasuredValueTypes {
|
||||
switch measuredValueType {
|
||||
case types.MeasuredValueTypeHumidity:
|
||||
if err := p.insertHumidity(ctx, sortedMeasuredValues); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.MeasuredValueTypePressure:
|
||||
if err := p.insertPressure(ctx, sortedMeasuredValues); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.MeasuredValueTypeTemperature:
|
||||
if err := p.insertTemperature(ctx, sortedMeasuredValues); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Postgres) insertHumidity(ctx context.Context, measuredValues []*types.MeasuredValue) error {
|
||||
|
||||
asset := fmt.Sprintf("%v/insertHumidity.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
|
||||
if measuredValue.ValueType != types.MeasuredValueTypeHumidity {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Postgres) insertPressure(ctx context.Context, measuredValues []*types.MeasuredValue) error {
|
||||
|
||||
asset := fmt.Sprintf("%v/insertPressure.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
|
||||
if measuredValue.ValueType != types.MeasuredValueTypePressure {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: Measured value id %v: %v", errorStatementExecute, measuredValue.ID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Postgres) insertTemperature(ctx context.Context, measuredValues []*types.MeasuredValue) error {
|
||||
|
||||
asset := fmt.Sprintf("%v/insertTemperature.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
|
||||
if measuredValue.ValueType != types.MeasuredValueTypeTemperature {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: Measured value id %v: %v", errorStatementExecute, measuredValue.ID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertSensors insert all specified sensors into the database
|
||||
func (p *Postgres) InsertSensors(ctx context.Context, sensors []*types.Sensor) error {
|
||||
|
||||
asset := fmt.Sprintf("%v/insertSensor.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, sensor := range sensors {
|
||||
|
||||
_, err := stmt.ExecContext(ctx, &sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectDeviceByID returns a device by his ID
|
||||
func (p *Postgres) SelectDeviceByID(ctx context.Context, id string) (*types.Device, error) {
|
||||
asset := fmt.Sprintf("%v/selectDeviceByID.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
|
||||
row := stmt.QueryRowContext(ctx, id)
|
||||
if row == nil {
|
||||
return nil, errorRowNotFound
|
||||
}
|
||||
|
||||
device := new(types.Device)
|
||||
err = row.Scan(&device.ID, &device.Name, &device.Location, &device.CreationDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorScanRow, err)
|
||||
}
|
||||
|
||||
return device, nil
|
||||
}
|
||||
|
||||
// SelectInfo returns the value of a key stored in the database
|
||||
func (p *Postgres) SelectInfo(ctx context.Context, key string) (string, error) {
|
||||
asset := fmt.Sprintf("%v/selectInfo.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
|
||||
row := stmt.QueryRowContext(ctx, key)
|
||||
if row == nil {
|
||||
return "", errorRowNotFound
|
||||
}
|
||||
|
||||
value := ""
|
||||
err = row.Scan(&value)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%v: %v", errorScanRow, err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// SelectHumidities returns humidity values
|
||||
func (p *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
|
||||
queryFile := fmt.Sprintf("%v/selectHumidities.sql", postgresAssetPath)
|
||||
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeHumidity, queryFile, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return measuredValues, nil
|
||||
}
|
||||
|
||||
// SelectHumidityByID returns a humidity value by his ID
|
||||
func (p *Postgres) SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
|
||||
queryFile := fmt.Sprintf("%v/selectHumidityByID.sql", postgresAssetPath)
|
||||
args := []interface{}{id}
|
||||
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeHumidity, queryFile, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(measuredValues) == 0 {
|
||||
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
|
||||
}
|
||||
|
||||
return measuredValues[0], nil
|
||||
}
|
||||
|
||||
// SelectMeasuredValues returns all measured values about all diffferent value
|
||||
// types
|
||||
func (p *Postgres) SelectMeasuredValues(ctx context.Context) ([]*types.MeasuredValue, error) {
|
||||
measuredValues := make([]*types.MeasuredValue, 0)
|
||||
|
||||
// MeasuredValue query functions
|
||||
queryFunctions := []func(ctx context.Context) ([]*types.MeasuredValue, error){
|
||||
p.SelectHumidities,
|
||||
p.SelectPressures,
|
||||
p.SelectTemperatures,
|
||||
}
|
||||
|
||||
// Execute query functions
|
||||
for _, queryFunction := range queryFunctions {
|
||||
queriedMeasuredValues, err := queryFunction(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
measuredValues = append(measuredValues, queriedMeasuredValues...)
|
||||
}
|
||||
return measuredValues, nil
|
||||
}
|
||||
|
||||
// SelectMeasuredValuesByIDAndType returns a measured value by his ID and type
|
||||
func (p *Postgres) SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error) {
|
||||
switch valueType {
|
||||
case types.MeasuredValueTypeHumidity:
|
||||
return p.SelectHumidityByID(ctx, id)
|
||||
case types.MeasuredValueTypePressure:
|
||||
return p.SelectPressureByID(ctx, id)
|
||||
case types.MeasuredValueTypeTemperature:
|
||||
return p.SelectTemperatureByID(ctx, id)
|
||||
default:
|
||||
return nil, fmt.Errorf("%v: %v", errorUnknownMeasuredValueType, valueType)
|
||||
}
|
||||
}
|
||||
|
||||
// SelectPressures returns pressure values
|
||||
func (p *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
|
||||
queryFile := fmt.Sprintf("%v/selectPressures.sql", postgresAssetPath)
|
||||
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypePressure, queryFile, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return measuredValues, nil
|
||||
}
|
||||
|
||||
// SelectPressureByID returns a pressure value by his ID
|
||||
func (p *Postgres) SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
|
||||
queryFile := fmt.Sprintf("%v/selectPressureByID.sql", postgresAssetPath)
|
||||
args := []interface{}{id}
|
||||
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypePressure, queryFile, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(measuredValues) == 0 {
|
||||
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
|
||||
}
|
||||
|
||||
return measuredValues[0], nil
|
||||
}
|
||||
|
||||
// SelectSensorByID returns a sensor by his ID
|
||||
func (p *Postgres) SelectSensorByID(ctx context.Context, id string) (*types.Sensor, error) {
|
||||
asset := fmt.Sprintf("%v/selectSensorByID.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
|
||||
row := stmt.QueryRowContext(ctx, id)
|
||||
if row == nil {
|
||||
return nil, errorRowNotFound
|
||||
}
|
||||
|
||||
sensor := new(types.Sensor)
|
||||
err = row.Scan(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorScanRow, err)
|
||||
}
|
||||
|
||||
return sensor, nil
|
||||
}
|
||||
|
||||
// SelectTemperatures returns temperature values
|
||||
func (p *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
|
||||
queryFile := fmt.Sprintf("%v/selectTemperatures.sql", postgresAssetPath)
|
||||
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeTemperature, queryFile, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return measuredValues, nil
|
||||
}
|
||||
|
||||
// SelectTemperatureByID returns a temperature value by his ID
|
||||
func (p *Postgres) SelectTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
|
||||
queryFile := fmt.Sprintf("%v/selectTemperatureByID.sql", postgresAssetPath)
|
||||
args := []interface{}{id}
|
||||
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeTemperature, queryFile, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(measuredValues) == 0 {
|
||||
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
|
||||
}
|
||||
|
||||
return measuredValues[0], nil
|
||||
}
|
||||
|
||||
func (p *Postgres) selectMeasuredValues(ctx context.Context, measuredValueType types.MeasuredValueType, queryFile string, queryArgs []interface{}) ([]*types.MeasuredValue, error) {
|
||||
queryBytes, err := Asset(queryFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
|
||||
rows, err := stmt.QueryContext(ctx, queryArgs...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorStatementQuery, err)
|
||||
}
|
||||
|
||||
measuredValues := make([]*types.MeasuredValue, 0)
|
||||
for rows.Next() {
|
||||
measuredValue := new(types.MeasuredValue)
|
||||
measuredValue.ValueType = measuredValueType
|
||||
rows.Scan(&measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
|
||||
measuredValues = append(measuredValues, measuredValue)
|
||||
}
|
||||
|
||||
return measuredValues, nil
|
||||
}
|
||||
|
||||
// UpdateDevices updates all specified devices into the database
|
||||
func (p *Postgres) UpdateDevices(ctx context.Context, devices []*types.Device) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateInfo updates the value which is stored to a key in the database
|
||||
func (p *Postgres) UpdateInfo(ctx context.Context, key string, value string) error {
|
||||
asset := fmt.Sprintf("%v/updateInfo.sql", postgresAssetPath)
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorGetAsset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := p.dbo.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
res, err := stmt.ExecContext(ctx, key, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorStatementExecute, err)
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affected == 0 {
|
||||
return errorNoRowsAffected
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateMeasuredValues updates the measured values which are stored in the database
|
||||
func (p *Postgres) UpdateMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSensors updates the sensors which are stored in the database
|
||||
func (p *Postgres) UpdateSensors(ctx context.Context, sensots []*types.Sensor) error {
|
||||
return nil
|
||||
}
|
@ -1,397 +0,0 @@
|
||||
package db_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/volker-raschek/flucky/pkg/storage/db"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/volker-raschek/flucky/test/goldenfiles"
|
||||
)
|
||||
|
||||
type test struct {
|
||||
Name string
|
||||
Test func(*testing.T)
|
||||
}
|
||||
|
||||
var (
|
||||
database db.Database
|
||||
|
||||
postgresContainerImage string = "docker.io/postgres/postgres"
|
||||
|
||||
storageEndpointString string = "postgres://flucky:flucky@markus-pc.trier.cryptic.systems/postgres?sslmode=disable"
|
||||
|
||||
goldenDevicesFilePath string = "test/goldenfiles/json/goldenDevices.json"
|
||||
goldenSensorsFilePath string = "test/goldenfiles/json/goldenSensors.json"
|
||||
goldenMeasuredValuesFilePath string = "test/goldenfiles/json/goldenMeasuredValuesUncompressedRounded.json"
|
||||
goldenPressuresFilePath string = "test/goldenfiles/json/goldenPressuresUncompressedRounded.json"
|
||||
goldenHumiditiesFilePath string = "test/goldenfiles/json/goldenHumiditiesUncompressedRounded.json"
|
||||
goldenTemperaturesFilePath string = "test/goldenfiles/json/goldenTemperaturesUncompressedRounded.json"
|
||||
|
||||
goldenDevices []*types.Device
|
||||
goldenSensors []*types.Sensor
|
||||
goldenMeasuredValues []*types.MeasuredValue
|
||||
goldenPressures []*types.MeasuredValue
|
||||
goldenHumidites []*types.MeasuredValue
|
||||
goldenTemperatures []*types.MeasuredValue
|
||||
)
|
||||
|
||||
func load(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
d, err := goldenfiles.GetGoldenDevices(goldenDevicesFilePath)
|
||||
require.NoError(err)
|
||||
goldenDevices = d
|
||||
|
||||
s, err := goldenfiles.GetGoldenSensors(goldenSensorsFilePath)
|
||||
require.NoError(err)
|
||||
goldenSensors = s
|
||||
|
||||
hum, err := goldenfiles.GetGoldenMeasuredValues(goldenHumiditiesFilePath)
|
||||
require.NoError(err)
|
||||
goldenHumidites = hum
|
||||
|
||||
mv, err := goldenfiles.GetGoldenMeasuredValues(goldenMeasuredValuesFilePath)
|
||||
require.NoError(err)
|
||||
goldenMeasuredValues = mv
|
||||
|
||||
pres, err := goldenfiles.GetGoldenMeasuredValues(goldenPressuresFilePath)
|
||||
require.NoError(err)
|
||||
goldenPressures = pres
|
||||
|
||||
temp, err := goldenfiles.GetGoldenMeasuredValues(goldenTemperaturesFilePath)
|
||||
require.NoError(err)
|
||||
goldenTemperatures = temp
|
||||
|
||||
}
|
||||
|
||||
func TestPostgres(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
load(t)
|
||||
|
||||
storageEndpoint, err := url.Parse(storageEndpointString)
|
||||
require.Nil(err)
|
||||
|
||||
db, err := db.New(storageEndpoint)
|
||||
database = db
|
||||
require.Nil(err)
|
||||
|
||||
tests := []*test{
|
||||
&test{
|
||||
Name: "schema",
|
||||
Test: testSchemaCreate,
|
||||
},
|
||||
&test{
|
||||
Name: "insertInfo",
|
||||
Test: testInsertInfo,
|
||||
},
|
||||
&test{
|
||||
Name: "insertDevices",
|
||||
Test: testInsertDevices,
|
||||
},
|
||||
&test{
|
||||
Name: "insertSensors",
|
||||
Test: testInsertSensors,
|
||||
},
|
||||
&test{
|
||||
Name: "insertHumidity",
|
||||
Test: testInsertHumidity,
|
||||
},
|
||||
&test{
|
||||
Name: "insertPressure",
|
||||
Test: testInsertPressure,
|
||||
},
|
||||
&test{
|
||||
Name: "insertTemperatures",
|
||||
Test: testInsertTemperatures,
|
||||
},
|
||||
&test{
|
||||
Name: "selectHumidities",
|
||||
Test: testSelectHumidities,
|
||||
},
|
||||
&test{
|
||||
Name: "selectPressures",
|
||||
Test: testSelectPressures,
|
||||
},
|
||||
&test{
|
||||
Name: "selectTemperatures",
|
||||
Test: testSelectTemperatures,
|
||||
},
|
||||
// &test{
|
||||
// Name: "selectMeasuredValues",
|
||||
// Test: testSelectMeasuredValues,
|
||||
// },
|
||||
&test{
|
||||
Name: "deleteHumidities",
|
||||
Test: testDeleteHumidity,
|
||||
},
|
||||
&test{
|
||||
Name: "deleteInfo",
|
||||
Test: testDeleteInfo,
|
||||
},
|
||||
&test{
|
||||
Name: "deletePressures",
|
||||
Test: testDeletePressures,
|
||||
},
|
||||
&test{
|
||||
Name: "deleteTemperatures",
|
||||
Test: testDeleteTemperatures,
|
||||
},
|
||||
&test{
|
||||
Name: "insertMeasuredValues",
|
||||
Test: testInsertMeasuredValues,
|
||||
},
|
||||
&test{
|
||||
Name: "deleteMeasuredValues",
|
||||
Test: testDeleteMeasuredValues,
|
||||
},
|
||||
&test{
|
||||
Name: "deleteSensors",
|
||||
Test: testDeleteSensors,
|
||||
},
|
||||
&test{
|
||||
Name: "deleteDevices",
|
||||
Test: testDeleteDevices,
|
||||
},
|
||||
&test{
|
||||
Name: "updateInfo",
|
||||
Test: testUpdateInfo,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, test.Test)
|
||||
}
|
||||
}
|
||||
|
||||
func testSchemaCreate(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
homePath := "pkg/storage/db/sql/psql/schema"
|
||||
sqlAssetFiles, err := db.AssetDir(homePath)
|
||||
require.NoError(err)
|
||||
|
||||
ctx := context.Background()
|
||||
for _, sqlAssetFile := range sqlAssetFiles {
|
||||
fromVersion, err := semver.NewVersion(strings.ReplaceAll(sqlAssetFile, ".sql", ""))
|
||||
require.NoError(err)
|
||||
err = database.Schema(ctx, fromVersion)
|
||||
require.NoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testInsertDevices(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.InsertDevices(ctx, goldenDevices)
|
||||
require.NoError(err)
|
||||
for _, goldenDevice := range goldenDevices {
|
||||
testDevice, err := database.SelectDeviceByID(ctx, goldenDevice.ID)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, goldenDevice, testDevice)
|
||||
}
|
||||
}
|
||||
|
||||
func testInsertSensors(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.InsertSensors(ctx, goldenSensors)
|
||||
require.NoError(err)
|
||||
for _, goldenSensor := range goldenSensors {
|
||||
testSensor, err := database.SelectSensorByID(ctx, goldenSensor.ID)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, goldenSensor, testSensor)
|
||||
}
|
||||
}
|
||||
|
||||
func testInsertHumidity(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.InsertMeasuredValues(ctx, goldenHumidites)
|
||||
require.NoError(err)
|
||||
for _, goldenHumidity := range goldenHumidites {
|
||||
testHumidity, err := database.SelectHumidityByID(ctx, goldenHumidity.ID)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{testHumidity}, []*types.MeasuredValue{testHumidity})
|
||||
}
|
||||
}
|
||||
|
||||
func testInsertInfo(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.InsertInfo(ctx, "test", "value")
|
||||
require.NoError(err)
|
||||
|
||||
value, err := database.SelectInfo(ctx, "test")
|
||||
require.NoError(err)
|
||||
|
||||
require.Equal("value", value)
|
||||
}
|
||||
|
||||
func testInsertMeasuredValues(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.InsertMeasuredValues(ctx, goldenMeasuredValues)
|
||||
require.NoError(err)
|
||||
for _, goldenMeasuredValue := range goldenMeasuredValues {
|
||||
testMeasuredValue, err := database.SelectMeasuredValuesByIDAndType(ctx, goldenMeasuredValue.ID, goldenMeasuredValue.ValueType)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{goldenMeasuredValue}, []*types.MeasuredValue{testMeasuredValue})
|
||||
}
|
||||
}
|
||||
|
||||
func testInsertPressure(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.InsertMeasuredValues(ctx, goldenPressures)
|
||||
require.NoError(err)
|
||||
for _, goldenPressure := range goldenPressures {
|
||||
testPressure, err := database.SelectPressureByID(ctx, goldenPressure.ID)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{testPressure}, []*types.MeasuredValue{testPressure})
|
||||
}
|
||||
}
|
||||
|
||||
func testInsertTemperatures(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.InsertMeasuredValues(ctx, goldenTemperatures)
|
||||
require.NoError(err)
|
||||
for _, goldenTemperature := range goldenTemperatures {
|
||||
testTemperature, err := database.SelectTemperatureByID(ctx, goldenTemperature.ID)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{goldenTemperature}, []*types.MeasuredValue{testTemperature})
|
||||
}
|
||||
}
|
||||
|
||||
func testSelectHumidities(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
humidities, err := database.SelectHumidities(ctx)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, goldenHumidites, humidities)
|
||||
}
|
||||
|
||||
func testSelectMeasuredValues(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
measuredValues, err := database.SelectMeasuredValues(ctx)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, goldenMeasuredValues, measuredValues)
|
||||
}
|
||||
|
||||
func testSelectPressures(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
pressures, err := database.SelectPressures(ctx)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, goldenPressures, pressures)
|
||||
}
|
||||
|
||||
func testSelectTemperatures(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
temperatures, err := database.SelectTemperatures(ctx)
|
||||
require.NoError(err)
|
||||
goldenfiles.CompareMeasuredValues(t, goldenTemperatures, temperatures)
|
||||
}
|
||||
|
||||
func testDeleteDevices(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.DeleteDevices(ctx, goldenDevices)
|
||||
require.NoError(err)
|
||||
for _, goldenDevice := range goldenDevices {
|
||||
_, err := database.SelectDeviceByID(ctx, goldenDevice.ID)
|
||||
require.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDeleteSensors(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.DeleteSensors(ctx, goldenSensors)
|
||||
require.NoError(err)
|
||||
for _, goldenSensor := range goldenSensors {
|
||||
_, err := database.SelectDeviceByID(ctx, goldenSensor.ID)
|
||||
require.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDeleteHumidity(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.DeleteMeasuredValues(ctx, goldenHumidites)
|
||||
require.NoError(err)
|
||||
for _, goldenHumidity := range goldenHumidites {
|
||||
_, err := database.SelectHumidityByID(ctx, goldenHumidity.ID)
|
||||
require.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDeleteInfo(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.DeleteInfo(ctx, "test")
|
||||
require.NoError(err)
|
||||
_, err = database.SelectInfo(ctx, "test")
|
||||
require.Error(err)
|
||||
}
|
||||
|
||||
func testDeleteMeasuredValues(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.DeleteMeasuredValues(ctx, goldenMeasuredValues)
|
||||
require.NoError(err)
|
||||
for _, goldenMeasuredValue := range goldenMeasuredValues {
|
||||
_, err := database.SelectPressureByID(ctx, goldenMeasuredValue.ID)
|
||||
require.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDeletePressures(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.DeleteMeasuredValues(ctx, goldenPressures)
|
||||
require.NoError(err)
|
||||
for _, goldenPressure := range goldenPressures {
|
||||
_, err := database.SelectPressureByID(ctx, goldenPressure.ID)
|
||||
require.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testDeleteTemperatures(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
err := database.DeleteMeasuredValues(ctx, goldenTemperatures)
|
||||
require.NoError(err)
|
||||
for _, goldenTemperature := range goldenTemperatures {
|
||||
_, err := database.SelectTemperatureByID(ctx, goldenTemperature.ID)
|
||||
require.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testUpdateInfo(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// VALID
|
||||
err := database.InsertInfo(ctx, "key", "value")
|
||||
require.NoError(err)
|
||||
err = database.UpdateInfo(ctx, "key", "value2")
|
||||
require.NoError(err)
|
||||
value, err := database.SelectInfo(ctx, "key")
|
||||
require.NoError(err)
|
||||
require.Equal("value2", value)
|
||||
err = database.DeleteInfo(ctx, "key")
|
||||
require.NoError(err)
|
||||
|
||||
// INVALID
|
||||
err = database.UpdateInfo(ctx, "key2", "value")
|
||||
require.Error(err)
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
DELETE FROM devices
|
||||
WHERE device_id = $1;
|
@ -1,2 +0,0 @@
|
||||
DELETE FROM humidities
|
||||
WHERE humidity_id = $1;
|
@ -1,2 +0,0 @@
|
||||
DELETE FROM info
|
||||
WHERE key = $1;
|
@ -1,2 +0,0 @@
|
||||
DELETE FROM pressures
|
||||
WHERE pressure_id = $1;
|
@ -1,2 +0,0 @@
|
||||
DELETE FROM sensors
|
||||
WHERE sensor_id = $1;
|
@ -1,2 +0,0 @@
|
||||
DELETE FROM temperatures
|
||||
WHERE temperature_id = $1;
|
@ -1,2 +0,0 @@
|
||||
INSERT INTO info (key, value)
|
||||
VALUES ($1, $2);
|
@ -1,152 +0,0 @@
|
||||
DROP TABLE IF EXISTS devices CASCADE;
|
||||
DROP TABLE IF EXISTS sensors CASCADE;
|
||||
DROP TABLE IF EXISTS humidities CASCADE;
|
||||
DROP TABLE IF EXISTS pressures CASCADE;
|
||||
DROP TABLE IF EXISTS temperatures CASCADE;
|
||||
DROP TABLE IF EXISTS info CASCADE;
|
||||
|
||||
|
||||
-- +----------------------------------------+
|
||||
-- | TABLES |
|
||||
-- +----------------------------------------+
|
||||
CREATE TABLE IF NOT EXISTS devices(
|
||||
device_id CHAR(36) CONSTRAINT pk_devices PRIMARY KEY,
|
||||
device_name VARCHAR(32) NOT NULL,
|
||||
device_location VARCHAR(32),
|
||||
device_last_contact TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sensors (
|
||||
sensor_id CHAR(36) CONSTRAINT pk_sensors PRIMARY KEY,
|
||||
sensor_name VARCHAR(32) NOT NULL,
|
||||
sensor_location VARCHAR(32) NOT NULL,
|
||||
wire_id VARCHAR(15),
|
||||
i2c_bus VARCHAR(255),
|
||||
i2c_address VARCHAR(12),
|
||||
gpio_number VARCHAR(6),
|
||||
sensor_model VARCHAR(16) NOT NULL,
|
||||
sensor_enabled BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
sensor_last_contact TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
device_id CHAR(36),
|
||||
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS humidities (
|
||||
humidity_id CHAR(36) CONSTRAINT pk_humidities PRIMARY KEY,
|
||||
humidity_value NUMERIC(9,3) NOT NULL,
|
||||
humidity_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
humidity_till_date TIMESTAMP WITH TIME ZONE,
|
||||
sensor_id CHAR(36),
|
||||
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_date TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pressures (
|
||||
pressure_id CHAR(36) CONSTRAINT pk_pressures PRIMARY KEY,
|
||||
pressure_value NUMERIC(10,3) NOT NULL,
|
||||
pressure_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
pressure_till_date TIMESTAMP WITH TIME ZONE,
|
||||
sensor_id CHAR(36),
|
||||
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_date TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS temperatures (
|
||||
temperature_id CHAR(36) CONSTRAINT pk_temperatures PRIMARY KEY,
|
||||
temperature_value NUMERIC(5,3) NOT NULL,
|
||||
temperature_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
temperature_till_date TIMESTAMP WITH TIME ZONE,
|
||||
sensor_id CHAR(36),
|
||||
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_date TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS info (
|
||||
key VARCHAR(32) CONSTRAINT pk_info PRIMARY KEY,
|
||||
value VARCHAR(32) NOT NULL
|
||||
);
|
||||
|
||||
-- +----------------------------------------+
|
||||
-- | FOREIGN-KEYS |
|
||||
-- +----------------------------------------+
|
||||
ALTER TABLE sensors
|
||||
ADD FOREIGN KEY (device_id)
|
||||
REFERENCES devices(device_id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE humidities
|
||||
ADD FOREIGN KEY (sensor_id)
|
||||
REFERENCES sensors(sensor_id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE pressures
|
||||
ADD FOREIGN KEY (sensor_id)
|
||||
REFERENCES sensors(sensor_id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE temperatures
|
||||
ADD FOREIGN KEY (sensor_id)
|
||||
REFERENCES sensors(sensor_id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
-- +----------------------------------------+
|
||||
-- | Trigger-Functions |
|
||||
-- +----------------------------------------+
|
||||
CREATE OR REPLACE FUNCTION device_last_contact()
|
||||
RETURNS trigger AS
|
||||
$BODY$
|
||||
BEGIN
|
||||
UPDATE devices
|
||||
SET device_last_contact = CURRENT_TIMESTAMP
|
||||
WHERE device_id = NEW.device_id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$BODY$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION sensor_last_contact()
|
||||
RETURNS trigger AS
|
||||
$BODY$
|
||||
BEGIN
|
||||
UPDATE sensors
|
||||
SET sensor_last_contact = CURRENT_TIMESTAMP,
|
||||
sensor_enabled = true
|
||||
WHERE sensor_id = NEW.sensor_id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$BODY$ LANGUAGE plpgsql;
|
||||
|
||||
-- +----------------------------------------+
|
||||
-- | Trigger |
|
||||
-- +----------------------------------------+
|
||||
DROP TRIGGER IF EXISTS ai_humidities ON humidities;
|
||||
DROP TRIGGER IF EXISTS ai_pressure ON pressures;
|
||||
DROP TRIGGER IF EXISTS ai_temperatures ON temperatures;
|
||||
|
||||
CREATE TRIGGER au_sensors
|
||||
AFTER UPDATE
|
||||
ON sensors
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE device_last_contact();
|
||||
|
||||
CREATE TRIGGER ai_humidities
|
||||
AFTER INSERT
|
||||
ON humidities
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE sensor_last_contact();
|
||||
|
||||
CREATE TRIGGER ai_pressures
|
||||
AFTER INSERT
|
||||
ON pressures
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE sensor_last_contact();
|
||||
|
||||
CREATE TRIGGER ai_temperatures
|
||||
AFTER INSERT
|
||||
ON temperatures
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE sensor_last_contact();
|
@ -1,3 +0,0 @@
|
||||
ALTER TABLE humidities ALTER COLUMN creation_date DROP NOT NULL;
|
||||
ALTER TABLE pressures ALTER COLUMN creation_date DROP NOT NULL;
|
||||
ALTER TABLE temperatures ALTER COLUMN creation_date DROP NOT NULL;
|
@ -1,10 +0,0 @@
|
||||
SELECT
|
||||
humidity_id,
|
||||
humidity_value,
|
||||
humidity_from_date,
|
||||
humidity_till_date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
FROM
|
||||
humidities;
|
@ -1,12 +0,0 @@
|
||||
SELECT
|
||||
humidity_id,
|
||||
humidity_value,
|
||||
humidity_from_date,
|
||||
humidity_till_date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
FROM
|
||||
humidities
|
||||
WHERE
|
||||
humidity_id = $1;
|
@ -1,3 +0,0 @@
|
||||
SELECT value
|
||||
FROM info
|
||||
WHERE key = $1;
|
@ -1,12 +0,0 @@
|
||||
SELECT
|
||||
pressure_id,
|
||||
pressure_value,
|
||||
pressure_from_date,
|
||||
pressure_till_date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
FROM
|
||||
pressures
|
||||
WHERE
|
||||
pressure_id = $1;
|
@ -1,10 +0,0 @@
|
||||
SELECT
|
||||
pressure_id,
|
||||
pressure_value,
|
||||
pressure_from_date,
|
||||
pressure_till_date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
FROM
|
||||
pressures;
|
@ -1,12 +0,0 @@
|
||||
SELECT
|
||||
temperature_id,
|
||||
temperature_value,
|
||||
temperature_from_date,
|
||||
temperature_till_date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
FROM
|
||||
temperatures
|
||||
WHERE
|
||||
temperature_id = $1;
|
@ -1,10 +0,0 @@
|
||||
SELECT
|
||||
temperature_id,
|
||||
temperature_value,
|
||||
temperature_from_date,
|
||||
temperature_till_date,
|
||||
sensor_id,
|
||||
creation_date,
|
||||
update_date
|
||||
FROM
|
||||
temperatures;
|
@ -1,3 +0,0 @@
|
||||
UPDATE info
|
||||
SET value = $2
|
||||
WHERE key = $1;
|
@ -1,142 +0,0 @@
|
||||
package logfile
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
type csvLogfile struct {
|
||||
logfile string
|
||||
}
|
||||
|
||||
func (cl *csvLogfile) Read() ([]*types.MeasuredValue, error) {
|
||||
|
||||
if _, err := os.Stat(cl.logfile); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(filepath.Dir(cl.logfile)); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(cl.logfile), 0755); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorDirectoryCreate, filepath.Dir(cl.logfile))
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.Create(cl.logfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorLogfileCreate, cl.logfile)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
f, err := os.Open(cl.logfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorLogfileOpen, cl.logfile)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r := csv.NewReader(f)
|
||||
records, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v: %v", errorLogfileDecode, cl.logfile, err)
|
||||
}
|
||||
|
||||
measuredValues := make([]*types.MeasuredValue, 0)
|
||||
|
||||
for _, record := range records {
|
||||
|
||||
// ValueType
|
||||
valueType, err := types.SelectMeasuredValueType(record[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v: %v", errorParseFloat, record[1], err)
|
||||
}
|
||||
|
||||
// Value
|
||||
value, err := strconv.ParseFloat(record[2], 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v: %v", errorParseFloat, record[2], err)
|
||||
}
|
||||
|
||||
// Times
|
||||
times := make([]time.Time, 0)
|
||||
for _, i := range []int{3, 4} {
|
||||
time, err := time.Parse(format.TimeFormat, record[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v: %v", errorParseTime, record[i], err)
|
||||
}
|
||||
times = append(times, time)
|
||||
}
|
||||
|
||||
measuredValue := &types.MeasuredValue{
|
||||
ID: record[0],
|
||||
ValueType: *valueType,
|
||||
Value: value,
|
||||
FromDate: times[0],
|
||||
TillDate: times[1],
|
||||
SensorID: record[5],
|
||||
}
|
||||
|
||||
// Creation date
|
||||
if record[6] != "null" {
|
||||
creationDate, err := time.Parse(format.TimeFormat, record[6])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v: %v", errorParseTime, record[6], err)
|
||||
}
|
||||
measuredValue.CreationDate = &creationDate
|
||||
}
|
||||
|
||||
// Update date
|
||||
if record[7] != "null" {
|
||||
updateDate, err := time.Parse(format.TimeFormat, record[7])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v: %v", errorParseTime, record[7], err)
|
||||
}
|
||||
measuredValue.UpdateDate = &updateDate
|
||||
}
|
||||
|
||||
measuredValues = append(measuredValues, measuredValue)
|
||||
|
||||
}
|
||||
|
||||
return measuredValues, nil
|
||||
|
||||
}
|
||||
|
||||
func (cl *csvLogfile) Write(measuredValues []*types.MeasuredValue) error {
|
||||
f, err := os.Create(cl.logfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorLogfileCreate, cl.logfile)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
w := csv.NewWriter(f)
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
|
||||
record := []string{
|
||||
measuredValue.ID,
|
||||
fmt.Sprintf("%v", measuredValue.ValueType),
|
||||
fmt.Sprintf("%v", measuredValue.Value),
|
||||
measuredValue.FromDate.Format(format.TimeFormat),
|
||||
measuredValue.TillDate.Format(format.TimeFormat),
|
||||
measuredValue.SensorID,
|
||||
}
|
||||
|
||||
record = append(record, measuredValue.CreationDate.Format(format.TimeFormat))
|
||||
|
||||
if measuredValue.UpdateDate != nil {
|
||||
record = append(record, measuredValue.UpdateDate.Format(format.TimeFormat))
|
||||
} else {
|
||||
record = append(record, "null")
|
||||
}
|
||||
|
||||
w.Write(record)
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
return nil
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package logfile
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errorDirectoryCreate = errors.New("Can not create directory")
|
||||
|
||||
errorLogfileCreate = errors.New("Can not create logfile")
|
||||
errorLogfileDecode = errors.New("Can not decode from reader")
|
||||
errorLogfileEncode = errors.New("Can not encode from writer")
|
||||
errorLogfileMarshal = errors.New("Can not marshal values")
|
||||
errorLogfileNotFound = errors.New("Can not find logfile")
|
||||
errorLogfileOpen = errors.New("Can not open logfile")
|
||||
errorLogfileRead = errors.New("Can not read from given reader")
|
||||
errorLogfileUnmarshal = errors.New("Can not unmarshal values")
|
||||
errorLogfileWrite = errors.New("Can not write with given writer")
|
||||
|
||||
errorNoValidHumidityID = errors.New("No valid humidity id detected or available")
|
||||
errorNoValidMesuredValue = errors.New("No mesured value detected or available")
|
||||
errorNoValidSensorID = errors.New("No sensor id detected or available")
|
||||
errorNoValidTemperatureID = errors.New("No valid temperature id detected or available")
|
||||
errorNoValidTime = errors.New("No time detected or available")
|
||||
errorNoValidTimePeriods = errors.New("No valid time periods")
|
||||
|
||||
errorParseFloat = errors.New("Can not parse float")
|
||||
errorParseMeasurementUnit = errors.New("Can not parse mesaurement unit")
|
||||
errorParseTime = errors.New("Can not parse time")
|
||||
|
||||
errorTypeSwitch = errors.New("Can not detect type via type switch")
|
||||
)
|
@ -1,11 +0,0 @@
|
||||
package logfile
|
||||
|
||||
import (
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
// Logfile is an interface for various logfiles
|
||||
type Logfile interface {
|
||||
Read() ([]*types.MeasuredValue, error)
|
||||
Write(measuredValues []*types.MeasuredValue) error
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package logfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
type jsonLogfile struct {
|
||||
logfile string
|
||||
}
|
||||
|
||||
func (jl *jsonLogfile) Read() ([]*types.MeasuredValue, error) {
|
||||
|
||||
if _, err := os.Stat(jl.logfile); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(filepath.Dir(jl.logfile)); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(jl.logfile), 0755); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorDirectoryCreate, filepath.Dir(jl.logfile))
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.Create(jl.logfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorLogfileCreate, jl.logfile)
|
||||
}
|
||||
f.WriteString("{}")
|
||||
f.Close()
|
||||
}
|
||||
|
||||
f, err := os.Open(jl.logfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v %v: %v", errorLogfileOpen, jl.logfile, err)
|
||||
}
|
||||
|
||||
measuredValues := make([]*types.MeasuredValue, 0)
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&measuredValues); err != nil {
|
||||
return nil, fmt.Errorf("%v %v: %v", errorLogfileDecode, jl.logfile, err)
|
||||
}
|
||||
|
||||
return measuredValues, nil
|
||||
}
|
||||
|
||||
func (jl *jsonLogfile) Write(measuredValues []*types.MeasuredValue) error {
|
||||
|
||||
if _, err := os.Stat(filepath.Dir(jl.logfile)); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(jl.logfile), 755); err != nil {
|
||||
return fmt.Errorf("Directory for the logfile can not be created: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.Create(jl.logfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v %v: %v", errorLogfileCreate, jl.logfile, err)
|
||||
}
|
||||
|
||||
jsonEncoder := json.NewEncoder(f)
|
||||
jsonEncoder.SetIndent("", " ")
|
||||
err = jsonEncoder.Encode(measuredValues)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v %v: %v", errorLogfileEncode, jl.logfile, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package logfile
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// New returns a log file with basic functions for reading and writing data. The
|
||||
// file extension of the logfile is taken into account to format the logfile
|
||||
// into the correct format.
|
||||
func New(logfile string) Logfile {
|
||||
ext := filepath.Ext(logfile)
|
||||
switch ext {
|
||||
case ".csv":
|
||||
return &csvLogfile{
|
||||
logfile: logfile,
|
||||
}
|
||||
case ".json":
|
||||
return &jsonLogfile{
|
||||
logfile: logfile,
|
||||
}
|
||||
case ".xml":
|
||||
return &xmlLogfile{
|
||||
logfile: logfile,
|
||||
}
|
||||
default:
|
||||
return &jsonLogfile{
|
||||
logfile: logfile,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package logfile
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
// MeasuredValues is an XML Wrapper for an array of measured values
|
||||
type MeasuredValues struct {
|
||||
XMLName xml.Name `xml:"measured_values"`
|
||||
MeasuredValues []*MeasuredValue `xml:"measured_value"`
|
||||
}
|
||||
|
||||
// MeasuredValue is an XML Wrapper for the original measured value struct
|
||||
type MeasuredValue struct {
|
||||
XMLName xml.Name `xml:"measured_value"`
|
||||
*types.MeasuredValue
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package logfile
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
)
|
||||
|
||||
type xmlLogfile struct {
|
||||
logfile string
|
||||
}
|
||||
|
||||
func (xl *xmlLogfile) GetLogfile() string {
|
||||
return xl.logfile
|
||||
}
|
||||
|
||||
func (xl *xmlLogfile) Read() ([]*types.MeasuredValue, error) {
|
||||
|
||||
if _, err := os.Stat(xl.logfile); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(filepath.Dir(xl.logfile)); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(xl.logfile), 0755); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorDirectoryCreate, filepath.Dir(xl.logfile))
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.Create(xl.logfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorLogfileCreate, xl.logfile)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
f, err := os.Open(xl.logfile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorLogfileOpen, xl.logfile)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
measuredValues := new(MeasuredValues)
|
||||
|
||||
if err := xml.NewDecoder(f).Decode(&measuredValues); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", errorLogfileDecode, err)
|
||||
}
|
||||
|
||||
cachedMeasuredValues := make([]*types.MeasuredValue, 0)
|
||||
for _, measuredValue := range measuredValues.MeasuredValues {
|
||||
cachedMeasuredValues = append(cachedMeasuredValues, measuredValue.MeasuredValue)
|
||||
}
|
||||
|
||||
return cachedMeasuredValues, nil
|
||||
}
|
||||
|
||||
func (xl *xmlLogfile) Write(measuredValues []*types.MeasuredValue) error {
|
||||
f, err := os.Create(xl.logfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorLogfileCreate, xl.logfile)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
cachedMeasuredValues := new(MeasuredValues)
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
cachedMeasuredValue := &MeasuredValue{
|
||||
MeasuredValue: measuredValue,
|
||||
}
|
||||
|
||||
cachedMeasuredValues.MeasuredValues = append(cachedMeasuredValues.MeasuredValues, cachedMeasuredValue)
|
||||
}
|
||||
|
||||
bytes, err := xml.MarshalIndent(cachedMeasuredValues, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorLogfileMarshal, err)
|
||||
}
|
||||
|
||||
_, err = f.Write(bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", errorLogfileWrite, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -11,4 +11,4 @@ INSERT INTO sensors (
|
||||
device_id,
|
||||
creation_date
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
|
@ -2,132 +2,239 @@ package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"sort"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/internal/format"
|
||||
"github.com/volker-raschek/flucky/pkg/storage/db"
|
||||
"github.com/volker-raschek/flucky/pkg/storage/logfile"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/volker-raschek/flucky/pkg/types"
|
||||
"github.com/volker-raschek/go-logger/pkg/logger"
|
||||
)
|
||||
|
||||
// Compression the measured values. The system checks whether the measured values
|
||||
// of the same type correspond to those of the predecessor. If this is the case,
|
||||
// the current value is discarded and the validity date of the previous value is
|
||||
// set to that of the current value. This means that no information is lost.
|
||||
// Only the validity period of the measured value is increased.
|
||||
func Compression(measuredValues []*types.MeasuredValue) []*types.MeasuredValue {
|
||||
compressedMeasuredValues := make([]*types.MeasuredValue, 0)
|
||||
lastMeasuredValuesBySensors := make(map[string]map[types.MeasuredValueType]*types.MeasuredValue, 0)
|
||||
|
||||
// Sort all measured values according to the start time of the validity date
|
||||
// in order to successfully implement the subsequent compression.
|
||||
sort.SliceStable(measuredValues, func(i int, j int) bool {
|
||||
return measuredValues[i].FromDate.Before(measuredValues[j].TillDate)
|
||||
})
|
||||
|
||||
now := format.FormatedTime()
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
|
||||
// If the sensor id does not exist in the map, a new map is initialized,
|
||||
// which can assume measured value types as the key. Behind this key there
|
||||
// is a pointer which refers to a measured value in the memory. This new map
|
||||
// is added to the map "lastMeasuredValuesBySensors" under the sensor ID.
|
||||
// This makes it possible to store one measured value per measured value
|
||||
// type per sensor.
|
||||
if _, ok := lastMeasuredValuesBySensors[measuredValue.SensorID]; !ok {
|
||||
lastMeasuredValuesBySensors[measuredValue.SensorID] = make(map[types.MeasuredValueType]*types.MeasuredValue, 0)
|
||||
}
|
||||
|
||||
if _, ok := lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType]; !ok {
|
||||
lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType] = measuredValue
|
||||
continue
|
||||
}
|
||||
|
||||
if lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType].Value == measuredValue.Value {
|
||||
lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType].TillDate = measuredValue.TillDate
|
||||
lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType].UpdateDate = &now
|
||||
} else if lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType].Value != measuredValue.Value {
|
||||
compressedMeasuredValues = append(compressedMeasuredValues, lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType])
|
||||
delete(lastMeasuredValuesBySensors[measuredValue.SensorID], measuredValue.ValueType)
|
||||
lastMeasuredValuesBySensors[measuredValue.SensorID][measuredValue.ValueType] = measuredValue
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all remaining entries from the map into the cache array
|
||||
for _, lastMeasuredValuesBySensor := range lastMeasuredValuesBySensors {
|
||||
for _, measuredValueType := range types.MeasuredValueTypes {
|
||||
if measuredValue, ok := lastMeasuredValuesBySensor[measuredValueType]; ok {
|
||||
compressedMeasuredValues = append(compressedMeasuredValues, measuredValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort all measured values again to include the measured values from the
|
||||
// cache.
|
||||
sort.SliceStable(compressedMeasuredValues, func(i int, j int) bool {
|
||||
return compressedMeasuredValues[i].FromDate.Before(compressedMeasuredValues[j].FromDate)
|
||||
})
|
||||
|
||||
return compressedMeasuredValues
|
||||
// Storage is a general interface for a storage endpoint
|
||||
type Storage interface {
|
||||
InsertDevice(ctx context.Context, device *types.Device) error
|
||||
InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
|
||||
InsertSensor(ctx context.Context, sensor *types.Sensor) error
|
||||
SelectDevice(ctx context.Context, id string) (*types.Device, error)
|
||||
SelectSensor(ctx context.Context, id string) (*types.Sensor, error)
|
||||
}
|
||||
|
||||
// Read measured values from the given storage endpoint url. The scheme must be
|
||||
// matched to a provider, if the scheme is not implemented, the function
|
||||
// returns an error
|
||||
func Read(ctx context.Context, storageEndpoint *url.URL) ([]*types.MeasuredValue, error) {
|
||||
switch storageEndpoint.Scheme {
|
||||
case "file":
|
||||
measuredValueLogfile := logfile.New(storageEndpoint.Path)
|
||||
return measuredValueLogfile.Read()
|
||||
var (
|
||||
postgresAssetPath = "pkg/storage/postgres"
|
||||
)
|
||||
|
||||
// Postgres implementation
|
||||
type Postgres struct {
|
||||
dbo *sql.DB
|
||||
flogger logger.Logger
|
||||
}
|
||||
|
||||
// InsertDevice into the database
|
||||
func (postgres *Postgres) InsertDevice(ctx context.Context, device *types.Device) error {
|
||||
asset := filepath.Join(postgresAssetPath, "insertDevice.sql")
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to begin new transaction: %v", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to prepare statement: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("Failed to execute statement: %v", err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// InsertMeasuredValues into the database
|
||||
func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
|
||||
splittedMeasuredValues := make(map[string][]*types.MeasuredValue, 0)
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok {
|
||||
splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
|
||||
}
|
||||
splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue)
|
||||
}
|
||||
|
||||
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to begin new transaction: %v", err)
|
||||
}
|
||||
|
||||
// General insert function
|
||||
insert := func(tx *sql.Tx, asset string, measuredValues []*types.MeasuredValue) error {
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to prepare statement: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, measuredValue := range measuredValues {
|
||||
_, err := stmt.Exec(&measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to execute statement: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for measuredValueType, measuredValues := range splittedMeasuredValues {
|
||||
var asset string
|
||||
|
||||
switch measuredValueType {
|
||||
case "humidity":
|
||||
asset = filepath.Join(postgresAssetPath, "insertHumidity.sql")
|
||||
case "pressure":
|
||||
asset = filepath.Join(postgresAssetPath, "insertPressure.sql")
|
||||
case "temperature":
|
||||
asset = filepath.Join(postgresAssetPath, "insertTemperature.sql")
|
||||
default:
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
|
||||
}
|
||||
|
||||
err := insert(tx, asset, measuredValues)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// InsertSensor into the database
|
||||
func (postgres *Postgres) InsertSensor(ctx context.Context, sensor *types.Sensor) error {
|
||||
asset := filepath.Join(postgresAssetPath, "insertSensor.sql")
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to begin new transaction: %v", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to prepare statement: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("Failed to execute statement: %v", err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// SelectDevice from database
|
||||
func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.Device, error) {
|
||||
asset := filepath.Join(postgresAssetPath, "selectDeviceByID.sql")
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
row := stmt.QueryRow(id)
|
||||
|
||||
device := new(types.Device)
|
||||
err = row.Scan(&device.ID, &device.Name, &device.Location, &device.CreationDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to scan row: %v", err)
|
||||
}
|
||||
|
||||
return device, nil
|
||||
}
|
||||
|
||||
// SelectSensor from database
|
||||
func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
|
||||
asset := filepath.Join(postgresAssetPath, "selectSensorByID.sql")
|
||||
queryBytes, err := Asset(asset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
||||
}
|
||||
query := string(queryBytes)
|
||||
|
||||
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
row := stmt.QueryRow(id)
|
||||
|
||||
sensor := new(types.Sensor)
|
||||
err = row.Scan(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to scan row: %v", err)
|
||||
}
|
||||
|
||||
return sensor, nil
|
||||
}
|
||||
|
||||
// New returns a new storage provider
|
||||
func New(storageEndpoint string, flogger logger.Logger) (Storage, error) {
|
||||
storageEndpointURL, err := url.Parse(storageEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch storageEndpointURL.Scheme {
|
||||
case "postgres":
|
||||
database, err := db.New(storageEndpoint)
|
||||
newDBO, err := sql.Open(storageEndpointURL.Scheme, storageEndpointURL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer database.Close()
|
||||
return database.SelectMeasuredValues(ctx)
|
||||
}
|
||||
return nil, fmt.Errorf("No supported scheme")
|
||||
}
|
||||
|
||||
func Round(measuredValues []*types.MeasuredValue, round float64) {
|
||||
for _, measuredValue := range measuredValues {
|
||||
measuredValue.Value = math.Round(measuredValue.Value/round) * round
|
||||
}
|
||||
}
|
||||
|
||||
// Write measured values to the given storage endpoint url. If the storage
|
||||
// provider defined to a file, the data will be overwritten. If a database
|
||||
// provider is used, the data is simply added without deleting the existing
|
||||
// data. The scheme must be matched to a storage provider, if the scheme is not
|
||||
// implemented, the function returns an error
|
||||
func Write(ctx context.Context, measuredValues []*types.MeasuredValue, storageEndpoint *url.URL) error {
|
||||
writeCreationDate(measuredValues)
|
||||
switch storageEndpoint.Scheme {
|
||||
case "file":
|
||||
measuredValueLogfile := logfile.New(storageEndpoint.Path)
|
||||
return measuredValueLogfile.Write(measuredValues)
|
||||
case "postgres":
|
||||
database, err := db.New(storageEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer database.Close()
|
||||
return database.InsertMeasuredValues(ctx, measuredValues)
|
||||
return &Postgres{
|
||||
dbo: newDBO,
|
||||
flogger: flogger,
|
||||
}, nil
|
||||
default:
|
||||
return fmt.Errorf("No supported scheme")
|
||||
}
|
||||
}
|
||||
|
||||
func writeCreationDate(measuredValues []*types.MeasuredValue) {
|
||||
now := format.FormatedTime()
|
||||
for _, measuredValue := range measuredValues {
|
||||
if measuredValue.CreationDate == nil {
|
||||
measuredValue.CreationDate = &now
|
||||
}
|
||||
return nil, fmt.Errorf("Unsupported database scheme: %v", storageEndpointURL.Scheme)
|
||||
}
|
||||
}
|
||||
|
@ -1,286 +0,0 @@
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/volker-raschek/flucky/pkg/storage"
|
||||
"github.com/volker-raschek/flucky/test/goldenfiles"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
source string
|
||||
expected string
|
||||
compression bool
|
||||
round float64
|
||||
}
|
||||
|
||||
var (
|
||||
testCases = []*testCase{
|
||||
// CSV - Uncompressed Not Rounded -> Uncompressed Rounded 0.5
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenMeasuredValuesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenMeasuredValuesUncompressedRounded.csv",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenHumiditiesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenHumiditiesUncompressedRounded.csv",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenPressuresUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenPressuresUncompressedRounded.csv",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenTemperaturesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenTemperaturesUncompressedRounded.csv",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
|
||||
// CSV - Uncompressed Not Rounded -> Compressed Not Rounded
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenMeasuredValuesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenMeasuredValuesCompressedNotRounded.csv",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenHumiditiesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenHumiditiesCompressedNotRounded.csv",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenPressuresUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenPressuresCompressedNotRounded.csv",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenTemperaturesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenTemperaturesCompressedNotRounded.csv",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
|
||||
// CSV - Uncompressed Not Rounded -> Compressed Rounded 0.5
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenMeasuredValuesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenMeasuredValuesCompressedRounded.csv",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenHumiditiesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenHumiditiesCompressedRounded.csv",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenPressuresUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenPressuresCompressedRounded.csv",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/csv/goldenTemperaturesUncompressedNotRounded.csv",
|
||||
expected: "test/goldenfiles/csv/goldenTemperaturesCompressedRounded.csv",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
|
||||
// JSON - Uncompressed Not Rounded -> Uncompressed Rounded 0.5
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenMeasuredValuesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenMeasuredValuesUncompressedRounded.json",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenHumiditiesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenHumiditiesUncompressedRounded.json",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenPressuresUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenPressuresUncompressedRounded.json",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenTemperaturesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenTemperaturesUncompressedRounded.json",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
|
||||
// JSON - Uncompressed Not Rounded -> Compressed Not Rounded
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenMeasuredValuesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenMeasuredValuesCompressedNotRounded.json",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenHumiditiesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenHumiditiesCompressedNotRounded.json",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenPressuresUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenPressuresCompressedNotRounded.json",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenTemperaturesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenTemperaturesCompressedNotRounded.json",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
|
||||
// JSON - Uncompressed Not Rounded -> Compressed Rounded 0.5
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenMeasuredValuesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenMeasuredValuesCompressedRounded.json",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenHumiditiesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenHumiditiesCompressedRounded.json",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenPressuresUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenPressuresCompressedRounded.json",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/json/goldenTemperaturesUncompressedNotRounded.json",
|
||||
expected: "test/goldenfiles/json/goldenTemperaturesCompressedRounded.json",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
|
||||
// XML - Uncompressed Not Rounded -> Uncompressed Rounded 0.5
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenMeasuredValuesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenMeasuredValuesUncompressedRounded.xml",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenHumiditiesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenHumiditiesUncompressedRounded.xml",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenPressuresUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenPressuresUncompressedRounded.xml",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenTemperaturesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenTemperaturesUncompressedRounded.xml",
|
||||
compression: false,
|
||||
round: 0.5,
|
||||
},
|
||||
|
||||
// XML - Uncompressed Not Rounded -> Compressed Not Rounded
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenMeasuredValuesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenMeasuredValuesCompressedNotRounded.xml",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenHumiditiesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenHumiditiesCompressedNotRounded.xml",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenPressuresUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenPressuresCompressedNotRounded.xml",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenTemperaturesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenTemperaturesCompressedNotRounded.xml",
|
||||
compression: true,
|
||||
round: 0,
|
||||
},
|
||||
|
||||
// XML - Uncompressed Not Rounded -> Compressed Rounded 0.5
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenMeasuredValuesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenMeasuredValuesCompressedRounded.xml",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenHumiditiesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenHumiditiesCompressedRounded.xml",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenPressuresUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenPressuresCompressedRounded.xml",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
&testCase{
|
||||
source: "test/goldenfiles/xml/goldenTemperaturesUncompressedNotRounded.xml",
|
||||
expected: "test/goldenfiles/xml/goldenTemperaturesCompressedRounded.xml",
|
||||
compression: true,
|
||||
round: 0.5,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestCompressionAndRounding(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
measuredValues, err := goldenfiles.GetGoldenMeasuredValues(testCase.source)
|
||||
require.NoError(err)
|
||||
|
||||
expectedMeasuredValues, err := goldenfiles.GetGoldenMeasuredValues(testCase.expected)
|
||||
require.NoError(err)
|
||||
|
||||
actualMeasuredValues := measuredValues
|
||||
|
||||
if testCase.round != 0 {
|
||||
storage.Round(actualMeasuredValues, testCase.round)
|
||||
}
|
||||
|
||||
if testCase.compression {
|
||||
actualMeasuredValues = storage.Compression(actualMeasuredValues)
|
||||
}
|
||||
|
||||
for i, _ := range expectedMeasuredValues {
|
||||
require.Equal(expectedMeasuredValues[i].ID, actualMeasuredValues[i].ID, "ID of element %v is not equal between expected and actual", i)
|
||||
require.Equal(expectedMeasuredValues[i].Value, actualMeasuredValues[i].Value, "Value of element %v is not equal between expected and actual", i)
|
||||
require.Equal(expectedMeasuredValues[i].ValueType, actualMeasuredValues[i].ValueType, "ValueType of element %v is not equal between expected and actual", i)
|
||||
require.Equal(expectedMeasuredValues[i].FromDate, actualMeasuredValues[i].FromDate, "FromDate of element %v is not equal between expected and actual", i)
|
||||
require.Equal(expectedMeasuredValues[i].TillDate, actualMeasuredValues[i].TillDate, "TillDate of element %v is not equal between expected and actual", i)
|
||||
require.Equal(expectedMeasuredValues[i].SensorID, actualMeasuredValues[i].SensorID, "SensorID of element %v is not equal between expected and actual", i)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ package types
|
||||
|
||||
import "time"
|
||||
|
||||
// Device ...
|
||||
// Device represent a device with all his settings.
|
||||
type Device struct {
|
||||
ID string `json:"id" xml:"id"`
|
||||
Name string `json:"name" xml:"name"`
|
||||
|
@ -1,206 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type GPIO string
|
||||
|
||||
const (
|
||||
GPIO02 GPIO = "GPIO02"
|
||||
GPIO03 = "GPIO03"
|
||||
GPIO04 = "GPIO04"
|
||||
GPIO05 = "GPIO05"
|
||||
GPIO06 = "GPIO06"
|
||||
GPIO07 = "GPIO07"
|
||||
GPIO08 = "GPIO08"
|
||||
GPIO10 = "GPIO10"
|
||||
GPIO11 = "GPIO11"
|
||||
GPIO12 = "GPIO12"
|
||||
GPIO13 = "GPIO13"
|
||||
GPIO14 = "GPIO14"
|
||||
GPIO15 = "GPIO15"
|
||||
GPIO16 = "GPIO16"
|
||||
GPIO17 = "GPIO17"
|
||||
GPIO18 = "GPIO18"
|
||||
GPIO19 = "GPIO19"
|
||||
GPIO20 = "GPIO20"
|
||||
GPIO21 = "GPIO21"
|
||||
GPIO22 = "GPIO22"
|
||||
GPIO23 = "GPIO23"
|
||||
GPIO24 = "GPIO24"
|
||||
GPIO25 = "GPIO25"
|
||||
GPIO26 = "GPIO26"
|
||||
GPIO27 = "GPIO27"
|
||||
)
|
||||
|
||||
func GPIOToString(gpio GPIO) (string, error) {
|
||||
switch gpio {
|
||||
case GPIO02:
|
||||
return "GPIO02", nil
|
||||
case GPIO03:
|
||||
return "GPIO03", nil
|
||||
case GPIO04:
|
||||
return "GPIO04", nil
|
||||
case GPIO05:
|
||||
return "GPIO05", nil
|
||||
case GPIO06:
|
||||
return "GPIO06", nil
|
||||
case GPIO07:
|
||||
return "GPIO07", nil
|
||||
case GPIO08:
|
||||
return "GPIO08", nil
|
||||
case GPIO10:
|
||||
return "GPIO10", nil
|
||||
case GPIO11:
|
||||
return "GPIO11", nil
|
||||
case GPIO12:
|
||||
return "GPIO12", nil
|
||||
case GPIO13:
|
||||
return "GPIO13", nil
|
||||
case GPIO14:
|
||||
return "GPIO14", nil
|
||||
case GPIO15:
|
||||
return "GPIO15", nil
|
||||
case GPIO16:
|
||||
return "GPIO16", nil
|
||||
case GPIO17:
|
||||
return "GPIO17", nil
|
||||
case GPIO18:
|
||||
return "GPIO18", nil
|
||||
case GPIO19:
|
||||
return "GPIO19", nil
|
||||
case GPIO20:
|
||||
return "GPIO20", nil
|
||||
case GPIO21:
|
||||
return "GPIO21", nil
|
||||
case GPIO22:
|
||||
return "GPIO22", nil
|
||||
case GPIO23:
|
||||
return "GPIO23", nil
|
||||
case GPIO24:
|
||||
return "GPIO24", nil
|
||||
case GPIO25:
|
||||
return "GPIO25", nil
|
||||
case GPIO26:
|
||||
return "GPIO26", nil
|
||||
case GPIO27:
|
||||
return "GPIO27", nil
|
||||
default:
|
||||
return "", fmt.Errorf("Can not determine gpio %v", gpio)
|
||||
}
|
||||
}
|
||||
|
||||
func GPIOToInt(gpio GPIO) (int, error) {
|
||||
switch gpio {
|
||||
case GPIO02:
|
||||
return 2, nil
|
||||
case GPIO03:
|
||||
return 3, nil
|
||||
case GPIO04:
|
||||
return 4, nil
|
||||
case GPIO05:
|
||||
return 5, nil
|
||||
case GPIO06:
|
||||
return 6, nil
|
||||
case GPIO07:
|
||||
return 7, nil
|
||||
case GPIO08:
|
||||
return 8, nil
|
||||
case GPIO10:
|
||||
return 10, nil
|
||||
case GPIO11:
|
||||
return 11, nil
|
||||
case GPIO12:
|
||||
return 12, nil
|
||||
case GPIO13:
|
||||
return 13, nil
|
||||
case GPIO14:
|
||||
return 14, nil
|
||||
case GPIO15:
|
||||
return 15, nil
|
||||
case GPIO16:
|
||||
return 16, nil
|
||||
case GPIO17:
|
||||
return 17, nil
|
||||
case GPIO18:
|
||||
return 18, nil
|
||||
case GPIO19:
|
||||
return 19, nil
|
||||
case GPIO20:
|
||||
return 20, nil
|
||||
case GPIO21:
|
||||
return 21, nil
|
||||
case GPIO22:
|
||||
return 22, nil
|
||||
case GPIO23:
|
||||
return 23, nil
|
||||
case GPIO24:
|
||||
return 24, nil
|
||||
case GPIO25:
|
||||
return 25, nil
|
||||
case GPIO26:
|
||||
return 26, nil
|
||||
case GPIO27:
|
||||
return 27, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("Can not determine gpio %v", gpio)
|
||||
}
|
||||
}
|
||||
|
||||
func StringToGPIO(gpio string) (GPIO, error) {
|
||||
switch gpio {
|
||||
case "GPIO02":
|
||||
return GPIO02, nil
|
||||
case "GPIO03":
|
||||
return GPIO03, nil
|
||||
case "GPIO04":
|
||||
return GPIO04, nil
|
||||
case "GPIO05":
|
||||
return GPIO05, nil
|
||||
case "GPIO06":
|
||||
return GPIO06, nil
|
||||
case "GPIO07":
|
||||
return GPIO07, nil
|
||||
case "GPIO08":
|
||||
return GPIO08, nil
|
||||
case "GPIO10":
|
||||
return GPIO10, nil
|
||||
case "GPIO11":
|
||||
return GPIO11, nil
|
||||
case "GPIO12":
|
||||
return GPIO12, nil
|
||||
case "GPIO13":
|
||||
return GPIO13, nil
|
||||
case "GPIO14":
|
||||
return GPIO14, nil
|
||||
case "GPIO15":
|
||||
return GPIO15, nil
|
||||
case "GPIO16":
|
||||
return GPIO16, nil
|
||||
case "GPIO17":
|
||||
return GPIO17, nil
|
||||
case "GPIO18":
|
||||
return GPIO18, nil
|
||||
case "GPIO19":
|
||||
return GPIO19, nil
|
||||
case "GPIO20":
|
||||
return GPIO20, nil
|
||||
case "GPIO21":
|
||||
return GPIO21, nil
|
||||
case "GPIO22":
|
||||
return GPIO22, nil
|
||||
case "GPIO23":
|
||||
return GPIO23, nil
|
||||
case "GPIO24":
|
||||
return GPIO24, nil
|
||||
case "GPIO25":
|
||||
return GPIO25, nil
|
||||
case "GPIO26":
|
||||
return GPIO26, nil
|
||||
case "GPIO27":
|
||||
return GPIO27, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Can not determine gpio %v", gpio)
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RGBLED struct {
|
||||
RGBLEDID string `json:"rgbled_id" xml:"rgbled_id"`
|
||||
RGBLEDName string `json:"rgbled_name" xml:"rgbled_name"`
|
||||
RGBLEDLocation string `json:"rgbled_location" xml:"rgb_location"`
|
||||
BaseColorsToGPIO map[BaseColor]*GPIO `json:"base_colors_to_gpio" xml:"base_colors_to_gpio"`
|
||||
ActionMapping map[LEDAction]LEDColor `json:"action_mapping" xml:"action_mapping"`
|
||||
RGBLEDEnabled bool `json:"rgbled_enabled" xml:"rgb_enabled"`
|
||||
DeviceID string `json:"device_id" xml:"device_id"`
|
||||
CreationDate time.Time `json:"creation_date" xml:"creation_date"`
|
||||
}
|
||||
|
||||
type BaseColor string
|
||||
|
||||
const (
|
||||
BaseColorBlue BaseColor = "blue"
|
||||
BaseColorRed BaseColor = "red"
|
||||
BaseColorGreen BaseColor = "green"
|
||||
)
|
||||
|
||||
type LEDColor string
|
||||
|
||||
const (
|
||||
LEDColorBlue LEDColor = "blue"
|
||||
LEDColorGreen LEDColor = "green"
|
||||
LEDColorNone LEDColor = "none"
|
||||
LEDColorPurple LEDColor = "purple"
|
||||
LEDColorRed LEDColor = "red"
|
||||
LEDColorTurquoise LEDColor = "turquoise"
|
||||
LEDColorYellow LEDColor = "yellow"
|
||||
LEDColorWhite LEDColor = "white"
|
||||
)
|
||||
|
||||
func StringToLEDColor(color string) (LEDColor, error) {
|
||||
switch color {
|
||||
case "blue":
|
||||
return LEDColorBlue, nil
|
||||
case "red":
|
||||
return LEDColorRed, nil
|
||||
case "green":
|
||||
return LEDColorGreen, nil
|
||||
case "none":
|
||||
return LEDColorNone, nil
|
||||
case "purple":
|
||||
return LEDColorPurple, nil
|
||||
case "turquoise":
|
||||
return LEDColorTurquoise, nil
|
||||
case "yellow":
|
||||
return LEDColorYellow, nil
|
||||
case "white":
|
||||
return LEDColorWhite, nil
|
||||
default:
|
||||
return LEDColorNone, fmt.Errorf("Can not convert color to const")
|
||||
}
|
||||
}
|
||||
|
||||
type LEDAction string
|
||||
|
||||
const (
|
||||
LEDActionError LEDAction = "error"
|
||||
LEDActionWarn = "warn"
|
||||
LEDActionRun = "run"
|
||||
LEDActionSync = "sync"
|
||||
LEDActionLogfile = "logfile"
|
||||
)
|
||||
|
||||
var DefaultActionMapping = map[LEDAction]LEDColor{
|
||||
LEDActionError: LEDColorRed,
|
||||
LEDActionWarn: LEDColorYellow,
|
||||
LEDActionRun: LEDColorGreen,
|
||||
LEDActionSync: LEDColorTurquoise,
|
||||
LEDActionLogfile: LEDColorBlue,
|
||||
}
|
@ -1,51 +1,19 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MeasuredValue represent a value provided by a measuring instrument. For
|
||||
// example from a sensor. It can contains different types, for example humidity
|
||||
// or temperature.
|
||||
type MeasuredValue struct {
|
||||
ID string `json:"id" xml:"id"`
|
||||
Value float64 `json:"value,string" xml:"value,string"`
|
||||
ValueType MeasuredValueType `json:"value_type" xml:"value_type"`
|
||||
FromDate time.Time `json:"from_date" xml:"from_date"`
|
||||
TillDate time.Time `json:"till_date" xml:"till_date"`
|
||||
SensorID string `json:"sensor_id" xml:"sensor_id"`
|
||||
CreationDate *time.Time `json:"creation_date" xml:"creation_date"`
|
||||
UpdateDate *time.Time `json:"update_date" xml:"update_date"`
|
||||
}
|
||||
|
||||
type MeasuredValueType string
|
||||
|
||||
const (
|
||||
MeasuredValueTypeHumidity MeasuredValueType = "humidity"
|
||||
MeasuredValueTypePressure MeasuredValueType = "pressure"
|
||||
MeasuredValueTypeTemperature MeasuredValueType = "temperature"
|
||||
)
|
||||
|
||||
var MeasuredValueTypes = []MeasuredValueType{
|
||||
MeasuredValueTypeHumidity,
|
||||
MeasuredValueTypePressure,
|
||||
MeasuredValueTypeTemperature,
|
||||
}
|
||||
|
||||
func SelectMeasuredValues(measuredValueType MeasuredValueType, measuredValues []*MeasuredValue) []*MeasuredValue {
|
||||
cachedMeasuredValues := make([]*MeasuredValue, 0)
|
||||
for _, measuredValue := range measuredValues {
|
||||
if measuredValue.ValueType == measuredValueType {
|
||||
cachedMeasuredValues = append(cachedMeasuredValues, measuredValue)
|
||||
}
|
||||
}
|
||||
return cachedMeasuredValues
|
||||
}
|
||||
|
||||
func SelectMeasuredValueType(valueType string) (*MeasuredValueType, error) {
|
||||
for _, measuredValueType := range MeasuredValueTypes {
|
||||
if fmt.Sprint(measuredValueType) == valueType {
|
||||
return &measuredValueType, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Can not determine value type: %v", valueType)
|
||||
ID string `json:"id" xml:"id"`
|
||||
Value float64 `json:"value,string" xml:"value,string"`
|
||||
ValueType string `json:"value_type" xml:"value_type"`
|
||||
FromDate time.Time `json:"from_date" xml:"from_date"`
|
||||
TillDate time.Time `json:"till_date" xml:"till_date"`
|
||||
SensorID string `json:"sensor_id" xml:"sensor_id"`
|
||||
CreationDate *time.Time `json:"creation_date" xml:"creation_date"`
|
||||
UpdateDate *time.Time `json:"update_date" xml:"update_date"`
|
||||
}
|
||||
|
@ -1,57 +1,48 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Sensor ...
|
||||
// Sensor represents a sensor with all his settings. The struct does not
|
||||
// contains any read method.
|
||||
type Sensor struct {
|
||||
ID string `json:"id" xml:"id"`
|
||||
Name string `json:"name" xml:"name"`
|
||||
Location string `json:"location" xml:"location"`
|
||||
WireID *string `json:"wire_id" xml:"wire_id"`
|
||||
I2CBus *int `json:"i2c_bus" xml:"i2c_bus"`
|
||||
I2CAddress *uint8 `json:"i2c_address" xml:"i2c_address"`
|
||||
GPIONumber *GPIO `json:"gpio_number" xml:"gpio_number"`
|
||||
Model SensorModel `json:"model" xml:"model"`
|
||||
Enabled bool `json:"enabled" xml:"enabled"`
|
||||
LastContact *time.Time `json:"last_contact" xml:"last_contact"`
|
||||
TickDuration string `json:"tick_duration" xml:"tick_duration"`
|
||||
DeviceID string `json:"device_id" xml:"device_id"`
|
||||
CreationDate time.Time `json:"creation_date" xml:"creation_date"`
|
||||
ID string `json:"id" xml:"id"`
|
||||
Name string `json:"name" xml:"name"`
|
||||
Location string `json:"location" xml:"location"`
|
||||
WireID *string `json:"wire_id" xml:"wire_id"`
|
||||
I2CBus *int `json:"i2c_bus" xml:"i2c_bus"`
|
||||
I2CAddress *uint8 `json:"i2c_address" xml:"i2c_address"`
|
||||
GPIONumber string `json:"gpio_number" xml:"gpio_number"`
|
||||
Model string `json:"model" xml:"model"`
|
||||
Enabled bool `json:"enabled" xml:"enabled"`
|
||||
LastContact *time.Time `json:"last_contact" xml:"last_contact"`
|
||||
TickDuration string `json:"tick_duration" xml:"tick_duration"`
|
||||
DeviceID string `json:"device_id" xml:"device_id"`
|
||||
CreationDate time.Time `json:"creation_date" xml:"creation_date"`
|
||||
}
|
||||
|
||||
// JSONDecoder decodes a json into a sensor
|
||||
func (s *Sensor) JSONDecoder(r io.Reader) error {
|
||||
decoder := json.NewDecoder(r)
|
||||
err := decoder.Decode(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not decode sensor from json: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sensor) JSONEncoder(w io.Writer) error {
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetIndent("", " ")
|
||||
err := encoder.Encode(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can not encode sensor to json: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sensor) FullName() string {
|
||||
if s.Name != "" {
|
||||
return s.Name
|
||||
} else if s.WireID != nil {
|
||||
return *s.WireID
|
||||
} else if s.I2CAddress != nil &&
|
||||
s.I2CBus != nil {
|
||||
return fmt.Sprintf("%v/%v", *s.I2CBus, *s.I2CAddress)
|
||||
}
|
||||
// GetID returns the UUID of the sensor.
|
||||
func (s *Sensor) GetID() string {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetDeviceID returns the UUID of the configured device.
|
||||
func (s *Sensor) GetDeviceID() string {
|
||||
return s.DeviceID
|
||||
}
|
||||
|
||||
// GetName returns the name of the sensor.
|
||||
func (s *Sensor) GetName() string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// GetTicker returns a new ticker, which tick every when the sensor should be
|
||||
// read
|
||||
func (s *Sensor) GetTicker() *time.Ticker {
|
||||
duration, err := time.ParseDuration(s.TickDuration)
|
||||
if err != nil {
|
||||
duration = time.Minute
|
||||
}
|
||||
return time.NewTicker(duration)
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
type SensorModel string
|
||||
|
||||
const (
|
||||
BME280 SensorModel = "BME280"
|
||||
DHT11 = "DHT11"
|
||||
DHT22 = "DHT22"
|
||||
DS18B20 = "DS18B20"
|
||||
)
|
||||
|
||||
// SelectSensorModel converts a string into a constant
|
||||
func SelectSensorModel(model string) (SensorModel, error) {
|
||||
switch model {
|
||||
case "BME280":
|
||||
return BME280, nil
|
||||
case "DHT11":
|
||||
return DHT11, nil
|
||||
case "DHT22":
|
||||
return DHT22, nil
|
||||
case "DS18B20":
|
||||
return DS18B20, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Sensor Model %v currently not supported", model)
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
scp -r ${FLUCKY_REMOTE}:/var/log/flucky/*.csv csv
|
||||
scp -r ${FLUCKY_REMOTE}:/var/log/flucky/*.json json
|
||||
scp -r ${FLUCKY_REMOTE}:/var/log/flucky/*.xml xml
|
@ -1,18 +0,0 @@
|
||||
0fc9c4bf-7e86-4cee-b99c-7642984b5fcd,humidity,50.8740234375,2019-10-11T11:51:01.969+02:00,2019-10-11T11:51:03.109+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.971+02:00,2019-10-11T11:51:20.729+02:00
|
||||
7b4f0fc3-ecec-4226-b621-49dd01cf9eb7,humidity,50.884765625,2019-10-11T11:51:04.251+02:00,2019-10-11T11:51:04.252+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:04.254+02:00,null
|
||||
72ccf0a7-e680-460c-a3b8-7d14f46b5b0c,humidity,50.861328125,2019-10-11T11:51:05.402+02:00,2019-10-11T11:51:05.403+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:05.405+02:00,null
|
||||
6f5277d9-abe3-4e87-b0f1-e4320756bbac,humidity,50.8505859375,2019-10-11T11:51:06.551+02:00,2019-10-11T11:51:06.552+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:06.565+02:00,null
|
||||
026370e8-2011-4bf0-8da9-ec4a9fe1ee19,humidity,50.7734375,2019-10-11T11:51:07.7+02:00,2019-10-11T11:51:07.7+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:07.702+02:00,null
|
||||
e96eff46-8322-424d-9a79-51c746124354,humidity,50.728515625,2019-10-11T11:51:08.843+02:00,2019-10-11T11:51:08.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:08.846+02:00,null
|
||||
6beb6aee-6888-4f4a-82ff-5bf700a22b0a,humidity,50.67578125,2019-10-11T11:51:09.986+02:00,2019-10-11T11:51:09.986+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:09.989+02:00,null
|
||||
62a8f169-344d-4f9c-828a-7b4ea4261fad,humidity,50.6650390625,2019-10-11T11:51:11.129+02:00,2019-10-11T11:51:11.13+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.133+02:00,null
|
||||
b1d64d40-59d7-4093-ba1d-208d869c9aae,humidity,50.6884765625,2019-10-11T11:51:11.264+02:00,2019-10-11T11:51:11.265+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.267+02:00,null
|
||||
f98b69ee-bef9-4609-8bfa-82f11d7f51b6,humidity,50.76171875,2019-10-11T11:51:12.401+02:00,2019-10-11T11:51:12.402+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:12.41+02:00,null
|
||||
eab34e23-2b43-40c2-9d6e-90e72fa13db5,humidity,50.7373046875,2019-10-11T11:51:13.551+02:00,2019-10-11T11:51:13.551+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:13.554+02:00,null
|
||||
4659b946-8a40-4ba4-9888-6997bd1795f2,humidity,50.73828125,2019-10-11T11:51:14.695+02:00,2019-10-11T11:51:14.695+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:14.698+02:00,null
|
||||
a6552aff-724c-4339-810a-36d911ddaa58,humidity,50.6845703125,2019-10-11T11:51:15.839+02:00,2019-10-11T11:51:15.839+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.842+02:00,null
|
||||
c311cacd-e346-4b68-902b-400be0f8e02e,humidity,50.6767578125,2019-10-11T11:51:15.976+02:00,2019-10-11T11:51:15.977+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.98+02:00,null
|
||||
2d4db614-17f8-4ddd-ba08-e2a5f77d74ea,humidity,50.68359375,2019-10-11T11:51:17.118+02:00,2019-10-11T11:51:17.118+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.121+02:00,null
|
||||
936d61e4-494e-4959-a39a-c1dbc4a7d162,humidity,50.685546875,2019-10-11T11:51:17.243+02:00,2019-10-11T11:51:17.244+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.247+02:00,null
|
||||
b913c9ca-4b5e-4c01-9aea-ee31d0d97977,humidity,50.6943359375,2019-10-11T11:51:18.381+02:00,2019-10-11T11:51:19.529+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:18.385+02:00,2019-10-11T11:51:20.729+02:00
|
||||
d1a5e29c-d7e7-4ccc-9ff7-80ba1f797312,humidity,50.7080078125,2019-10-11T11:51:19.664+02:00,2019-10-11T11:51:19.664+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.667+02:00,null
|
|
@ -1,4 +0,0 @@
|
||||
0fc9c4bf-7e86-4cee-b99c-7642984b5fcd,humidity,51,2019-10-11T11:51:01.969+02:00,2019-10-11T11:51:07.7+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.971+02:00,2019-10-11T11:51:20.754+02:00
|
||||
e96eff46-8322-424d-9a79-51c746124354,humidity,50.5,2019-10-11T11:51:08.843+02:00,2019-10-11T11:51:11.265+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:08.846+02:00,2019-10-11T11:51:20.754+02:00
|
||||
f98b69ee-bef9-4609-8bfa-82f11d7f51b6,humidity,51,2019-10-11T11:51:12.401+02:00,2019-10-11T11:51:12.402+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:12.41+02:00,null
|
||||
eab34e23-2b43-40c2-9d6e-90e72fa13db5,humidity,50.5,2019-10-11T11:51:13.551+02:00,2019-10-11T11:51:19.664+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:13.554+02:00,2019-10-11T11:51:20.754+02:00
|
|
@ -1,20 +0,0 @@
|
||||
0fc9c4bf-7e86-4cee-b99c-7642984b5fcd,humidity,50.8740234375,2019-10-11T11:51:01.969+02:00,2019-10-11T11:51:01.969+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.971+02:00,null
|
||||
45e8b20c-1ab0-4cf1-9ef1-aa82537557f6,humidity,50.8740234375,2019-10-11T11:51:03.109+02:00,2019-10-11T11:51:03.109+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:03.111+02:00,null
|
||||
7b4f0fc3-ecec-4226-b621-49dd01cf9eb7,humidity,50.884765625,2019-10-11T11:51:04.251+02:00,2019-10-11T11:51:04.252+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:04.254+02:00,null
|
||||
72ccf0a7-e680-460c-a3b8-7d14f46b5b0c,humidity,50.861328125,2019-10-11T11:51:05.402+02:00,2019-10-11T11:51:05.403+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:05.405+02:00,null
|
||||
6f5277d9-abe3-4e87-b0f1-e4320756bbac,humidity,50.8505859375,2019-10-11T11:51:06.551+02:00,2019-10-11T11:51:06.552+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:06.565+02:00,null
|
||||
026370e8-2011-4bf0-8da9-ec4a9fe1ee19,humidity,50.7734375,2019-10-11T11:51:07.7+02:00,2019-10-11T11:51:07.7+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:07.702+02:00,null
|
||||
e96eff46-8322-424d-9a79-51c746124354,humidity,50.728515625,2019-10-11T11:51:08.843+02:00,2019-10-11T11:51:08.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:08.846+02:00,null
|
||||
6beb6aee-6888-4f4a-82ff-5bf700a22b0a,humidity,50.67578125,2019-10-11T11:51:09.986+02:00,2019-10-11T11:51:09.986+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:09.989+02:00,null
|
||||
62a8f169-344d-4f9c-828a-7b4ea4261fad,humidity,50.6650390625,2019-10-11T11:51:11.129+02:00,2019-10-11T11:51:11.13+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.133+02:00,null
|
||||
b1d64d40-59d7-4093-ba1d-208d869c9aae,humidity,50.6884765625,2019-10-11T11:51:11.264+02:00,2019-10-11T11:51:11.265+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.267+02:00,null
|
||||
f98b69ee-bef9-4609-8bfa-82f11d7f51b6,humidity,50.76171875,2019-10-11T11:51:12.401+02:00,2019-10-11T11:51:12.402+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:12.41+02:00,null
|
||||
eab34e23-2b43-40c2-9d6e-90e72fa13db5,humidity,50.7373046875,2019-10-11T11:51:13.551+02:00,2019-10-11T11:51:13.551+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:13.554+02:00,null
|
||||
4659b946-8a40-4ba4-9888-6997bd1795f2,humidity,50.73828125,2019-10-11T11:51:14.695+02:00,2019-10-11T11:51:14.695+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:14.698+02:00,null
|
||||
a6552aff-724c-4339-810a-36d911ddaa58,humidity,50.6845703125,2019-10-11T11:51:15.839+02:00,2019-10-11T11:51:15.839+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.842+02:00,null
|
||||
c311cacd-e346-4b68-902b-400be0f8e02e,humidity,50.6767578125,2019-10-11T11:51:15.976+02:00,2019-10-11T11:51:15.977+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.98+02:00,null
|
||||
2d4db614-17f8-4ddd-ba08-e2a5f77d74ea,humidity,50.68359375,2019-10-11T11:51:17.118+02:00,2019-10-11T11:51:17.118+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.121+02:00,null
|
||||
936d61e4-494e-4959-a39a-c1dbc4a7d162,humidity,50.685546875,2019-10-11T11:51:17.243+02:00,2019-10-11T11:51:17.244+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.247+02:00,null
|
||||
b913c9ca-4b5e-4c01-9aea-ee31d0d97977,humidity,50.6943359375,2019-10-11T11:51:18.381+02:00,2019-10-11T11:51:18.382+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:18.385+02:00,null
|
||||
1171d0dd-2173-4da2-b2c6-7f7daaad7cd1,humidity,50.6943359375,2019-10-11T11:51:19.529+02:00,2019-10-11T11:51:19.529+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.532+02:00,null
|
||||
d1a5e29c-d7e7-4ccc-9ff7-80ba1f797312,humidity,50.7080078125,2019-10-11T11:51:19.664+02:00,2019-10-11T11:51:19.664+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.667+02:00,null
|
|
@ -1,20 +0,0 @@
|
||||
0fc9c4bf-7e86-4cee-b99c-7642984b5fcd,humidity,51,2019-10-11T11:51:01.969+02:00,2019-10-11T11:51:01.969+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.971+02:00,null
|
||||
45e8b20c-1ab0-4cf1-9ef1-aa82537557f6,humidity,51,2019-10-11T11:51:03.109+02:00,2019-10-11T11:51:03.109+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:03.111+02:00,null
|
||||
7b4f0fc3-ecec-4226-b621-49dd01cf9eb7,humidity,51,2019-10-11T11:51:04.251+02:00,2019-10-11T11:51:04.252+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:04.254+02:00,null
|
||||
72ccf0a7-e680-460c-a3b8-7d14f46b5b0c,humidity,51,2019-10-11T11:51:05.402+02:00,2019-10-11T11:51:05.403+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:05.405+02:00,null
|
||||
6f5277d9-abe3-4e87-b0f1-e4320756bbac,humidity,51,2019-10-11T11:51:06.551+02:00,2019-10-11T11:51:06.552+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:06.565+02:00,null
|
||||
026370e8-2011-4bf0-8da9-ec4a9fe1ee19,humidity,51,2019-10-11T11:51:07.7+02:00,2019-10-11T11:51:07.7+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:07.702+02:00,null
|
||||
e96eff46-8322-424d-9a79-51c746124354,humidity,50.5,2019-10-11T11:51:08.843+02:00,2019-10-11T11:51:08.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:08.846+02:00,null
|
||||
6beb6aee-6888-4f4a-82ff-5bf700a22b0a,humidity,50.5,2019-10-11T11:51:09.986+02:00,2019-10-11T11:51:09.986+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:09.989+02:00,null
|
||||
62a8f169-344d-4f9c-828a-7b4ea4261fad,humidity,50.5,2019-10-11T11:51:11.129+02:00,2019-10-11T11:51:11.13+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.133+02:00,null
|
||||
b1d64d40-59d7-4093-ba1d-208d869c9aae,humidity,50.5,2019-10-11T11:51:11.264+02:00,2019-10-11T11:51:11.265+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.267+02:00,null
|
||||
f98b69ee-bef9-4609-8bfa-82f11d7f51b6,humidity,51,2019-10-11T11:51:12.401+02:00,2019-10-11T11:51:12.402+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:12.41+02:00,null
|
||||
eab34e23-2b43-40c2-9d6e-90e72fa13db5,humidity,50.5,2019-10-11T11:51:13.551+02:00,2019-10-11T11:51:13.551+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:13.554+02:00,null
|
||||
4659b946-8a40-4ba4-9888-6997bd1795f2,humidity,50.5,2019-10-11T11:51:14.695+02:00,2019-10-11T11:51:14.695+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:14.698+02:00,null
|
||||
a6552aff-724c-4339-810a-36d911ddaa58,humidity,50.5,2019-10-11T11:51:15.839+02:00,2019-10-11T11:51:15.839+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.842+02:00,null
|
||||
c311cacd-e346-4b68-902b-400be0f8e02e,humidity,50.5,2019-10-11T11:51:15.976+02:00,2019-10-11T11:51:15.977+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.98+02:00,null
|
||||
2d4db614-17f8-4ddd-ba08-e2a5f77d74ea,humidity,50.5,2019-10-11T11:51:17.118+02:00,2019-10-11T11:51:17.118+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.121+02:00,null
|
||||
936d61e4-494e-4959-a39a-c1dbc4a7d162,humidity,50.5,2019-10-11T11:51:17.243+02:00,2019-10-11T11:51:17.244+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.247+02:00,null
|
||||
b913c9ca-4b5e-4c01-9aea-ee31d0d97977,humidity,50.5,2019-10-11T11:51:18.381+02:00,2019-10-11T11:51:18.382+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:18.385+02:00,null
|
||||
1171d0dd-2173-4da2-b2c6-7f7daaad7cd1,humidity,50.5,2019-10-11T11:51:19.529+02:00,2019-10-11T11:51:19.529+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.532+02:00,null
|
||||
d1a5e29c-d7e7-4ccc-9ff7-80ba1f797312,humidity,50.5,2019-10-11T11:51:19.664+02:00,2019-10-11T11:51:19.664+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.667+02:00,null
|
|
@ -1,50 +0,0 @@
|
||||
2029cc8c-56cf-4cd1-adf8-06e768fbad29,temperature,20.90999984741211,2019-10-11T11:50:43.317+02:00,2019-10-11T11:50:43.317+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:43.319+02:00,null
|
||||
a588cc90-4dca-4a4e-be81-2d20b9f64458,temperature,20.920000076293945,2019-10-11T11:50:44.459+02:00,2019-10-11T11:50:47.745+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:44.473+02:00,2019-10-11T11:51:01.71+02:00
|
||||
88f5c095-8e88-4c80-b66a-cf154919afa8,temperature,20.93000030517578,2019-10-11T11:50:49.53+02:00,2019-10-11T11:50:49.53+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:50.018+02:00,null
|
||||
7c3b7eac-3b43-4461-b204-d74c2bde3a08,temperature,20.920000076293945,2019-10-11T11:50:51.187+02:00,2019-10-11T11:50:53.652+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:51.189+02:00,2019-10-11T11:51:01.71+02:00
|
||||
acb55d68-d909-41ba-a445-8ef9eab0c7aa,temperature,20.950000762939453,2019-10-11T11:50:53.788+02:00,2019-10-11T11:50:53.789+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:53.79+02:00,null
|
||||
6563ff1e-371b-46a2-aca8-cc49b418bf61,temperature,20.93000030517578,2019-10-11T11:50:54.929+02:00,2019-10-11T11:50:55.536+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.405+02:00,2019-10-11T11:51:01.71+02:00
|
||||
157eda79-3283-4cda-a21d-db0aa2da8548,temperature,20.959999084472656,2019-10-11T11:50:55.674+02:00,2019-10-11T11:50:55.674+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.677+02:00,null
|
||||
c7d8d6ce-59d9-4448-9934-69cebc071cf6,temperature,20.940000534057617,2019-10-11T11:50:56.817+02:00,2019-10-11T11:50:56.817+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:56.82+02:00,null
|
||||
07d08037-7267-44ab-8d90-878271809a4c,temperature,20.93000030517578,2019-10-11T11:50:57.959+02:00,2019-10-11T11:50:59.1+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:57.962+02:00,2019-10-11T11:51:01.71+02:00
|
||||
0b865113-d0f4-42f4-af3d-50e60edcc523,temperature,20.959999084472656,2019-10-11T11:50:59.238+02:00,2019-10-11T11:50:59.238+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.241+02:00,null
|
||||
c40ad502-b6f1-49d8-8b6d-e7d5e475a6e2,temperature,20.979999542236328,2019-10-11T11:50:59.368+02:00,2019-10-11T11:50:59.368+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.371+02:00,null
|
||||
8d98e723-7a07-4ccf-b3bc-7a2653952b8b,temperature,20.920000076293945,2019-10-11T11:51:00.509+02:00,2019-10-11T11:51:01.658+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:00.516+02:00,2019-10-11T11:51:01.71+02:00
|
||||
0fc9c4bf-7e86-4cee-b99c-7642984b5fcd,humidity,50.8740234375,2019-10-11T11:51:01.969+02:00,2019-10-11T11:51:03.109+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.971+02:00,2019-10-11T11:51:20.729+02:00
|
||||
7b4f0fc3-ecec-4226-b621-49dd01cf9eb7,humidity,50.884765625,2019-10-11T11:51:04.251+02:00,2019-10-11T11:51:04.252+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:04.254+02:00,null
|
||||
72ccf0a7-e680-460c-a3b8-7d14f46b5b0c,humidity,50.861328125,2019-10-11T11:51:05.402+02:00,2019-10-11T11:51:05.403+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:05.405+02:00,null
|
||||
6f5277d9-abe3-4e87-b0f1-e4320756bbac,humidity,50.8505859375,2019-10-11T11:51:06.551+02:00,2019-10-11T11:51:06.552+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:06.565+02:00,null
|
||||
026370e8-2011-4bf0-8da9-ec4a9fe1ee19,humidity,50.7734375,2019-10-11T11:51:07.7+02:00,2019-10-11T11:51:07.7+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:07.702+02:00,null
|
||||
e96eff46-8322-424d-9a79-51c746124354,humidity,50.728515625,2019-10-11T11:51:08.843+02:00,2019-10-11T11:51:08.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:08.846+02:00,null
|
||||
6beb6aee-6888-4f4a-82ff-5bf700a22b0a,humidity,50.67578125,2019-10-11T11:51:09.986+02:00,2019-10-11T11:51:09.986+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:09.989+02:00,null
|
||||
62a8f169-344d-4f9c-828a-7b4ea4261fad,humidity,50.6650390625,2019-10-11T11:51:11.129+02:00,2019-10-11T11:51:11.13+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.133+02:00,null
|
||||
b1d64d40-59d7-4093-ba1d-208d869c9aae,humidity,50.6884765625,2019-10-11T11:51:11.264+02:00,2019-10-11T11:51:11.265+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.267+02:00,null
|
||||
f98b69ee-bef9-4609-8bfa-82f11d7f51b6,humidity,50.76171875,2019-10-11T11:51:12.401+02:00,2019-10-11T11:51:12.402+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:12.41+02:00,null
|
||||
eab34e23-2b43-40c2-9d6e-90e72fa13db5,humidity,50.7373046875,2019-10-11T11:51:13.551+02:00,2019-10-11T11:51:13.551+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:13.554+02:00,null
|
||||
4659b946-8a40-4ba4-9888-6997bd1795f2,humidity,50.73828125,2019-10-11T11:51:14.695+02:00,2019-10-11T11:51:14.695+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:14.698+02:00,null
|
||||
a6552aff-724c-4339-810a-36d911ddaa58,humidity,50.6845703125,2019-10-11T11:51:15.839+02:00,2019-10-11T11:51:15.839+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.842+02:00,null
|
||||
c311cacd-e346-4b68-902b-400be0f8e02e,humidity,50.6767578125,2019-10-11T11:51:15.976+02:00,2019-10-11T11:51:15.977+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.98+02:00,null
|
||||
2d4db614-17f8-4ddd-ba08-e2a5f77d74ea,humidity,50.68359375,2019-10-11T11:51:17.118+02:00,2019-10-11T11:51:17.118+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.121+02:00,null
|
||||
936d61e4-494e-4959-a39a-c1dbc4a7d162,humidity,50.685546875,2019-10-11T11:51:17.243+02:00,2019-10-11T11:51:17.244+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.247+02:00,null
|
||||
b913c9ca-4b5e-4c01-9aea-ee31d0d97977,humidity,50.6943359375,2019-10-11T11:51:18.381+02:00,2019-10-11T11:51:19.529+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:18.385+02:00,2019-10-11T11:51:20.729+02:00
|
||||
d1a5e29c-d7e7-4ccc-9ff7-80ba1f797312,humidity,50.7080078125,2019-10-11T11:51:19.664+02:00,2019-10-11T11:51:19.664+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.667+02:00,null
|
||||
140d6183-36c8-4903-a798-def4fcb3bd74,pressure,100150.5,2019-10-11T11:51:20.98+02:00,2019-10-11T11:51:20.98+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:20.982+02:00,null
|
||||
98102a36-a38e-4ccc-9886-c119a7971414,pressure,100151.703125,2019-10-11T11:51:22.123+02:00,2019-10-11T11:51:22.123+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:22.125+02:00,null
|
||||
5ffe51ae-687c-47df-8936-7785de9beed7,pressure,100150.8984375,2019-10-11T11:51:23.261+02:00,2019-10-11T11:51:23.262+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:23.263+02:00,null
|
||||
e534cb23-c725-403b-b5c9-5455256a8662,pressure,100153.203125,2019-10-11T11:51:23.739+02:00,2019-10-11T11:51:23.739+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.594+02:00,null
|
||||
e3c2a10b-373f-43d5-b02c-b381d1ad203b,pressure,100149.296875,2019-10-11T11:51:24.721+02:00,2019-10-11T11:51:24.721+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.723+02:00,null
|
||||
e9fbdd79-10ca-4fda-aed4-6fa2dbf3ff94,pressure,100150.796875,2019-10-11T11:51:24.849+02:00,2019-10-11T11:51:24.849+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:25.212+02:00,null
|
||||
70f6de4d-3a17-4688-b635-b8965e85be43,pressure,100149.6015625,2019-10-11T11:51:26.352+02:00,2019-10-11T11:51:26.352+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.354+02:00,null
|
||||
0e42478b-da36-4096-afad-d8c2a7685ccd,pressure,100149.5,2019-10-11T11:51:26.488+02:00,2019-10-11T11:51:26.488+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.49+02:00,null
|
||||
0313994c-db52-42d6-b059-2ca13afdd5d8,pressure,100151.3984375,2019-10-11T11:51:26.622+02:00,2019-10-11T11:51:26.622+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.624+02:00,null
|
||||
9cb66b4c-31d8-40dc-8c5b-9d243c2bb974,pressure,100150.203125,2019-10-11T11:51:26.754+02:00,2019-10-11T11:51:26.754+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.757+02:00,null
|
||||
6e626fdb-326d-4984-9585-7b64976be1a2,pressure,100150.5,2019-10-11T11:51:26.894+02:00,2019-10-11T11:51:26.894+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.896+02:00,null
|
||||
6869ae37-0200-40c2-85a5-d4412d81b62d,pressure,100154.1015625,2019-10-11T11:51:27.029+02:00,2019-10-11T11:51:27.029+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:27.032+02:00,null
|
||||
8a0770b9-f8b0-42de-90fe-3e6a6311818b,pressure,100152.3984375,2019-10-11T11:51:28.169+02:00,2019-10-11T11:51:28.169+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.171+02:00,null
|
||||
cf989dbb-2e73-4042-b339-b4dbc75b0a7c,pressure,100151.3984375,2019-10-11T11:51:28.294+02:00,2019-10-11T11:51:28.294+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.296+02:00,null
|
||||
53a2d8fb-3b15-48a2-a277-1b821adba088,pressure,100151.1015625,2019-10-11T11:51:28.426+02:00,2019-10-11T11:51:28.426+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.428+02:00,null
|
||||
9de16913-67f3-4e0b-9976-ddadbd057bae,pressure,100153.8984375,2019-10-11T11:51:29.563+02:00,2019-10-11T11:51:29.563+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:29.565+02:00,null
|
||||
d8c255a5-c5bd-4e9e-9aec-d0e6ae795719,pressure,100154.6015625,2019-10-11T11:51:30.707+02:00,2019-10-11T11:51:30.707+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:30.71+02:00,null
|
||||
31ab31ac-e991-4081-8b4e-eba0f789b806,pressure,100153.796875,2019-10-11T11:51:31.843+02:00,2019-10-11T11:51:31.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:31.846+02:00,null
|
||||
ed2ec0a9-9bc1-424a-9edf-85aad7a059ec,pressure,100150.703125,2019-10-11T11:51:32.981+02:00,2019-10-11T11:51:32.981+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:32.984+02:00,null
|
||||
31e03c4c-49a1-4c0d-a750-16c09b75f96b,pressure,100151.203125,2019-10-11T11:51:34.125+02:00,2019-10-11T11:51:34.126+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:34.133+02:00,null
|
|
@ -1,24 +0,0 @@
|
||||
2029cc8c-56cf-4cd1-adf8-06e768fbad29,temperature,21,2019-10-11T11:50:43.317+02:00,2019-10-11T11:51:01.658+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:43.319+02:00,2019-10-11T11:51:01.735+02:00
|
||||
0fc9c4bf-7e86-4cee-b99c-7642984b5fcd,humidity,51,2019-10-11T11:51:01.969+02:00,2019-10-11T11:51:07.7+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.971+02:00,2019-10-11T11:51:20.754+02:00
|
||||
e96eff46-8322-424d-9a79-51c746124354,humidity,50.5,2019-10-11T11:51:08.843+02:00,2019-10-11T11:51:11.265+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:08.846+02:00,2019-10-11T11:51:20.754+02:00
|
||||
f98b69ee-bef9-4609-8bfa-82f11d7f51b6,humidity,51,2019-10-11T11:51:12.401+02:00,2019-10-11T11:51:12.402+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:12.41+02:00,null
|
||||
eab34e23-2b43-40c2-9d6e-90e72fa13db5,humidity,50.5,2019-10-11T11:51:13.551+02:00,2019-10-11T11:51:19.664+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:13.554+02:00,2019-10-11T11:51:20.754+02:00
|
||||
140d6183-36c8-4903-a798-def4fcb3bd74,pressure,100150.5,2019-10-11T11:51:20.98+02:00,2019-10-11T11:51:20.98+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:20.982+02:00,null
|
||||
98102a36-a38e-4ccc-9886-c119a7971414,pressure,100151.5,2019-10-11T11:51:22.123+02:00,2019-10-11T11:51:22.123+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:22.125+02:00,null
|
||||
5ffe51ae-687c-47df-8936-7785de9beed7,pressure,100151,2019-10-11T11:51:23.261+02:00,2019-10-11T11:51:23.262+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:23.263+02:00,null
|
||||
e534cb23-c725-403b-b5c9-5455256a8662,pressure,100153,2019-10-11T11:51:23.739+02:00,2019-10-11T11:51:23.739+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.594+02:00,null
|
||||
e3c2a10b-373f-43d5-b02c-b381d1ad203b,pressure,100149.5,2019-10-11T11:51:24.721+02:00,2019-10-11T11:51:24.721+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.723+02:00,null
|
||||
e9fbdd79-10ca-4fda-aed4-6fa2dbf3ff94,pressure,100151,2019-10-11T11:51:24.849+02:00,2019-10-11T11:51:24.849+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:25.212+02:00,null
|
||||
70f6de4d-3a17-4688-b635-b8965e85be43,pressure,100149.5,2019-10-11T11:51:26.352+02:00,2019-10-11T11:51:26.488+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.354+02:00,2019-10-11T11:51:35.22+02:00
|
||||
0313994c-db52-42d6-b059-2ca13afdd5d8,pressure,100151.5,2019-10-11T11:51:26.622+02:00,2019-10-11T11:51:26.622+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.624+02:00,null
|
||||
9cb66b4c-31d8-40dc-8c5b-9d243c2bb974,pressure,100150,2019-10-11T11:51:26.754+02:00,2019-10-11T11:51:26.754+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.757+02:00,null
|
||||
6e626fdb-326d-4984-9585-7b64976be1a2,pressure,100150.5,2019-10-11T11:51:26.894+02:00,2019-10-11T11:51:26.894+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.896+02:00,null
|
||||
6869ae37-0200-40c2-85a5-d4412d81b62d,pressure,100154,2019-10-11T11:51:27.029+02:00,2019-10-11T11:51:27.029+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:27.032+02:00,null
|
||||
8a0770b9-f8b0-42de-90fe-3e6a6311818b,pressure,100152.5,2019-10-11T11:51:28.169+02:00,2019-10-11T11:51:28.169+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.171+02:00,null
|
||||
cf989dbb-2e73-4042-b339-b4dbc75b0a7c,pressure,100151.5,2019-10-11T11:51:28.294+02:00,2019-10-11T11:51:28.294+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.296+02:00,null
|
||||
53a2d8fb-3b15-48a2-a277-1b821adba088,pressure,100151,2019-10-11T11:51:28.426+02:00,2019-10-11T11:51:28.426+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.428+02:00,null
|
||||
9de16913-67f3-4e0b-9976-ddadbd057bae,pressure,100154,2019-10-11T11:51:29.563+02:00,2019-10-11T11:51:29.563+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:29.565+02:00,null
|
||||
d8c255a5-c5bd-4e9e-9aec-d0e6ae795719,pressure,100154.5,2019-10-11T11:51:30.707+02:00,2019-10-11T11:51:30.707+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:30.71+02:00,null
|
||||
31ab31ac-e991-4081-8b4e-eba0f789b806,pressure,100154,2019-10-11T11:51:31.843+02:00,2019-10-11T11:51:31.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:31.846+02:00,null
|
||||
ed2ec0a9-9bc1-424a-9edf-85aad7a059ec,pressure,100150.5,2019-10-11T11:51:32.981+02:00,2019-10-11T11:51:32.981+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:32.984+02:00,null
|
||||
31e03c4c-49a1-4c0d-a750-16c09b75f96b,pressure,100151,2019-10-11T11:51:34.125+02:00,2019-10-11T11:51:34.126+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:34.133+02:00,null
|
|
@ -1,60 +0,0 @@
|
||||
2029cc8c-56cf-4cd1-adf8-06e768fbad29,temperature,20.90999984741211,2019-10-11T11:50:43.317+02:00,2019-10-11T11:50:43.317+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:43.319+02:00,null
|
||||
a588cc90-4dca-4a4e-be81-2d20b9f64458,temperature,20.920000076293945,2019-10-11T11:50:44.459+02:00,2019-10-11T11:50:44.459+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:44.473+02:00,null
|
||||
1ebb60de-6066-4731-9f55-ee1c51748b7f,temperature,20.920000076293945,2019-10-11T11:50:45.614+02:00,2019-10-11T11:50:45.614+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:45.615+02:00,null
|
||||
6cbc586f-a5c9-4296-b3b2-200afae12059,temperature,20.920000076293945,2019-10-11T11:50:46.776+02:00,2019-10-11T11:50:46.776+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:47.134+02:00,null
|
||||
14e03e32-f214-4f61-97b0-8e021c47bead,temperature,20.920000076293945,2019-10-11T11:50:47.745+02:00,2019-10-11T11:50:47.745+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:47.747+02:00,null
|
||||
88f5c095-8e88-4c80-b66a-cf154919afa8,temperature,20.93000030517578,2019-10-11T11:50:49.53+02:00,2019-10-11T11:50:49.53+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:50.018+02:00,null
|
||||
7c3b7eac-3b43-4461-b204-d74c2bde3a08,temperature,20.920000076293945,2019-10-11T11:50:51.187+02:00,2019-10-11T11:50:51.187+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:51.189+02:00,null
|
||||
3caf0b08-38d5-4536-baa5-86d4d75e359d,temperature,20.920000076293945,2019-10-11T11:50:52.508+02:00,2019-10-11T11:50:52.508+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:52.51+02:00,null
|
||||
4b8595bf-3624-4963-b796-efc648515cd9,temperature,20.920000076293945,2019-10-11T11:50:53.652+02:00,2019-10-11T11:50:53.652+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:53.654+02:00,null
|
||||
acb55d68-d909-41ba-a445-8ef9eab0c7aa,temperature,20.950000762939453,2019-10-11T11:50:53.788+02:00,2019-10-11T11:50:53.789+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:53.79+02:00,null
|
||||
6563ff1e-371b-46a2-aca8-cc49b418bf61,temperature,20.93000030517578,2019-10-11T11:50:54.929+02:00,2019-10-11T11:50:54.93+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.405+02:00,null
|
||||
a64f57c4-b95c-45b1-b78a-a6be263abb85,temperature,20.93000030517578,2019-10-11T11:50:55.536+02:00,2019-10-11T11:50:55.536+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.538+02:00,null
|
||||
157eda79-3283-4cda-a21d-db0aa2da8548,temperature,20.959999084472656,2019-10-11T11:50:55.674+02:00,2019-10-11T11:50:55.674+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.677+02:00,null
|
||||
c7d8d6ce-59d9-4448-9934-69cebc071cf6,temperature,20.940000534057617,2019-10-11T11:50:56.817+02:00,2019-10-11T11:50:56.817+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:56.82+02:00,null
|
||||
07d08037-7267-44ab-8d90-878271809a4c,temperature,20.93000030517578,2019-10-11T11:50:57.959+02:00,2019-10-11T11:50:57.96+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:57.962+02:00,null
|
||||
bf2e981f-8bad-466e-9a12-c1a5d9b64399,temperature,20.93000030517578,2019-10-11T11:50:59.1+02:00,2019-10-11T11:50:59.1+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.103+02:00,null
|
||||
0b865113-d0f4-42f4-af3d-50e60edcc523,temperature,20.959999084472656,2019-10-11T11:50:59.238+02:00,2019-10-11T11:50:59.238+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.241+02:00,null
|
||||
c40ad502-b6f1-49d8-8b6d-e7d5e475a6e2,temperature,20.979999542236328,2019-10-11T11:50:59.368+02:00,2019-10-11T11:50:59.368+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.371+02:00,null
|
||||
8d98e723-7a07-4ccf-b3bc-7a2653952b8b,temperature,20.920000076293945,2019-10-11T11:51:00.509+02:00,2019-10-11T11:51:00.51+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:00.516+02:00,null
|
||||
f61b7bfb-14eb-4b43-a7b6-47b61fa46dda,temperature,20.920000076293945,2019-10-11T11:51:01.658+02:00,2019-10-11T11:51:01.658+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.661+02:00,null
|
||||
0fc9c4bf-7e86-4cee-b99c-7642984b5fcd,humidity,50.8740234375,2019-10-11T11:51:01.969+02:00,2019-10-11T11:51:01.969+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.971+02:00,null
|
||||
45e8b20c-1ab0-4cf1-9ef1-aa82537557f6,humidity,50.8740234375,2019-10-11T11:51:03.109+02:00,2019-10-11T11:51:03.109+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:03.111+02:00,null
|
||||
7b4f0fc3-ecec-4226-b621-49dd01cf9eb7,humidity,50.884765625,2019-10-11T11:51:04.251+02:00,2019-10-11T11:51:04.252+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:04.254+02:00,null
|
||||
72ccf0a7-e680-460c-a3b8-7d14f46b5b0c,humidity,50.861328125,2019-10-11T11:51:05.402+02:00,2019-10-11T11:51:05.403+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:05.405+02:00,null
|
||||
6f5277d9-abe3-4e87-b0f1-e4320756bbac,humidity,50.8505859375,2019-10-11T11:51:06.551+02:00,2019-10-11T11:51:06.552+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:06.565+02:00,null
|
||||
026370e8-2011-4bf0-8da9-ec4a9fe1ee19,humidity,50.7734375,2019-10-11T11:51:07.7+02:00,2019-10-11T11:51:07.7+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:07.702+02:00,null
|
||||
e96eff46-8322-424d-9a79-51c746124354,humidity,50.728515625,2019-10-11T11:51:08.843+02:00,2019-10-11T11:51:08.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:08.846+02:00,null
|
||||
6beb6aee-6888-4f4a-82ff-5bf700a22b0a,humidity,50.67578125,2019-10-11T11:51:09.986+02:00,2019-10-11T11:51:09.986+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:09.989+02:00,null
|
||||
62a8f169-344d-4f9c-828a-7b4ea4261fad,humidity,50.6650390625,2019-10-11T11:51:11.129+02:00,2019-10-11T11:51:11.13+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.133+02:00,null
|
||||
b1d64d40-59d7-4093-ba1d-208d869c9aae,humidity,50.6884765625,2019-10-11T11:51:11.264+02:00,2019-10-11T11:51:11.265+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.267+02:00,null
|
||||
f98b69ee-bef9-4609-8bfa-82f11d7f51b6,humidity,50.76171875,2019-10-11T11:51:12.401+02:00,2019-10-11T11:51:12.402+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:12.41+02:00,null
|
||||
eab34e23-2b43-40c2-9d6e-90e72fa13db5,humidity,50.7373046875,2019-10-11T11:51:13.551+02:00,2019-10-11T11:51:13.551+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:13.554+02:00,null
|
||||
4659b946-8a40-4ba4-9888-6997bd1795f2,humidity,50.73828125,2019-10-11T11:51:14.695+02:00,2019-10-11T11:51:14.695+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:14.698+02:00,null
|
||||
a6552aff-724c-4339-810a-36d911ddaa58,humidity,50.6845703125,2019-10-11T11:51:15.839+02:00,2019-10-11T11:51:15.839+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.842+02:00,null
|
||||
c311cacd-e346-4b68-902b-400be0f8e02e,humidity,50.6767578125,2019-10-11T11:51:15.976+02:00,2019-10-11T11:51:15.977+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.98+02:00,null
|
||||
2d4db614-17f8-4ddd-ba08-e2a5f77d74ea,humidity,50.68359375,2019-10-11T11:51:17.118+02:00,2019-10-11T11:51:17.118+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.121+02:00,null
|
||||
936d61e4-494e-4959-a39a-c1dbc4a7d162,humidity,50.685546875,2019-10-11T11:51:17.243+02:00,2019-10-11T11:51:17.244+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.247+02:00,null
|
||||
b913c9ca-4b5e-4c01-9aea-ee31d0d97977,humidity,50.6943359375,2019-10-11T11:51:18.381+02:00,2019-10-11T11:51:18.382+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:18.385+02:00,null
|
||||
1171d0dd-2173-4da2-b2c6-7f7daaad7cd1,humidity,50.6943359375,2019-10-11T11:51:19.529+02:00,2019-10-11T11:51:19.529+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.532+02:00,null
|
||||
d1a5e29c-d7e7-4ccc-9ff7-80ba1f797312,humidity,50.7080078125,2019-10-11T11:51:19.664+02:00,2019-10-11T11:51:19.664+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.667+02:00,null
|
||||
140d6183-36c8-4903-a798-def4fcb3bd74,pressure,100150.5,2019-10-11T11:51:20.98+02:00,2019-10-11T11:51:20.98+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:20.982+02:00,null
|
||||
98102a36-a38e-4ccc-9886-c119a7971414,pressure,100151.703125,2019-10-11T11:51:22.123+02:00,2019-10-11T11:51:22.123+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:22.125+02:00,null
|
||||
5ffe51ae-687c-47df-8936-7785de9beed7,pressure,100150.8984375,2019-10-11T11:51:23.261+02:00,2019-10-11T11:51:23.262+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:23.263+02:00,null
|
||||
e534cb23-c725-403b-b5c9-5455256a8662,pressure,100153.203125,2019-10-11T11:51:23.739+02:00,2019-10-11T11:51:23.739+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.594+02:00,null
|
||||
e3c2a10b-373f-43d5-b02c-b381d1ad203b,pressure,100149.296875,2019-10-11T11:51:24.721+02:00,2019-10-11T11:51:24.721+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.723+02:00,null
|
||||
e9fbdd79-10ca-4fda-aed4-6fa2dbf3ff94,pressure,100150.796875,2019-10-11T11:51:24.849+02:00,2019-10-11T11:51:24.849+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:25.212+02:00,null
|
||||
70f6de4d-3a17-4688-b635-b8965e85be43,pressure,100149.6015625,2019-10-11T11:51:26.352+02:00,2019-10-11T11:51:26.352+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.354+02:00,null
|
||||
0e42478b-da36-4096-afad-d8c2a7685ccd,pressure,100149.5,2019-10-11T11:51:26.488+02:00,2019-10-11T11:51:26.488+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.49+02:00,null
|
||||
0313994c-db52-42d6-b059-2ca13afdd5d8,pressure,100151.3984375,2019-10-11T11:51:26.622+02:00,2019-10-11T11:51:26.622+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.624+02:00,null
|
||||
9cb66b4c-31d8-40dc-8c5b-9d243c2bb974,pressure,100150.203125,2019-10-11T11:51:26.754+02:00,2019-10-11T11:51:26.754+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.757+02:00,null
|
||||
6e626fdb-326d-4984-9585-7b64976be1a2,pressure,100150.5,2019-10-11T11:51:26.894+02:00,2019-10-11T11:51:26.894+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.896+02:00,null
|
||||
6869ae37-0200-40c2-85a5-d4412d81b62d,pressure,100154.1015625,2019-10-11T11:51:27.029+02:00,2019-10-11T11:51:27.029+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:27.032+02:00,null
|
||||
8a0770b9-f8b0-42de-90fe-3e6a6311818b,pressure,100152.3984375,2019-10-11T11:51:28.169+02:00,2019-10-11T11:51:28.169+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.171+02:00,null
|
||||
cf989dbb-2e73-4042-b339-b4dbc75b0a7c,pressure,100151.3984375,2019-10-11T11:51:28.294+02:00,2019-10-11T11:51:28.294+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.296+02:00,null
|
||||
53a2d8fb-3b15-48a2-a277-1b821adba088,pressure,100151.1015625,2019-10-11T11:51:28.426+02:00,2019-10-11T11:51:28.426+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.428+02:00,null
|
||||
9de16913-67f3-4e0b-9976-ddadbd057bae,pressure,100153.8984375,2019-10-11T11:51:29.563+02:00,2019-10-11T11:51:29.563+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:29.565+02:00,null
|
||||
d8c255a5-c5bd-4e9e-9aec-d0e6ae795719,pressure,100154.6015625,2019-10-11T11:51:30.707+02:00,2019-10-11T11:51:30.707+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:30.71+02:00,null
|
||||
31ab31ac-e991-4081-8b4e-eba0f789b806,pressure,100153.796875,2019-10-11T11:51:31.843+02:00,2019-10-11T11:51:31.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:31.846+02:00,null
|
||||
ed2ec0a9-9bc1-424a-9edf-85aad7a059ec,pressure,100150.703125,2019-10-11T11:51:32.981+02:00,2019-10-11T11:51:32.981+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:32.984+02:00,null
|
||||
31e03c4c-49a1-4c0d-a750-16c09b75f96b,pressure,100151.203125,2019-10-11T11:51:34.125+02:00,2019-10-11T11:51:34.126+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:34.133+02:00,null
|
|
@ -1,60 +0,0 @@
|
||||
2029cc8c-56cf-4cd1-adf8-06e768fbad29,temperature,21,2019-10-11T11:50:43.317+02:00,2019-10-11T11:50:43.317+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:43.319+02:00,null
|
||||
a588cc90-4dca-4a4e-be81-2d20b9f64458,temperature,21,2019-10-11T11:50:44.459+02:00,2019-10-11T11:50:44.459+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:44.473+02:00,null
|
||||
1ebb60de-6066-4731-9f55-ee1c51748b7f,temperature,21,2019-10-11T11:50:45.614+02:00,2019-10-11T11:50:45.614+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:45.615+02:00,null
|
||||
6cbc586f-a5c9-4296-b3b2-200afae12059,temperature,21,2019-10-11T11:50:46.776+02:00,2019-10-11T11:50:46.776+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:47.134+02:00,null
|
||||
14e03e32-f214-4f61-97b0-8e021c47bead,temperature,21,2019-10-11T11:50:47.745+02:00,2019-10-11T11:50:47.745+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:47.747+02:00,null
|
||||
88f5c095-8e88-4c80-b66a-cf154919afa8,temperature,21,2019-10-11T11:50:49.53+02:00,2019-10-11T11:50:49.53+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:50.018+02:00,null
|
||||
7c3b7eac-3b43-4461-b204-d74c2bde3a08,temperature,21,2019-10-11T11:50:51.187+02:00,2019-10-11T11:50:51.187+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:51.189+02:00,null
|
||||
3caf0b08-38d5-4536-baa5-86d4d75e359d,temperature,21,2019-10-11T11:50:52.508+02:00,2019-10-11T11:50:52.508+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:52.51+02:00,null
|
||||
4b8595bf-3624-4963-b796-efc648515cd9,temperature,21,2019-10-11T11:50:53.652+02:00,2019-10-11T11:50:53.652+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:53.654+02:00,null
|
||||
acb55d68-d909-41ba-a445-8ef9eab0c7aa,temperature,21,2019-10-11T11:50:53.788+02:00,2019-10-11T11:50:53.789+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:53.79+02:00,null
|
||||
6563ff1e-371b-46a2-aca8-cc49b418bf61,temperature,21,2019-10-11T11:50:54.929+02:00,2019-10-11T11:50:54.93+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.405+02:00,null
|
||||
a64f57c4-b95c-45b1-b78a-a6be263abb85,temperature,21,2019-10-11T11:50:55.536+02:00,2019-10-11T11:50:55.536+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.538+02:00,null
|
||||
157eda79-3283-4cda-a21d-db0aa2da8548,temperature,21,2019-10-11T11:50:55.674+02:00,2019-10-11T11:50:55.674+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.677+02:00,null
|
||||
c7d8d6ce-59d9-4448-9934-69cebc071cf6,temperature,21,2019-10-11T11:50:56.817+02:00,2019-10-11T11:50:56.817+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:56.82+02:00,null
|
||||
07d08037-7267-44ab-8d90-878271809a4c,temperature,21,2019-10-11T11:50:57.959+02:00,2019-10-11T11:50:57.96+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:57.962+02:00,null
|
||||
bf2e981f-8bad-466e-9a12-c1a5d9b64399,temperature,21,2019-10-11T11:50:59.1+02:00,2019-10-11T11:50:59.1+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.103+02:00,null
|
||||
0b865113-d0f4-42f4-af3d-50e60edcc523,temperature,21,2019-10-11T11:50:59.238+02:00,2019-10-11T11:50:59.238+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.241+02:00,null
|
||||
c40ad502-b6f1-49d8-8b6d-e7d5e475a6e2,temperature,21,2019-10-11T11:50:59.368+02:00,2019-10-11T11:50:59.368+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.371+02:00,null
|
||||
8d98e723-7a07-4ccf-b3bc-7a2653952b8b,temperature,21,2019-10-11T11:51:00.509+02:00,2019-10-11T11:51:00.51+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:00.516+02:00,null
|
||||
f61b7bfb-14eb-4b43-a7b6-47b61fa46dda,temperature,21,2019-10-11T11:51:01.658+02:00,2019-10-11T11:51:01.658+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.661+02:00,null
|
||||
0fc9c4bf-7e86-4cee-b99c-7642984b5fcd,humidity,51,2019-10-11T11:51:01.969+02:00,2019-10-11T11:51:01.969+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.971+02:00,null
|
||||
45e8b20c-1ab0-4cf1-9ef1-aa82537557f6,humidity,51,2019-10-11T11:51:03.109+02:00,2019-10-11T11:51:03.109+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:03.111+02:00,null
|
||||
7b4f0fc3-ecec-4226-b621-49dd01cf9eb7,humidity,51,2019-10-11T11:51:04.251+02:00,2019-10-11T11:51:04.252+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:04.254+02:00,null
|
||||
72ccf0a7-e680-460c-a3b8-7d14f46b5b0c,humidity,51,2019-10-11T11:51:05.402+02:00,2019-10-11T11:51:05.403+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:05.405+02:00,null
|
||||
6f5277d9-abe3-4e87-b0f1-e4320756bbac,humidity,51,2019-10-11T11:51:06.551+02:00,2019-10-11T11:51:06.552+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:06.565+02:00,null
|
||||
026370e8-2011-4bf0-8da9-ec4a9fe1ee19,humidity,51,2019-10-11T11:51:07.7+02:00,2019-10-11T11:51:07.7+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:07.702+02:00,null
|
||||
e96eff46-8322-424d-9a79-51c746124354,humidity,50.5,2019-10-11T11:51:08.843+02:00,2019-10-11T11:51:08.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:08.846+02:00,null
|
||||
6beb6aee-6888-4f4a-82ff-5bf700a22b0a,humidity,50.5,2019-10-11T11:51:09.986+02:00,2019-10-11T11:51:09.986+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:09.989+02:00,null
|
||||
62a8f169-344d-4f9c-828a-7b4ea4261fad,humidity,50.5,2019-10-11T11:51:11.129+02:00,2019-10-11T11:51:11.13+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.133+02:00,null
|
||||
b1d64d40-59d7-4093-ba1d-208d869c9aae,humidity,50.5,2019-10-11T11:51:11.264+02:00,2019-10-11T11:51:11.265+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:11.267+02:00,null
|
||||
f98b69ee-bef9-4609-8bfa-82f11d7f51b6,humidity,51,2019-10-11T11:51:12.401+02:00,2019-10-11T11:51:12.402+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:12.41+02:00,null
|
||||
eab34e23-2b43-40c2-9d6e-90e72fa13db5,humidity,50.5,2019-10-11T11:51:13.551+02:00,2019-10-11T11:51:13.551+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:13.554+02:00,null
|
||||
4659b946-8a40-4ba4-9888-6997bd1795f2,humidity,50.5,2019-10-11T11:51:14.695+02:00,2019-10-11T11:51:14.695+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:14.698+02:00,null
|
||||
a6552aff-724c-4339-810a-36d911ddaa58,humidity,50.5,2019-10-11T11:51:15.839+02:00,2019-10-11T11:51:15.839+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.842+02:00,null
|
||||
c311cacd-e346-4b68-902b-400be0f8e02e,humidity,50.5,2019-10-11T11:51:15.976+02:00,2019-10-11T11:51:15.977+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:15.98+02:00,null
|
||||
2d4db614-17f8-4ddd-ba08-e2a5f77d74ea,humidity,50.5,2019-10-11T11:51:17.118+02:00,2019-10-11T11:51:17.118+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.121+02:00,null
|
||||
936d61e4-494e-4959-a39a-c1dbc4a7d162,humidity,50.5,2019-10-11T11:51:17.243+02:00,2019-10-11T11:51:17.244+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:17.247+02:00,null
|
||||
b913c9ca-4b5e-4c01-9aea-ee31d0d97977,humidity,50.5,2019-10-11T11:51:18.381+02:00,2019-10-11T11:51:18.382+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:18.385+02:00,null
|
||||
1171d0dd-2173-4da2-b2c6-7f7daaad7cd1,humidity,50.5,2019-10-11T11:51:19.529+02:00,2019-10-11T11:51:19.529+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.532+02:00,null
|
||||
d1a5e29c-d7e7-4ccc-9ff7-80ba1f797312,humidity,50.5,2019-10-11T11:51:19.664+02:00,2019-10-11T11:51:19.664+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:19.667+02:00,null
|
||||
140d6183-36c8-4903-a798-def4fcb3bd74,pressure,100150.5,2019-10-11T11:51:20.98+02:00,2019-10-11T11:51:20.98+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:20.982+02:00,null
|
||||
98102a36-a38e-4ccc-9886-c119a7971414,pressure,100151.5,2019-10-11T11:51:22.123+02:00,2019-10-11T11:51:22.123+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:22.125+02:00,null
|
||||
5ffe51ae-687c-47df-8936-7785de9beed7,pressure,100151,2019-10-11T11:51:23.261+02:00,2019-10-11T11:51:23.262+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:23.263+02:00,null
|
||||
e534cb23-c725-403b-b5c9-5455256a8662,pressure,100153,2019-10-11T11:51:23.739+02:00,2019-10-11T11:51:23.739+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.594+02:00,null
|
||||
e3c2a10b-373f-43d5-b02c-b381d1ad203b,pressure,100149.5,2019-10-11T11:51:24.721+02:00,2019-10-11T11:51:24.721+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.723+02:00,null
|
||||
e9fbdd79-10ca-4fda-aed4-6fa2dbf3ff94,pressure,100151,2019-10-11T11:51:24.849+02:00,2019-10-11T11:51:24.849+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:25.212+02:00,null
|
||||
70f6de4d-3a17-4688-b635-b8965e85be43,pressure,100149.5,2019-10-11T11:51:26.352+02:00,2019-10-11T11:51:26.352+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.354+02:00,null
|
||||
0e42478b-da36-4096-afad-d8c2a7685ccd,pressure,100149.5,2019-10-11T11:51:26.488+02:00,2019-10-11T11:51:26.488+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.49+02:00,null
|
||||
0313994c-db52-42d6-b059-2ca13afdd5d8,pressure,100151.5,2019-10-11T11:51:26.622+02:00,2019-10-11T11:51:26.622+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.624+02:00,null
|
||||
9cb66b4c-31d8-40dc-8c5b-9d243c2bb974,pressure,100150,2019-10-11T11:51:26.754+02:00,2019-10-11T11:51:26.754+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.757+02:00,null
|
||||
6e626fdb-326d-4984-9585-7b64976be1a2,pressure,100150.5,2019-10-11T11:51:26.894+02:00,2019-10-11T11:51:26.894+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.896+02:00,null
|
||||
6869ae37-0200-40c2-85a5-d4412d81b62d,pressure,100154,2019-10-11T11:51:27.029+02:00,2019-10-11T11:51:27.029+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:27.032+02:00,null
|
||||
8a0770b9-f8b0-42de-90fe-3e6a6311818b,pressure,100152.5,2019-10-11T11:51:28.169+02:00,2019-10-11T11:51:28.169+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.171+02:00,null
|
||||
cf989dbb-2e73-4042-b339-b4dbc75b0a7c,pressure,100151.5,2019-10-11T11:51:28.294+02:00,2019-10-11T11:51:28.294+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.296+02:00,null
|
||||
53a2d8fb-3b15-48a2-a277-1b821adba088,pressure,100151,2019-10-11T11:51:28.426+02:00,2019-10-11T11:51:28.426+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.428+02:00,null
|
||||
9de16913-67f3-4e0b-9976-ddadbd057bae,pressure,100154,2019-10-11T11:51:29.563+02:00,2019-10-11T11:51:29.563+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:29.565+02:00,null
|
||||
d8c255a5-c5bd-4e9e-9aec-d0e6ae795719,pressure,100154.5,2019-10-11T11:51:30.707+02:00,2019-10-11T11:51:30.707+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:30.71+02:00,null
|
||||
31ab31ac-e991-4081-8b4e-eba0f789b806,pressure,100154,2019-10-11T11:51:31.843+02:00,2019-10-11T11:51:31.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:31.846+02:00,null
|
||||
ed2ec0a9-9bc1-424a-9edf-85aad7a059ec,pressure,100150.5,2019-10-11T11:51:32.981+02:00,2019-10-11T11:51:32.981+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:32.984+02:00,null
|
||||
31e03c4c-49a1-4c0d-a750-16c09b75f96b,pressure,100151,2019-10-11T11:51:34.125+02:00,2019-10-11T11:51:34.126+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:34.133+02:00,null
|
|
@ -1,20 +0,0 @@
|
||||
140d6183-36c8-4903-a798-def4fcb3bd74,pressure,100150.5,2019-10-11T11:51:20.98+02:00,2019-10-11T11:51:20.98+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:20.982+02:00,null
|
||||
98102a36-a38e-4ccc-9886-c119a7971414,pressure,100151.703125,2019-10-11T11:51:22.123+02:00,2019-10-11T11:51:22.123+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:22.125+02:00,null
|
||||
5ffe51ae-687c-47df-8936-7785de9beed7,pressure,100150.8984375,2019-10-11T11:51:23.261+02:00,2019-10-11T11:51:23.262+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:23.263+02:00,null
|
||||
e534cb23-c725-403b-b5c9-5455256a8662,pressure,100153.203125,2019-10-11T11:51:23.739+02:00,2019-10-11T11:51:23.739+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.594+02:00,null
|
||||
e3c2a10b-373f-43d5-b02c-b381d1ad203b,pressure,100149.296875,2019-10-11T11:51:24.721+02:00,2019-10-11T11:51:24.721+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.723+02:00,null
|
||||
e9fbdd79-10ca-4fda-aed4-6fa2dbf3ff94,pressure,100150.796875,2019-10-11T11:51:24.849+02:00,2019-10-11T11:51:24.849+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:25.212+02:00,null
|
||||
70f6de4d-3a17-4688-b635-b8965e85be43,pressure,100149.6015625,2019-10-11T11:51:26.352+02:00,2019-10-11T11:51:26.352+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.354+02:00,null
|
||||
0e42478b-da36-4096-afad-d8c2a7685ccd,pressure,100149.5,2019-10-11T11:51:26.488+02:00,2019-10-11T11:51:26.488+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.49+02:00,null
|
||||
0313994c-db52-42d6-b059-2ca13afdd5d8,pressure,100151.3984375,2019-10-11T11:51:26.622+02:00,2019-10-11T11:51:26.622+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.624+02:00,null
|
||||
9cb66b4c-31d8-40dc-8c5b-9d243c2bb974,pressure,100150.203125,2019-10-11T11:51:26.754+02:00,2019-10-11T11:51:26.754+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.757+02:00,null
|
||||
6e626fdb-326d-4984-9585-7b64976be1a2,pressure,100150.5,2019-10-11T11:51:26.894+02:00,2019-10-11T11:51:26.894+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.896+02:00,null
|
||||
6869ae37-0200-40c2-85a5-d4412d81b62d,pressure,100154.1015625,2019-10-11T11:51:27.029+02:00,2019-10-11T11:51:27.029+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:27.032+02:00,null
|
||||
8a0770b9-f8b0-42de-90fe-3e6a6311818b,pressure,100152.3984375,2019-10-11T11:51:28.169+02:00,2019-10-11T11:51:28.169+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.171+02:00,null
|
||||
cf989dbb-2e73-4042-b339-b4dbc75b0a7c,pressure,100151.3984375,2019-10-11T11:51:28.294+02:00,2019-10-11T11:51:28.294+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.296+02:00,null
|
||||
53a2d8fb-3b15-48a2-a277-1b821adba088,pressure,100151.1015625,2019-10-11T11:51:28.426+02:00,2019-10-11T11:51:28.426+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.428+02:00,null
|
||||
9de16913-67f3-4e0b-9976-ddadbd057bae,pressure,100153.8984375,2019-10-11T11:51:29.563+02:00,2019-10-11T11:51:29.563+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:29.565+02:00,null
|
||||
d8c255a5-c5bd-4e9e-9aec-d0e6ae795719,pressure,100154.6015625,2019-10-11T11:51:30.707+02:00,2019-10-11T11:51:30.707+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:30.71+02:00,null
|
||||
31ab31ac-e991-4081-8b4e-eba0f789b806,pressure,100153.796875,2019-10-11T11:51:31.843+02:00,2019-10-11T11:51:31.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:31.846+02:00,null
|
||||
ed2ec0a9-9bc1-424a-9edf-85aad7a059ec,pressure,100150.703125,2019-10-11T11:51:32.981+02:00,2019-10-11T11:51:32.981+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:32.984+02:00,null
|
||||
31e03c4c-49a1-4c0d-a750-16c09b75f96b,pressure,100151.203125,2019-10-11T11:51:34.125+02:00,2019-10-11T11:51:34.126+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:34.133+02:00,null
|
|
@ -1,19 +0,0 @@
|
||||
140d6183-36c8-4903-a798-def4fcb3bd74,pressure,100150.5,2019-10-11T11:51:20.98+02:00,2019-10-11T11:51:20.98+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:20.982+02:00,null
|
||||
98102a36-a38e-4ccc-9886-c119a7971414,pressure,100151.5,2019-10-11T11:51:22.123+02:00,2019-10-11T11:51:22.123+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:22.125+02:00,null
|
||||
5ffe51ae-687c-47df-8936-7785de9beed7,pressure,100151,2019-10-11T11:51:23.261+02:00,2019-10-11T11:51:23.262+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:23.263+02:00,null
|
||||
e534cb23-c725-403b-b5c9-5455256a8662,pressure,100153,2019-10-11T11:51:23.739+02:00,2019-10-11T11:51:23.739+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.594+02:00,null
|
||||
e3c2a10b-373f-43d5-b02c-b381d1ad203b,pressure,100149.5,2019-10-11T11:51:24.721+02:00,2019-10-11T11:51:24.721+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.723+02:00,null
|
||||
e9fbdd79-10ca-4fda-aed4-6fa2dbf3ff94,pressure,100151,2019-10-11T11:51:24.849+02:00,2019-10-11T11:51:24.849+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:25.212+02:00,null
|
||||
70f6de4d-3a17-4688-b635-b8965e85be43,pressure,100149.5,2019-10-11T11:51:26.352+02:00,2019-10-11T11:51:26.488+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.354+02:00,2019-10-11T11:51:35.22+02:00
|
||||
0313994c-db52-42d6-b059-2ca13afdd5d8,pressure,100151.5,2019-10-11T11:51:26.622+02:00,2019-10-11T11:51:26.622+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.624+02:00,null
|
||||
9cb66b4c-31d8-40dc-8c5b-9d243c2bb974,pressure,100150,2019-10-11T11:51:26.754+02:00,2019-10-11T11:51:26.754+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.757+02:00,null
|
||||
6e626fdb-326d-4984-9585-7b64976be1a2,pressure,100150.5,2019-10-11T11:51:26.894+02:00,2019-10-11T11:51:26.894+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.896+02:00,null
|
||||
6869ae37-0200-40c2-85a5-d4412d81b62d,pressure,100154,2019-10-11T11:51:27.029+02:00,2019-10-11T11:51:27.029+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:27.032+02:00,null
|
||||
8a0770b9-f8b0-42de-90fe-3e6a6311818b,pressure,100152.5,2019-10-11T11:51:28.169+02:00,2019-10-11T11:51:28.169+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.171+02:00,null
|
||||
cf989dbb-2e73-4042-b339-b4dbc75b0a7c,pressure,100151.5,2019-10-11T11:51:28.294+02:00,2019-10-11T11:51:28.294+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.296+02:00,null
|
||||
53a2d8fb-3b15-48a2-a277-1b821adba088,pressure,100151,2019-10-11T11:51:28.426+02:00,2019-10-11T11:51:28.426+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.428+02:00,null
|
||||
9de16913-67f3-4e0b-9976-ddadbd057bae,pressure,100154,2019-10-11T11:51:29.563+02:00,2019-10-11T11:51:29.563+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:29.565+02:00,null
|
||||
d8c255a5-c5bd-4e9e-9aec-d0e6ae795719,pressure,100154.5,2019-10-11T11:51:30.707+02:00,2019-10-11T11:51:30.707+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:30.71+02:00,null
|
||||
31ab31ac-e991-4081-8b4e-eba0f789b806,pressure,100154,2019-10-11T11:51:31.843+02:00,2019-10-11T11:51:31.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:31.846+02:00,null
|
||||
ed2ec0a9-9bc1-424a-9edf-85aad7a059ec,pressure,100150.5,2019-10-11T11:51:32.981+02:00,2019-10-11T11:51:32.981+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:32.984+02:00,null
|
||||
31e03c4c-49a1-4c0d-a750-16c09b75f96b,pressure,100151,2019-10-11T11:51:34.125+02:00,2019-10-11T11:51:34.126+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:34.133+02:00,null
|
|
@ -1,20 +0,0 @@
|
||||
140d6183-36c8-4903-a798-def4fcb3bd74,pressure,100150.5,2019-10-11T11:51:20.98+02:00,2019-10-11T11:51:20.98+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:20.982+02:00,null
|
||||
98102a36-a38e-4ccc-9886-c119a7971414,pressure,100151.703125,2019-10-11T11:51:22.123+02:00,2019-10-11T11:51:22.123+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:22.125+02:00,null
|
||||
5ffe51ae-687c-47df-8936-7785de9beed7,pressure,100150.8984375,2019-10-11T11:51:23.261+02:00,2019-10-11T11:51:23.262+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:23.263+02:00,null
|
||||
e534cb23-c725-403b-b5c9-5455256a8662,pressure,100153.203125,2019-10-11T11:51:23.739+02:00,2019-10-11T11:51:23.739+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.594+02:00,null
|
||||
e3c2a10b-373f-43d5-b02c-b381d1ad203b,pressure,100149.296875,2019-10-11T11:51:24.721+02:00,2019-10-11T11:51:24.721+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.723+02:00,null
|
||||
e9fbdd79-10ca-4fda-aed4-6fa2dbf3ff94,pressure,100150.796875,2019-10-11T11:51:24.849+02:00,2019-10-11T11:51:24.849+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:25.212+02:00,null
|
||||
70f6de4d-3a17-4688-b635-b8965e85be43,pressure,100149.6015625,2019-10-11T11:51:26.352+02:00,2019-10-11T11:51:26.352+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.354+02:00,null
|
||||
0e42478b-da36-4096-afad-d8c2a7685ccd,pressure,100149.5,2019-10-11T11:51:26.488+02:00,2019-10-11T11:51:26.488+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.49+02:00,null
|
||||
0313994c-db52-42d6-b059-2ca13afdd5d8,pressure,100151.3984375,2019-10-11T11:51:26.622+02:00,2019-10-11T11:51:26.622+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.624+02:00,null
|
||||
9cb66b4c-31d8-40dc-8c5b-9d243c2bb974,pressure,100150.203125,2019-10-11T11:51:26.754+02:00,2019-10-11T11:51:26.754+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.757+02:00,null
|
||||
6e626fdb-326d-4984-9585-7b64976be1a2,pressure,100150.5,2019-10-11T11:51:26.894+02:00,2019-10-11T11:51:26.894+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.896+02:00,null
|
||||
6869ae37-0200-40c2-85a5-d4412d81b62d,pressure,100154.1015625,2019-10-11T11:51:27.029+02:00,2019-10-11T11:51:27.029+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:27.032+02:00,null
|
||||
8a0770b9-f8b0-42de-90fe-3e6a6311818b,pressure,100152.3984375,2019-10-11T11:51:28.169+02:00,2019-10-11T11:51:28.169+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.171+02:00,null
|
||||
cf989dbb-2e73-4042-b339-b4dbc75b0a7c,pressure,100151.3984375,2019-10-11T11:51:28.294+02:00,2019-10-11T11:51:28.294+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.296+02:00,null
|
||||
53a2d8fb-3b15-48a2-a277-1b821adba088,pressure,100151.1015625,2019-10-11T11:51:28.426+02:00,2019-10-11T11:51:28.426+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.428+02:00,null
|
||||
9de16913-67f3-4e0b-9976-ddadbd057bae,pressure,100153.8984375,2019-10-11T11:51:29.563+02:00,2019-10-11T11:51:29.563+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:29.565+02:00,null
|
||||
d8c255a5-c5bd-4e9e-9aec-d0e6ae795719,pressure,100154.6015625,2019-10-11T11:51:30.707+02:00,2019-10-11T11:51:30.707+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:30.71+02:00,null
|
||||
31ab31ac-e991-4081-8b4e-eba0f789b806,pressure,100153.796875,2019-10-11T11:51:31.843+02:00,2019-10-11T11:51:31.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:31.846+02:00,null
|
||||
ed2ec0a9-9bc1-424a-9edf-85aad7a059ec,pressure,100150.703125,2019-10-11T11:51:32.981+02:00,2019-10-11T11:51:32.981+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:32.984+02:00,null
|
||||
31e03c4c-49a1-4c0d-a750-16c09b75f96b,pressure,100151.203125,2019-10-11T11:51:34.125+02:00,2019-10-11T11:51:34.126+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:34.133+02:00,null
|
|
@ -1,20 +0,0 @@
|
||||
140d6183-36c8-4903-a798-def4fcb3bd74,pressure,100150.5,2019-10-11T11:51:20.98+02:00,2019-10-11T11:51:20.98+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:20.982+02:00,null
|
||||
98102a36-a38e-4ccc-9886-c119a7971414,pressure,100151.5,2019-10-11T11:51:22.123+02:00,2019-10-11T11:51:22.123+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:22.125+02:00,null
|
||||
5ffe51ae-687c-47df-8936-7785de9beed7,pressure,100151,2019-10-11T11:51:23.261+02:00,2019-10-11T11:51:23.262+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:23.263+02:00,null
|
||||
e534cb23-c725-403b-b5c9-5455256a8662,pressure,100153,2019-10-11T11:51:23.739+02:00,2019-10-11T11:51:23.739+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.594+02:00,null
|
||||
e3c2a10b-373f-43d5-b02c-b381d1ad203b,pressure,100149.5,2019-10-11T11:51:24.721+02:00,2019-10-11T11:51:24.721+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:24.723+02:00,null
|
||||
e9fbdd79-10ca-4fda-aed4-6fa2dbf3ff94,pressure,100151,2019-10-11T11:51:24.849+02:00,2019-10-11T11:51:24.849+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:25.212+02:00,null
|
||||
70f6de4d-3a17-4688-b635-b8965e85be43,pressure,100149.5,2019-10-11T11:51:26.352+02:00,2019-10-11T11:51:26.352+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.354+02:00,null
|
||||
0e42478b-da36-4096-afad-d8c2a7685ccd,pressure,100149.5,2019-10-11T11:51:26.488+02:00,2019-10-11T11:51:26.488+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.49+02:00,null
|
||||
0313994c-db52-42d6-b059-2ca13afdd5d8,pressure,100151.5,2019-10-11T11:51:26.622+02:00,2019-10-11T11:51:26.622+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.624+02:00,null
|
||||
9cb66b4c-31d8-40dc-8c5b-9d243c2bb974,pressure,100150,2019-10-11T11:51:26.754+02:00,2019-10-11T11:51:26.754+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.757+02:00,null
|
||||
6e626fdb-326d-4984-9585-7b64976be1a2,pressure,100150.5,2019-10-11T11:51:26.894+02:00,2019-10-11T11:51:26.894+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:26.896+02:00,null
|
||||
6869ae37-0200-40c2-85a5-d4412d81b62d,pressure,100154,2019-10-11T11:51:27.029+02:00,2019-10-11T11:51:27.029+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:27.032+02:00,null
|
||||
8a0770b9-f8b0-42de-90fe-3e6a6311818b,pressure,100152.5,2019-10-11T11:51:28.169+02:00,2019-10-11T11:51:28.169+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.171+02:00,null
|
||||
cf989dbb-2e73-4042-b339-b4dbc75b0a7c,pressure,100151.5,2019-10-11T11:51:28.294+02:00,2019-10-11T11:51:28.294+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.296+02:00,null
|
||||
53a2d8fb-3b15-48a2-a277-1b821adba088,pressure,100151,2019-10-11T11:51:28.426+02:00,2019-10-11T11:51:28.426+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:28.428+02:00,null
|
||||
9de16913-67f3-4e0b-9976-ddadbd057bae,pressure,100154,2019-10-11T11:51:29.563+02:00,2019-10-11T11:51:29.563+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:29.565+02:00,null
|
||||
d8c255a5-c5bd-4e9e-9aec-d0e6ae795719,pressure,100154.5,2019-10-11T11:51:30.707+02:00,2019-10-11T11:51:30.707+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:30.71+02:00,null
|
||||
31ab31ac-e991-4081-8b4e-eba0f789b806,pressure,100154,2019-10-11T11:51:31.843+02:00,2019-10-11T11:51:31.843+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:31.846+02:00,null
|
||||
ed2ec0a9-9bc1-424a-9edf-85aad7a059ec,pressure,100150.5,2019-10-11T11:51:32.981+02:00,2019-10-11T11:51:32.981+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:32.984+02:00,null
|
||||
31e03c4c-49a1-4c0d-a750-16c09b75f96b,pressure,100151,2019-10-11T11:51:34.125+02:00,2019-10-11T11:51:34.126+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:34.133+02:00,null
|
|
@ -1,12 +0,0 @@
|
||||
2029cc8c-56cf-4cd1-adf8-06e768fbad29,temperature,20.90999984741211,2019-10-11T11:50:43.317+02:00,2019-10-11T11:50:43.317+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:43.319+02:00,null
|
||||
a588cc90-4dca-4a4e-be81-2d20b9f64458,temperature,20.920000076293945,2019-10-11T11:50:44.459+02:00,2019-10-11T11:50:47.745+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:44.473+02:00,2019-10-11T11:51:01.71+02:00
|
||||
88f5c095-8e88-4c80-b66a-cf154919afa8,temperature,20.93000030517578,2019-10-11T11:50:49.53+02:00,2019-10-11T11:50:49.53+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:50.018+02:00,null
|
||||
7c3b7eac-3b43-4461-b204-d74c2bde3a08,temperature,20.920000076293945,2019-10-11T11:50:51.187+02:00,2019-10-11T11:50:53.652+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:51.189+02:00,2019-10-11T11:51:01.71+02:00
|
||||
acb55d68-d909-41ba-a445-8ef9eab0c7aa,temperature,20.950000762939453,2019-10-11T11:50:53.788+02:00,2019-10-11T11:50:53.789+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:53.79+02:00,null
|
||||
6563ff1e-371b-46a2-aca8-cc49b418bf61,temperature,20.93000030517578,2019-10-11T11:50:54.929+02:00,2019-10-11T11:50:55.536+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.405+02:00,2019-10-11T11:51:01.71+02:00
|
||||
157eda79-3283-4cda-a21d-db0aa2da8548,temperature,20.959999084472656,2019-10-11T11:50:55.674+02:00,2019-10-11T11:50:55.674+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.677+02:00,null
|
||||
c7d8d6ce-59d9-4448-9934-69cebc071cf6,temperature,20.940000534057617,2019-10-11T11:50:56.817+02:00,2019-10-11T11:50:56.817+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:56.82+02:00,null
|
||||
07d08037-7267-44ab-8d90-878271809a4c,temperature,20.93000030517578,2019-10-11T11:50:57.959+02:00,2019-10-11T11:50:59.1+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:57.962+02:00,2019-10-11T11:51:01.71+02:00
|
||||
0b865113-d0f4-42f4-af3d-50e60edcc523,temperature,20.959999084472656,2019-10-11T11:50:59.238+02:00,2019-10-11T11:50:59.238+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.241+02:00,null
|
||||
c40ad502-b6f1-49d8-8b6d-e7d5e475a6e2,temperature,20.979999542236328,2019-10-11T11:50:59.368+02:00,2019-10-11T11:50:59.368+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.371+02:00,null
|
||||
8d98e723-7a07-4ccf-b3bc-7a2653952b8b,temperature,20.920000076293945,2019-10-11T11:51:00.509+02:00,2019-10-11T11:51:01.658+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:00.516+02:00,2019-10-11T11:51:01.71+02:00
|
|
@ -1 +0,0 @@
|
||||
2029cc8c-56cf-4cd1-adf8-06e768fbad29,temperature,21,2019-10-11T11:50:43.317+02:00,2019-10-11T11:51:01.658+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:43.319+02:00,2019-10-11T11:51:01.735+02:00
|
|
@ -1,20 +0,0 @@
|
||||
2029cc8c-56cf-4cd1-adf8-06e768fbad29,temperature,20.90999984741211,2019-10-11T11:50:43.317+02:00,2019-10-11T11:50:43.317+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:43.319+02:00,null
|
||||
a588cc90-4dca-4a4e-be81-2d20b9f64458,temperature,20.920000076293945,2019-10-11T11:50:44.459+02:00,2019-10-11T11:50:44.459+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:44.473+02:00,null
|
||||
1ebb60de-6066-4731-9f55-ee1c51748b7f,temperature,20.920000076293945,2019-10-11T11:50:45.614+02:00,2019-10-11T11:50:45.614+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:45.615+02:00,null
|
||||
6cbc586f-a5c9-4296-b3b2-200afae12059,temperature,20.920000076293945,2019-10-11T11:50:46.776+02:00,2019-10-11T11:50:46.776+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:47.134+02:00,null
|
||||
14e03e32-f214-4f61-97b0-8e021c47bead,temperature,20.920000076293945,2019-10-11T11:50:47.745+02:00,2019-10-11T11:50:47.745+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:47.747+02:00,null
|
||||
88f5c095-8e88-4c80-b66a-cf154919afa8,temperature,20.93000030517578,2019-10-11T11:50:49.53+02:00,2019-10-11T11:50:49.53+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:50.018+02:00,null
|
||||
7c3b7eac-3b43-4461-b204-d74c2bde3a08,temperature,20.920000076293945,2019-10-11T11:50:51.187+02:00,2019-10-11T11:50:51.187+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:51.189+02:00,null
|
||||
3caf0b08-38d5-4536-baa5-86d4d75e359d,temperature,20.920000076293945,2019-10-11T11:50:52.508+02:00,2019-10-11T11:50:52.508+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:52.51+02:00,null
|
||||
4b8595bf-3624-4963-b796-efc648515cd9,temperature,20.920000076293945,2019-10-11T11:50:53.652+02:00,2019-10-11T11:50:53.652+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:53.654+02:00,null
|
||||
acb55d68-d909-41ba-a445-8ef9eab0c7aa,temperature,20.950000762939453,2019-10-11T11:50:53.788+02:00,2019-10-11T11:50:53.789+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:53.79+02:00,null
|
||||
6563ff1e-371b-46a2-aca8-cc49b418bf61,temperature,20.93000030517578,2019-10-11T11:50:54.929+02:00,2019-10-11T11:50:54.93+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.405+02:00,null
|
||||
a64f57c4-b95c-45b1-b78a-a6be263abb85,temperature,20.93000030517578,2019-10-11T11:50:55.536+02:00,2019-10-11T11:50:55.536+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.538+02:00,null
|
||||
157eda79-3283-4cda-a21d-db0aa2da8548,temperature,20.959999084472656,2019-10-11T11:50:55.674+02:00,2019-10-11T11:50:55.674+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:55.677+02:00,null
|
||||
c7d8d6ce-59d9-4448-9934-69cebc071cf6,temperature,20.940000534057617,2019-10-11T11:50:56.817+02:00,2019-10-11T11:50:56.817+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:56.82+02:00,null
|
||||
07d08037-7267-44ab-8d90-878271809a4c,temperature,20.93000030517578,2019-10-11T11:50:57.959+02:00,2019-10-11T11:50:57.96+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:57.962+02:00,null
|
||||
bf2e981f-8bad-466e-9a12-c1a5d9b64399,temperature,20.93000030517578,2019-10-11T11:50:59.1+02:00,2019-10-11T11:50:59.1+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.103+02:00,null
|
||||
0b865113-d0f4-42f4-af3d-50e60edcc523,temperature,20.959999084472656,2019-10-11T11:50:59.238+02:00,2019-10-11T11:50:59.238+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.241+02:00,null
|
||||
c40ad502-b6f1-49d8-8b6d-e7d5e475a6e2,temperature,20.979999542236328,2019-10-11T11:50:59.368+02:00,2019-10-11T11:50:59.368+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:50:59.371+02:00,null
|
||||
8d98e723-7a07-4ccf-b3bc-7a2653952b8b,temperature,20.920000076293945,2019-10-11T11:51:00.509+02:00,2019-10-11T11:51:00.51+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:00.516+02:00,null
|
||||
f61b7bfb-14eb-4b43-a7b6-47b61fa46dda,temperature,20.920000076293945,2019-10-11T11:51:01.658+02:00,2019-10-11T11:51:01.658+02:00,d7fc0e1f-9d5a-45c7-bc01-e85bd1b610c6,2019-10-11T11:51:01.661+02:00,null
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user