fix(pkg/config): use storage endpoints

changes:
- Only one storage endpoint can be defined. This consists of a URL which
  can be used to specify whether the data is to be stored in a file or
  in a database.
This commit is contained in:
Markus Pesch 2019-12-07 16:53:49 +01:00
parent afe55b3d33
commit dbef4f8241
Signed by: volker.raschek
GPG Key ID: 852BCC170D81A982
30 changed files with 959 additions and 882 deletions

View File

@ -49,7 +49,7 @@ var rootCmd = &cobra.Command{
DeviceName: hostname, DeviceName: hostname,
CreationDate: t, CreationDate: t,
}, },
Logfile: "/var/log/flucky/logfile.csv", StorageEndpoint: "file:///var/log/flucky/logfile.csv",
} }
err = config.Write(&cnf, configFile) err = config.Write(&cnf, configFile)

View File

@ -50,5 +50,5 @@ func InitCmd(cmd *cobra.Command, cnfFile *string, sversion *semver.Version) {
cmd.AddCommand(daemonCmd) cmd.AddCommand(daemonCmd)
daemonCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured values") daemonCmd.Flags().BoolVar(&compression, "compression", true, "Compress measured values")
daemonCmd.Flags().StringVar(&cleanCacheInterval, "clean-cache-interval", "5m", "Minute intervall to clean cache and write measured values into logfile") daemonCmd.Flags().StringVar(&cleanCacheInterval, "clean-cache-interval", "5m", "Minute intervall to clean cache and write measured values into logfile")
daemonCmd.Flags().Float64Var(&round, "round", 0, "Round values. The value 0 deactivates the function") daemonCmd.Flags().Float64Var(&round, "round", 0.5, "Round values. The value 0 deactivates the function")
} }

View File

@ -1,13 +1,7 @@
package db package db
import ( import (
"context"
"log"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
"github.com/go-flucky/flucky/pkg/config"
database "github.com/go-flucky/flucky/pkg/storage/db"
"github.com/go-flucky/flucky/pkg/types"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -22,33 +16,33 @@ var dbCmd = &cobra.Command{
Short: "Operates with the configured database", Short: "Operates with the configured database",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// read configuration // // read configuration
cnf, err := config.Read(*configFile) // cnf, err := config.Read(*configFile)
if err != nil { // if err != nil {
log.Fatalln(err) // log.Fatalln(err)
} // }
postgresDB, err := database.New(cnf.DatabaseSettings) // postgresDB, err := database.New(cnf.DatabaseSettings)
if err != nil { // if err != nil {
log.Fatalf("%v", err) // log.Fatalf("%v", err)
} // }
ctx := context.Background() // ctx := context.Background()
devices := []*types.Device{ // devices := []*types.Device{
&types.Device{ // &types.Device{
DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c", // DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c",
DeviceName: "raspberr-pi", // DeviceName: "raspberr-pi",
}, // },
&types.Device{ // &types.Device{
DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c", // DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c",
DeviceName: "raspberr-pi", // DeviceName: "raspberr-pi",
}, // },
} // }
if err := postgresDB.InsertDevices(ctx, devices); err != nil { // if err := postgresDB.InsertDevices(ctx, devices); err != nil {
log.Fatalln(err) // log.Fatalln(err)
} // }
}, },
} }

View File

@ -1,16 +1,16 @@
package humidity package humidity
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"os" "os"
"github.com/go-flucky/flucky/pkg/storage/logfile" "github.com/go-flucky/flucky/pkg/storage"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli" "github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config" "github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -26,19 +26,14 @@ var listTemperatureCmd = &cobra.Command{
log.Fatalln(err) log.Fatalln(err)
} }
logfile := logfile.New(cnf.Logfile) ctx := context.Background()
storageEndpoint, err := cnf.GetStorageEndpointURL()
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Logfile(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues, err := logfile.Read()
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
if err := rgbled.Off(rgbLEDs); err != nil { measuredValues, err := storage.Read(ctx, storageEndpoint)
if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@ -5,7 +5,7 @@ import (
"log" "log"
"os" "os"
"github.com/go-flucky/flucky/pkg/storage/logfile" "github.com/go-flucky/flucky/pkg/storage"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli" "github.com/go-flucky/flucky/pkg/cli"
@ -56,8 +56,12 @@ var readHumidityCmd = &cobra.Command{
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout) cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
if logs { if logs {
measuredValuesLogfile := logfile.New(cnf.Logfile) storageEndpoint, err := cnf.GetStorageEndpointURL()
err := logfile.Append(measuredValuesLogfile, measuredValues) if err != nil {
log.Fatalln(err)
}
err = storage.Write(ctx, measuredValues, storageEndpoint)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@ -1,14 +1,14 @@
package pressure package pressure
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"os" "os"
"github.com/go-flucky/flucky/pkg/cli" "github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config" "github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/rgbled" "github.com/go-flucky/flucky/pkg/storage"
"github.com/go-flucky/flucky/pkg/storage/logfile"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -25,19 +25,14 @@ var listTemperatureCmd = &cobra.Command{
log.Fatalln(err) log.Fatalln(err)
} }
logfile := logfile.New(cnf.Logfile) ctx := context.Background()
storageEndpoint, err := cnf.GetStorageEndpointURL()
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Logfile(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues, err := logfile.Read()
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
if err := rgbled.Off(rgbLEDs); err != nil { measuredValues, err := storage.Read(ctx, storageEndpoint)
if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@ -5,7 +5,7 @@ import (
"log" "log"
"os" "os"
"github.com/go-flucky/flucky/pkg/storage/logfile" "github.com/go-flucky/flucky/pkg/storage"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli" "github.com/go-flucky/flucky/pkg/cli"
@ -56,8 +56,12 @@ var readPressureCmd = &cobra.Command{
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout) cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
if logs { if logs {
measuredValuesLogfile := logfile.New(cnf.Logfile) storageEndpoint, err := cnf.GetStorageEndpointURL()
err := logfile.Append(measuredValuesLogfile, measuredValues) if err != nil {
log.Fatalln(err)
}
err = storage.Write(ctx, measuredValues, storageEndpoint)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@ -1,16 +1,16 @@
package temperature package temperature
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"os" "os"
"github.com/go-flucky/flucky/pkg/storage/logfile" "github.com/go-flucky/flucky/pkg/storage"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli" "github.com/go-flucky/flucky/pkg/cli"
"github.com/go-flucky/flucky/pkg/config" "github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -26,19 +26,14 @@ var listTemperatureCmd = &cobra.Command{
log.Fatalln(err) log.Fatalln(err)
} }
logfile := logfile.New(cnf.Logfile) ctx := context.Background()
storageEndpoint, err := cnf.GetStorageEndpointURL()
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Logfile(rgbLEDs); err != nil {
log.Fatalln(err)
}
measuredValues, err := logfile.Read()
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
if err := rgbled.Off(rgbLEDs); err != nil { measuredValues, err := storage.Read(ctx, storageEndpoint)
if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@ -6,8 +6,7 @@ import (
"log" "log"
"os" "os"
"github.com/go-flucky/flucky/pkg/rgbled" "github.com/go-flucky/flucky/pkg/storage"
"github.com/go-flucky/flucky/pkg/storage/logfile"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/pkg/cli" "github.com/go-flucky/flucky/pkg/cli"
@ -42,15 +41,9 @@ var readTemperatureCmd = &cobra.Command{
return return
} }
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED)
if err := rgbled.Run(rgbLEDs); err != nil {
log.Fatalln(err)
}
ctx := context.Background() ctx := context.Background()
measuredValues, err := sensor.Read(ctx, sensors) measuredValues, err := sensor.Read(ctx, sensors)
if err != nil { if err != nil {
rgbled.Error(rgbLEDs)
log.Fatalln(err) log.Fatalln(err)
} }
@ -60,14 +53,16 @@ var readTemperatureCmd = &cobra.Command{
cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout) cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout)
if logs { if logs {
measuredValuesLogfile := logfile.New(cnf.Logfile) storageEndpoint, err := cnf.GetStorageEndpointURL()
err := logfile.Append(measuredValuesLogfile, measuredValues) if err != nil {
log.Fatalln(err)
}
err = storage.Write(ctx, measuredValues, storageEndpoint)
if err != nil { if err != nil {
rgbled.Error(rgbLEDs)
log.Fatalln(err) log.Fatalln(err)
} }
} }
rgbled.Off(rgbLEDs)
}, },
} }

View File

@ -4,13 +4,13 @@ services:
container_name: postgres container_name: postgres
environment: environment:
- PGTZ=${TZ} - PGTZ=${TZ}
- POSTGRES_PASSWORD=${PG_PASSWORD} - POSTGRES_DB=${POSTGRES_DB_NAME}
- POSTGRES_USER=${PG_USER} - POSTGRES_USER=${POSTGRES_DB_USER}
- POSTGRES_DB=${PG_NAME} - POSTGRES_PASSWORD=${POSTGRES_DB_PASS}
- TZ=${TZ} - TZ=${TZ}
image: postgres:11.5-alpine image: postgres:11.5-alpine
ports: ports:
- ${PG_EXTERN_PORT}:${PG_INTERN_PORT}/tcp - 5432:5432
restart: always restart: always
volumes: volumes:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro

View File

@ -1,59 +1,600 @@
package config package config
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "net/url"
"path/filepath"
"regexp" "time"
"github.com/go-flucky/flucky/pkg/internal/format"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/sensor"
"github.com/go-flucky/flucky/pkg/types"
uuid "github.com/satori/go.uuid"
) )
var validUUID = regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$") // Configuration of flucky
type Configuration struct {
// Read the configuration file Device *types.Device `json:"device"`
func Read(configFile string) (*Configuration, error) { StorageEndpoint string `json:"storage_endpoint"`
RGBLEDs []*types.RGBLED `json:"rgb_leds"`
fc := &Configuration{} Sensors []*types.Sensor `json:"sensors"`
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 // 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)
}
} }
// Write the configuration into a file, specified by the configuration filepath // check if sensor has a valid device id
func Write(cfg *Configuration, configFile string) error { if rgbLED.DeviceID != c.Device.DeviceID {
rgbLED.DeviceID = c.Device.DeviceID
}
if _, err := os.Stat(configFile); os.IsNotExist(err) { // overwrite creation date
configDir := filepath.Dir(configFile) rgbLED.CreationDate = time.Now()
err := os.MkdirAll(configDir, os.ModeDir)
if err != nil { // check
return fmt.Errorf("Can not create config directory %v: %v", configDir, err) c.RGBLEDs = append(c.RGBLEDs, rgbLED)
return nil
}
// AddSensor add a new sensor
func (c *Configuration) AddSensor(sensor *types.Sensor) error {
// check if sensorID is a valid UUID string
if !validUUID.MatchString(sensor.SensorID) {
sensor.SensorID = uuid.NewV4().String()
}
// check if sensor name and sensor uuid already exists
for _, s := range c.Sensors {
if s.SensorName == sensor.SensorName {
return fmt.Errorf("Sensor %v already exists", s.SensorName)
}
if s.SensorID == sensor.SensorID {
return fmt.Errorf("Sensor %v with UUID %v already exists", s.SensorName, s.SensorID)
}
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.SensorName)
}
} }
} }
f, err := os.Create(configFile) // check if sensor has a valid device id
if err != nil { if sensor.DeviceID != c.Device.DeviceID {
return fmt.Errorf("Can not write config file: %v", err) sensor.DeviceID = c.Device.DeviceID
} }
defer f.Close()
encoder := json.NewEncoder(f) // overwrite creation date
encoder.SetIndent("", " ") sensor.CreationDate = format.FormatedTime()
err = encoder.Encode(cfg)
if err != nil { //TODO: check if wire sensor exists in /dev/bus/w1/devices
return fmt.Errorf("Error in encoding struct to json: %v", err)
// 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
}
}
if !found {
return fmt.Errorf("Can not found RGB-LED %v", name)
} }
return nil return nil
}
// DisableSensor disables a sensor by its name or its unique UUID
func (c *Configuration) DisableSensor(name string) error {
found := false
for _, sensor := range c.Sensors {
// disable sensor matched after name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
sensor.SensorEnabled = false
found = true
break
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
sensor.SensorEnabled = false
found = true
break
}
}
if !found {
return fmt.Errorf("Can not found sensor %v", name)
}
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 {
found := false
for _, sensor := range c.Sensors {
// disable sensor matched after name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
sensor.SensorEnabled = true
found = true
break
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
sensor.SensorEnabled = true
found = true
break
}
}
if !found {
return fmt.Errorf("Can not found sensor %v", name)
}
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.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
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.SensorID:
configHumiditySensors[s.SensorID] = s
case s.SensorName:
configHumiditySensors[s.SensorID] = 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.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
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.SensorID:
configPressureSensors[s.SensorID] = s
case s.SensorName:
configPressureSensors[s.SensorID] = 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)
}
// 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.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range c.Sensors {
if !sensor.SensorEnabled {
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.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
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.SensorID:
configTemperatureSensors[s.SensorID] = s
case s.SensorName:
configTemperatureSensors[s.SensorID] = 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 {
// remove machted name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
c.Sensors = append(c.Sensors[:i], c.Sensors[i+1:]...)
return nil
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
c.Sensors = append(c.Sensors[:i], c.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.SensorName == oldName ||
sensor.SensorID == oldName {
sensor.SensorName = 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.SensorModel {
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.SensorModel]; 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.SensorModel]; 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.SensorModel]; ok {
temperatureSensors = append(temperatureSensors, s)
}
}
return temperatureSensors
} }

View File

@ -1,21 +0,0 @@
package config
type DatabaseSettings struct {
Vendor DatabaseVendor `json:"vendor"`
Host string `json:"host"`
Port string `json:"port"`
Database string `json:"database"`
User string `json:"user"`
Password string `json:"password"`
}
type DatabaseVendor string
func (dv DatabaseVendor) String() string {
return string(dv)
}
const (
VendorPostgreSQL DatabaseVendor = "postgres"
VendorOracle = "oracle"
)

View File

@ -1,594 +0,0 @@
package config
import (
"fmt"
"time"
"github.com/go-flucky/flucky/pkg/internal/format"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/sensor"
"github.com/go-flucky/flucky/pkg/types"
uuid "github.com/satori/go.uuid"
)
var humiditySensorModels = map[types.SensorModel]types.SensorModel{
types.BME280: types.BME280,
types.DHT11: types.DHT11,
types.DHT22: types.DHT22,
}
var pressureSensorModels = map[types.SensorModel]types.SensorModel{
types.BME280: types.BME280,
}
var temperatureSensorModels = map[types.SensorModel]types.SensorModel{
types.BME280: types.BME280,
types.DHT11: types.DHT11,
types.DHT22: types.DHT22,
types.DS18B20: types.DS18B20,
}
// Configuration of flucky
type Configuration struct {
DatabaseSettings *DatabaseSettings `json:"database_settings"`
Device *types.Device `json:"device"`
Logfile string `json:"logfile" xml:"logfile"`
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.DeviceID {
rgbLED.DeviceID = c.Device.DeviceID
}
// overwrite creation date
rgbLED.CreationDate = time.Now()
// check
c.RGBLEDs = append(c.RGBLEDs, rgbLED)
return nil
}
// AddSensor add a new sensor
func (c *Configuration) AddSensor(sensor *types.Sensor) error {
// check if sensorID is a valid UUID string
if !validUUID.MatchString(sensor.SensorID) {
sensor.SensorID = uuid.NewV4().String()
}
// check if sensor name and sensor uuid already exists
for _, s := range c.Sensors {
if s.SensorName == sensor.SensorName {
return fmt.Errorf("Sensor %v already exists", s.SensorName)
}
if s.SensorID == sensor.SensorID {
return fmt.Errorf("Sensor %v with UUID %v already exists", s.SensorName, s.SensorID)
}
if sensor.WireID != nil {
if *s.WireID == *sensor.WireID {
return fmt.Errorf("Sensor with 1wire-id %v already exists as %v", *s.WireID, s.SensorName)
}
}
}
// check if sensor has a valid device id
if sensor.DeviceID != c.Device.DeviceID {
sensor.DeviceID = c.Device.DeviceID
}
// 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
}
}
if !found {
return fmt.Errorf("Can not found RGB-LED %v", name)
}
return nil
}
// DisableSensor disables a sensor by its name or its unique UUID
func (c *Configuration) DisableSensor(name string) error {
found := false
for _, sensor := range c.Sensors {
// disable sensor matched after name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
sensor.SensorEnabled = false
found = true
break
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
sensor.SensorEnabled = false
found = true
break
}
}
if !found {
return fmt.Errorf("Can not found sensor %v", name)
}
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 {
found := false
for _, sensor := range c.Sensors {
// disable sensor matched after name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
sensor.SensorEnabled = true
found = true
break
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
sensor.SensorEnabled = true
found = true
break
}
}
if !found {
return fmt.Errorf("Can not found sensor %v", name)
}
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.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
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.SensorID:
configHumiditySensors[s.SensorID] = s
case s.SensorName:
configHumiditySensors[s.SensorID] = 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.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
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.SensorID:
configPressureSensors[s.SensorID] = s
case s.SensorName:
configPressureSensors[s.SensorID] = 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)
}
// 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.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range c.Sensors {
if !sensor.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
default:
return c.convertSensors(cachedSensors)
}
}
// 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.SensorEnabled {
cachedSensors = append(cachedSensors, sensor)
}
}
return c.convertSensors(cachedSensors)
case DISABLED:
for _, sensor := range sensors {
if !sensor.SensorEnabled {
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.SensorID:
configTemperatureSensors[s.SensorID] = s
case s.SensorName:
configTemperatureSensors[s.SensorID] = s
}
}
}
temperatureSensors := make([]*types.Sensor, 0)
for _, cs := range configTemperatureSensors {
temperatureSensors = append(temperatureSensors, cs)
}
return c.convertSensors(temperatureSensors)
}
// RemoveDatabaseSettings remove data base setting informations
func (c *Configuration) RemoveDatabaseSettings() {
c.DatabaseSettings = nil
}
// 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 {
// remove machted name
if !validUUID.MatchString(name) &&
sensor.SensorName == name {
c.Sensors = append(c.Sensors[:i], c.Sensors[i+1:]...)
return nil
}
// remove machted uuid
if validUUID.MatchString(name) &&
sensor.SensorID == name {
c.Sensors = append(c.Sensors[:i], c.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.SensorName == oldName ||
sensor.SensorID == oldName {
sensor.SensorName = newName
return nil
}
}
return fmt.Errorf("Could not find remote %v to replace into with %v", oldName, newName)
}
// SetDatabaseSettings set database setting informations
func (c *Configuration) SetDatabaseSettings(databaseSettings *DatabaseSettings) {
c.DatabaseSettings = databaseSettings
}
func (c *Configuration) convertSensors(sensors []*types.Sensor) []sensor.Sensor {
cachedSensors := make([]sensor.Sensor, 0)
for _, s := range sensors {
switch s.SensorModel {
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.SensorModel]; 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.SensorModel]; 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.SensorModel]; ok {
temperatureSensors = append(temperatureSensors, s)
}
}
return temperatureSensors
}

59
pkg/config/io.go Normal file
View File

@ -0,0 +1,59 @@
package config
import (
"encoding/json"
"fmt"
"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}$")
// Read the configuration file
func Read(configFile string) (*Configuration, error) {
fc := &Configuration{}
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
}
// Write the configuration into a file, specified by the configuration filepath
func Write(cfg *Configuration, 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)
}
}
f, err := os.Create(configFile)
if err != nil {
return fmt.Errorf("Can not write config file: %v", 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
}

22
pkg/config/sensors.go Normal file
View File

@ -0,0 +1,22 @@
package config
import "github.com/go-flucky/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,
}
)

View File

@ -9,11 +9,9 @@ import (
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
"github.com/go-flucky/flucky/pkg/config" "github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/rgbled"
"github.com/go-flucky/flucky/pkg/sensor" "github.com/go-flucky/flucky/pkg/sensor"
"github.com/go-flucky/flucky/pkg/storage" "github.com/go-flucky/flucky/pkg/storage"
"github.com/go-flucky/flucky/pkg/storage/db" "github.com/go-flucky/flucky/pkg/storage/db"
"github.com/go-flucky/flucky/pkg/storage/logfile"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
"github.com/volker-raschek/go-logger/pkg/logger" "github.com/volker-raschek/go-logger/pkg/logger"
) )
@ -25,12 +23,8 @@ var (
postgresUser = "postgres" postgresUser = "postgres"
postgresPassword = "postgres" postgresPassword = "postgres"
flogger logger.Logger
)
func init() {
flogger = logger.NewSilentLogger() flogger = logger.NewSilentLogger()
} )
func SetLogger(logger logger.Logger) { func SetLogger(logger logger.Logger) {
flogger = logger flogger = logger
@ -39,69 +33,63 @@ func SetLogger(logger logger.Logger) {
// Start the daemon // Start the daemon
func Start(cnf *config.Configuration, cleanCacheInterval time.Duration, compression bool, round float64, version *semver.Version) { func Start(cnf *config.Configuration, cleanCacheInterval time.Duration, compression bool, round float64, version *semver.Version) {
// Context
parentCtx := context.Background()
ctx, cancel := context.WithCancel(parentCtx)
// Ticker
// saveTicker := time.Tick(cleanCacheInterval)
// 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 // Info
flogger.Info("Use clean-cache-interval: %v", cleanCacheInterval.String()) flogger.Info("Use clean-cache-interval: %v", cleanCacheInterval.String())
flogger.Info("Use compression: %v", compression) flogger.Info("Use compression: %v", compression)
flogger.Info("Round: %v", round) flogger.Info("Round: %v", round)
ticker := time.Tick(cleanCacheInterval) ticker := time.Tick(cleanCacheInterval)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)
errorChannel := make(chan error, 0)
measuredValuesChannel := make(chan []*types.MeasuredValue, 0)
ctx := context.Background()
childContext, cancel := context.WithCancel(ctx)
measuredValuesLogfile := logfile.New(cnf.Logfile)
measuredValuesCache := make([]*types.MeasuredValue, 0) measuredValuesCache := make([]*types.MeasuredValue, 0)
// measuredValuesLogfile := logfile.New(cnf.Logfile)
var postgres db.Database // Producer
if cnf.DatabaseSettings != nil { go sensor.ReadContinuously(ctx, cnf.GetSensors(config.ENABLED), measuredValueChannel, errorChannel)
p, err := db.New(cnf.DatabaseSettings)
if err != nil {
flogger.Error("%v", err)
}
if err := p.Schema(ctx, version); err != nil {
flogger.Error("%v", err)
}
postgres = p
checkDeviceInDatabase(ctx, cnf.Device, postgres)
checkSensorsInDatabase(ctx, cnf.Sensors, postgres)
defer postgres.Close()
}
rgbLEDs := cnf.GetRGBLEDs(config.ENABLED) // Distributor
//measuredValueChannels := distribute.MeasuredValues(ctx, 5, measuredValueChannel)
go sensor.ReadContinuously(childContext, cnf.GetSensors(config.ENABLED), measuredValuesChannel, errorChannel)
for { for {
err := rgbled.Run(rgbLEDs)
if err != nil {
flogger.Error("Can not turn on green info light: %v", err)
}
select { 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: case err, _ := <-errorChannel:
flogger.Error("%v", err) flogger.Error("%v", err)
case fatal, _ := <-fatalChannel:
err = rgbled.Error(rgbLEDs) flogger.Fatal("Received a fatal error: %v", fatal)
if err != nil { case interrupt := <-interruptChannel:
flogger.Error("Can not turn on red info light: %v", err) flogger.Info("Received OS Signal: %v", interrupt)
} flogger.Info("Close context")
cancel()
time.Sleep(time.Second * 2) flogger.Info("Close channels")
close(debugChannel)
close(infoChannel)
close(warnChannel)
close(errorChannel)
close(interruptChannel)
return
case <-ticker: case <-ticker:
err := rgbled.Logfile(rgbLEDs)
if err != nil {
flogger.Error("Can not turn on blue info light: %v", err)
}
if round != 0 { if round != 0 {
storage.Round(measuredValuesCache, round) storage.Round(measuredValuesCache, round)
@ -111,46 +99,14 @@ func Start(cnf *config.Configuration, cleanCacheInterval time.Duration, compress
measuredValuesCache = storage.Compression(measuredValuesCache) measuredValuesCache = storage.Compression(measuredValuesCache)
} }
if err := logfile.Append(measuredValuesLogfile, measuredValuesCache); err != nil { // if err := logfile.Append(measuredValuesLogfile, measuredValuesCache); err != nil {
err2 := rgbled.Error(rgbLEDs) // flogger.Error("Can not save caches measured values in logfile: %v", err)
if err2 != nil { // }
flogger.Error("Can not turn on red info light: %v", err2)
}
flogger.Error("Can not save caches measured values in logfile: %v", err)
}
if postgres != nil {
if err := postgres.InsertMeasuredValues(ctx, measuredValuesCache); err != nil {
err2 := rgbled.Error(rgbLEDs)
if err2 != nil {
flogger.Error("Can not turn on red info light: %v", err)
}
flogger.Error("Can not save cached measured values in database: %v", err)
}
}
measuredValuesCache = make([]*types.MeasuredValue, 0) measuredValuesCache = make([]*types.MeasuredValue, 0)
case measuredValues, _ := <-measuredValuesChannel: case measuredValue, _ := <-measuredValueChannel:
measuredValuesCache = append(measuredValuesCache, measuredValues...) measuredValuesCache = append(measuredValuesCache, measuredValue)
case killSignal := <-interrupt:
flogger.Warn("Daemon was interruped by system signal %v\n", killSignal)
cancel()
err := rgbled.Error(rgbLEDs)
if err != nil {
flogger.Error("Can not turn on red info light: %v", err)
}
flogger.Warn("Save remaining data from the cache")
err = logfile.Append(measuredValuesLogfile, measuredValuesCache)
if err != nil {
flogger.Fatal("%v", err)
}
return
} }
} }
} }

View File

@ -4,13 +4,13 @@ import (
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
) )
func MeasuredValues(measuredValuesChannel <-chan []*types.MeasuredValue) []*types.MeasuredValue { func MeasuredValues(measuredValueChannel <-chan *types.MeasuredValue) []*types.MeasuredValue {
cachedMeasuredValues := make([]*types.MeasuredValue, 0) cachedMeasuredValues := make([]*types.MeasuredValue, 0)
for { for {
select { select {
case measuredValues, more := <-measuredValuesChannel: case measuredValue, more := <-measuredValueChannel:
if more { if more {
cachedMeasuredValues = append(cachedMeasuredValues, measuredValues...) cachedMeasuredValues = append(cachedMeasuredValues, measuredValue)
continue continue
} }
default: default:

View File

@ -0,0 +1,30 @@
package distribute
import (
"context"
"github.com/go-flucky/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
}

View File

@ -94,7 +94,7 @@ func (s *BME280) Read() ([]*types.MeasuredValue, error) {
// ReadChannel reads the measured values from the sensor and writes them to a // ReadChannel reads the measured values from the sensor and writes them to a
// channel. // channel.
func (s *BME280) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) { func (s *BME280) ReadChannel(measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) {
if wg != nil { if wg != nil {
defer wg.Done() defer wg.Done()
} }
@ -105,20 +105,22 @@ func (s *BME280) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue
return return
} }
measuredValuesChannel <- measuredValues for _, measuredValue := range measuredValues {
measuredValueChannel <- measuredValue
}
} }
// ReadContinously reads the measured values continously from the sensor and // ReadContinously reads the measured values continously from the sensor and
// writes them to a channel. // writes them to a channel.
func (s *BME280) ReadContinously(ctx context.Context, measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error) { func (s *BME280) ReadContinously(ctx context.Context, measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err()) errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err())
return return
default: default:
s.ReadChannel(measuredValuesChannel, errorChannel, nil) s.ReadChannel(measuredValueChannel, errorChannel, nil)
} }
} }
} }

View File

@ -68,7 +68,7 @@ func (s *DHT11) Read() ([]*types.MeasuredValue, error) {
// ReadChannel reads the measured values from the sensor and writes them to a // ReadChannel reads the measured values from the sensor and writes them to a
// channel. // channel.
func (s *DHT11) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) { func (s *DHT11) ReadChannel(measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) {
if wg != nil { if wg != nil {
defer wg.Done() defer wg.Done()
} }
@ -79,20 +79,22 @@ func (s *DHT11) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue,
return return
} }
measuredValuesChannel <- measuredValues for _, measuredValue := range measuredValues {
measuredValueChannel <- measuredValue
}
} }
// ReadContinously reads the measured values continously from the sensor and // ReadContinously reads the measured values continously from the sensor and
// writes them to a channel. // writes them to a channel.
func (s *DHT11) ReadContinously(ctx context.Context, measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error) { func (s *DHT11) ReadContinously(ctx context.Context, measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err()) errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err())
return return
default: default:
s.ReadChannel(measuredValuesChannel, errorChannel, nil) s.ReadChannel(measuredValueChannel, errorChannel, nil)
} }
} }
} }

View File

@ -68,7 +68,7 @@ func (s *DHT22) Read() ([]*types.MeasuredValue, error) {
// ReadChannel reads the measured values from the sensor and writes them to a // ReadChannel reads the measured values from the sensor and writes them to a
// channel. // channel.
func (s *DHT22) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) { func (s *DHT22) ReadChannel(measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) {
if wg != nil { if wg != nil {
defer wg.Done() defer wg.Done()
} }
@ -79,20 +79,22 @@ func (s *DHT22) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue,
return return
} }
measuredValuesChannel <- measuredValues for _, measuredValue := range measuredValues {
measuredValueChannel <- measuredValue
}
} }
// ReadContinously reads the measured values continously from the sensor and // ReadContinously reads the measured values continously from the sensor and
// writes them to a channel. // writes them to a channel.
func (s *DHT22) ReadContinously(ctx context.Context, measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error) { func (s *DHT22) ReadContinously(ctx context.Context, measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err()) errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err())
return return
default: default:
s.ReadChannel(measuredValuesChannel, errorChannel, nil) s.ReadChannel(measuredValueChannel, errorChannel, nil)
} }
} }
} }

View File

@ -67,7 +67,7 @@ func (s *DS18B20) Read() ([]*types.MeasuredValue, error) {
// ReadChannel reads the measured values from the sensor and writes them to a // ReadChannel reads the measured values from the sensor and writes them to a
// channel. // channel.
func (s *DS18B20) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) { func (s *DS18B20) ReadChannel(measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) {
if wg != nil { if wg != nil {
defer wg.Done() defer wg.Done()
} }
@ -78,20 +78,22 @@ func (s *DS18B20) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValu
return return
} }
measuredValuesChannel <- measuredValues for _, measuredValue := range measuredValues {
measuredValueChannel <- measuredValue
}
} }
// ReadContinously reads the measured values continously from the sensor and // ReadContinously reads the measured values continously from the sensor and
// writes them to a channel. // writes them to a channel.
func (s *DS18B20) ReadContinously(ctx context.Context, measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error) { func (s *DS18B20) ReadContinously(ctx context.Context, measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err()) errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err())
return return
default: default:
s.ReadChannel(measuredValuesChannel, errorChannel, nil) s.ReadChannel(measuredValueChannel, errorChannel, nil)
} }
} }
} }

View File

@ -10,6 +10,6 @@ import (
type Sensor interface { type Sensor interface {
GetSensorModel() types.SensorModel GetSensorModel() types.SensorModel
Read() ([]*types.MeasuredValue, error) Read() ([]*types.MeasuredValue, error)
ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) ReadChannel(measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup)
ReadContinously(ctx context.Context, measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error) ReadContinously(ctx context.Context, measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error)
} }

View File

@ -14,30 +14,30 @@ import (
// Read measured values from sensors // Read measured values from sensors
func Read(ctx context.Context, sensors []Sensor) ([]*types.MeasuredValue, error) { func Read(ctx context.Context, sensors []Sensor) ([]*types.MeasuredValue, error) {
measuredValuesChannel := make(chan []*types.MeasuredValue, len(sensors)) measuredValueChannel := make(chan *types.MeasuredValue, len(sensors))
errorChannel := make(chan error, len(sensors)) errorChannel := make(chan error, len(sensors))
ReadChannel(ctx, sensors, measuredValuesChannel, errorChannel) ReadChannel(ctx, sensors, measuredValueChannel, errorChannel)
errors := collect.Errors(errorChannel) errors := collect.Errors(errorChannel)
if len(errors) > 0 { if len(errors) > 0 {
return nil, prittyprint.FormatErrors(errors) return nil, prittyprint.FormatErrors(errors)
} }
measuredValues := collect.MeasuredValues(measuredValuesChannel) measuredValues := collect.MeasuredValues(measuredValueChannel)
return measuredValues, nil return measuredValues, nil
} }
// ReadChannel reads the measured values from sensors and writes them to a // ReadChannel reads the measured values from sensors and writes them to a
// channel. // channel.
func ReadChannel(ctx context.Context, sensors []Sensor, measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error) { func ReadChannel(ctx context.Context, sensors []Sensor, measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error) {
wg := new(sync.WaitGroup) wg := new(sync.WaitGroup)
wg.Add(len(sensors)) wg.Add(len(sensors))
for _, sensor := range sensors { for _, sensor := range sensors {
go sensor.ReadChannel(measuredValuesChannel, errorChannel, wg) go sensor.ReadChannel(measuredValueChannel, errorChannel, wg)
} }
wg.Wait() wg.Wait()
@ -45,14 +45,14 @@ func ReadChannel(ctx context.Context, sensors []Sensor, measuredValuesChannel ch
// ReadContinuously reads the measured values continously from sensors and writes // ReadContinuously reads the measured values continously from sensors and writes
// them to a channel. // them to a channel.
func ReadContinuously(ctx context.Context, sensors []Sensor, measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error) { func ReadContinuously(ctx context.Context, sensors []Sensor, measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
errorChannel <- fmt.Errorf("Context closed: %v", ctx.Err()) errorChannel <- fmt.Errorf("Context closed: %v", ctx.Err())
return return
default: default:
ReadChannel(ctx, sensors, measuredValuesChannel, errorChannel) ReadChannel(ctx, sensors, measuredValueChannel, errorChannel)
} }
} }
} }

View File

@ -3,8 +3,8 @@ package db
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"net/url"
"github.com/go-flucky/flucky/pkg/config"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"github.com/volker-raschek/go-logger/pkg/logger" "github.com/volker-raschek/go-logger/pkg/logger"
) )
@ -13,15 +13,14 @@ var (
flogger = logger.NewSilentLogger() flogger = logger.NewSilentLogger()
) )
func New(databaseSettings *config.DatabaseSettings) (Database, error) { func New(storageEndpoint *url.URL) (Database, error) {
connStr := fmt.Sprintf("%v://%v:%v@%v:%v/%v?sslmode=disable", databaseSettings.Vendor.String(), databaseSettings.User, databaseSettings.Password, databaseSettings.Host, databaseSettings.Port, databaseSettings.Database) newDBO, err := sql.Open(storageEndpoint.Scheme, storageEndpoint.String())
newDBO, err := sql.Open(databaseSettings.Vendor.String(), connStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch databaseSettings.Vendor { switch storageEndpoint.Scheme {
case config.VendorPostgreSQL: case "postgres":
return &Postgres{ return &Postgres{
dbo: newDBO, dbo: newDBO,
}, nil }, nil

View File

@ -29,6 +29,7 @@ type Database interface {
SelectDeviceByID(ctx context.Context, id string) (*types.Device, error) SelectDeviceByID(ctx context.Context, id string) (*types.Device, error)
SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error)
SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectMeasuredValues(ctx context.Context) ([]*types.MeasuredValue, error)
SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error) SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error)
SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error)
SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error)

View File

@ -11,6 +11,8 @@ import (
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
// PostgreSQL lib
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
@ -18,17 +20,19 @@ var (
postgresAssetPath = "pkg/storage/db/sql/psql" postgresAssetPath = "pkg/storage/db/sql/psql"
) )
// Postgres provide functions to interact with a postgres database
type Postgres struct { type Postgres struct {
dbo *sql.DB dbo *sql.DB
} }
// Close the database connection
func (p *Postgres) Close() error { func (p *Postgres) Close() error {
return p.dbo.Close() return p.dbo.Close()
} }
// Schema create or upgrade database schema to the version of the flucky binary // 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 { func (p *Postgres) Schema(ctx context.Context, version *semver.Version) error {
schemaFunc := func(ctx context.Context, fromVersion *semver.Version, toVersion *semver.Version) error { schemaFunc := func(ctx context.Context, fromVersion *semver.Version, toVersion *semver.Version) error {
assetPath := fmt.Sprintf("%v/schema", postgresAssetPath) assetPath := fmt.Sprintf("%v/schema", postgresAssetPath)
@ -94,9 +98,10 @@ func (p *Postgres) Schema(ctx context.Context, version *semver.Version) error {
} }
return schemaFunc(ctx, fromVersion, version) 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 { func (p *Postgres) DeleteDevices(ctx context.Context, devices []*types.Device) error {
asset := fmt.Sprintf("%v/deleteDevice.sql", postgresAssetPath) asset := fmt.Sprintf("%v/deleteDevice.sql", postgresAssetPath)
queryBytes, err := Asset(asset) queryBytes, err := Asset(asset)
@ -121,6 +126,8 @@ func (p *Postgres) DeleteDevices(ctx context.Context, devices []*types.Device) e
return nil return nil
} }
// DeleteSensors delete recusively all spicified sensors, including all measured
// values
func (p *Postgres) DeleteSensors(ctx context.Context, sensors []*types.Sensor) error { func (p *Postgres) DeleteSensors(ctx context.Context, sensors []*types.Sensor) error {
asset := fmt.Sprintf("%v/deleteSensor.sql", postgresAssetPath) asset := fmt.Sprintf("%v/deleteSensor.sql", postgresAssetPath)
queryBytes, err := Asset(asset) queryBytes, err := Asset(asset)
@ -145,6 +152,7 @@ func (p *Postgres) DeleteSensors(ctx context.Context, sensors []*types.Sensor) e
return nil return nil
} }
// DeleteMeasuredValues delete all spicified measured values
func (p *Postgres) DeleteMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error { func (p *Postgres) DeleteMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
deleteMeasuredValue := func(ctx context.Context, query string, measuredValues []*types.MeasuredValue) error { deleteMeasuredValue := func(ctx context.Context, query string, measuredValues []*types.MeasuredValue) error {
@ -214,6 +222,7 @@ func (p *Postgres) DeleteMeasuredValues(ctx context.Context, measuredValues []*t
return nil return nil
} }
// InsertDevices insert all specified devices into the database
func (p *Postgres) InsertDevices(ctx context.Context, devices []*types.Device) error { func (p *Postgres) InsertDevices(ctx context.Context, devices []*types.Device) error {
asset := fmt.Sprintf("%v/insertDevice.sql", postgresAssetPath) asset := fmt.Sprintf("%v/insertDevice.sql", postgresAssetPath)
queryBytes, err := Asset(asset) queryBytes, err := Asset(asset)
@ -238,6 +247,7 @@ func (p *Postgres) InsertDevices(ctx context.Context, devices []*types.Device) e
return nil 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 { func (p *Postgres) InsertInfo(ctx context.Context, key string, value string) error {
asset := fmt.Sprintf("%v/insertInfo.sql", postgresAssetPath) asset := fmt.Sprintf("%v/insertInfo.sql", postgresAssetPath)
queryBytes, err := Asset(asset) queryBytes, err := Asset(asset)
@ -260,6 +270,7 @@ func (p *Postgres) InsertInfo(ctx context.Context, key string, value string) err
return nil return nil
} }
// InsertMeasuredValues insert all specified measured values into the database
func (p *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error { func (p *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
sortedMeasuredValueTypes := make(map[types.MeasuredValueType][]*types.MeasuredValue) sortedMeasuredValueTypes := make(map[types.MeasuredValueType][]*types.MeasuredValue)
@ -343,7 +354,7 @@ func (p *Postgres) insertPressure(ctx context.Context, measuredValues []*types.M
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate) _, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
if err != nil { if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err) return fmt.Errorf("%v: Measured value id %v: %v", errorStatementExecute, measuredValue.ID, err)
} }
} }
return nil return nil
@ -372,12 +383,13 @@ func (p *Postgres) insertTemperature(ctx context.Context, measuredValues []*type
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate) _, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
if err != nil { if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err) return fmt.Errorf("%v: Measured value id %v: %v", errorStatementExecute, measuredValue.ID, err)
} }
} }
return nil return nil
} }
// InsertSensors insert all specified sensors into the database
func (p *Postgres) InsertSensors(ctx context.Context, sensors []*types.Sensor) error { func (p *Postgres) InsertSensors(ctx context.Context, sensors []*types.Sensor) error {
asset := fmt.Sprintf("%v/insertSensor.sql", postgresAssetPath) asset := fmt.Sprintf("%v/insertSensor.sql", postgresAssetPath)
@ -403,6 +415,7 @@ func (p *Postgres) InsertSensors(ctx context.Context, sensors []*types.Sensor) e
return nil return nil
} }
// SelectDeviceByID returns a device by his ID
func (p *Postgres) SelectDeviceByID(ctx context.Context, id string) (*types.Device, error) { func (p *Postgres) SelectDeviceByID(ctx context.Context, id string) (*types.Device, error) {
asset := fmt.Sprintf("%v/selectDeviceByID.sql", postgresAssetPath) asset := fmt.Sprintf("%v/selectDeviceByID.sql", postgresAssetPath)
queryBytes, err := Asset(asset) queryBytes, err := Asset(asset)
@ -430,6 +443,7 @@ func (p *Postgres) SelectDeviceByID(ctx context.Context, id string) (*types.Devi
return device, nil 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) { func (p *Postgres) SelectInfo(ctx context.Context, key string) (string, error) {
asset := fmt.Sprintf("%v/selectInfo.sql", postgresAssetPath) asset := fmt.Sprintf("%v/selectInfo.sql", postgresAssetPath)
queryBytes, err := Asset(asset) queryBytes, err := Asset(asset)
@ -457,6 +471,7 @@ func (p *Postgres) SelectInfo(ctx context.Context, key string) (string, error) {
return value, nil return value, nil
} }
// SelectHumidities returns humidity values
func (p *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) { func (p *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectHumidities.sql", postgresAssetPath) queryFile := fmt.Sprintf("%v/selectHumidities.sql", postgresAssetPath)
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeHumidity, queryFile, nil) measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeHumidity, queryFile, nil)
@ -466,6 +481,7 @@ func (p *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue
return measuredValues, nil return measuredValues, nil
} }
// SelectHumidityByID returns a humidity value by his ID
func (p *Postgres) SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) { func (p *Postgres) SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectHumidityByID.sql", postgresAssetPath) queryFile := fmt.Sprintf("%v/selectHumidityByID.sql", postgresAssetPath)
args := []interface{}{id} args := []interface{}{id}
@ -481,6 +497,30 @@ func (p *Postgres) SelectHumidityByID(ctx context.Context, id string) (*types.Me
return measuredValues[0], nil 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) { func (p *Postgres) SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error) {
switch valueType { switch valueType {
case types.MeasuredValueTypeHumidity: case types.MeasuredValueTypeHumidity:
@ -494,6 +534,7 @@ func (p *Postgres) SelectMeasuredValuesByIDAndType(ctx context.Context, id strin
} }
} }
// SelectPressures returns pressure values
func (p *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) { func (p *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectPressures.sql", postgresAssetPath) queryFile := fmt.Sprintf("%v/selectPressures.sql", postgresAssetPath)
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypePressure, queryFile, nil) measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypePressure, queryFile, nil)
@ -503,6 +544,7 @@ func (p *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue,
return measuredValues, nil return measuredValues, nil
} }
// SelectPressureByID returns a pressure value by his ID
func (p *Postgres) SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) { func (p *Postgres) SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectPressureByID.sql", postgresAssetPath) queryFile := fmt.Sprintf("%v/selectPressureByID.sql", postgresAssetPath)
args := []interface{}{id} args := []interface{}{id}
@ -518,6 +560,7 @@ func (p *Postgres) SelectPressureByID(ctx context.Context, id string) (*types.Me
return measuredValues[0], nil return measuredValues[0], nil
} }
// SelectSensorByID returns a sensor by his ID
func (p *Postgres) SelectSensorByID(ctx context.Context, id string) (*types.Sensor, error) { func (p *Postgres) SelectSensorByID(ctx context.Context, id string) (*types.Sensor, error) {
asset := fmt.Sprintf("%v/selectSensorByID.sql", postgresAssetPath) asset := fmt.Sprintf("%v/selectSensorByID.sql", postgresAssetPath)
queryBytes, err := Asset(asset) queryBytes, err := Asset(asset)
@ -545,6 +588,7 @@ func (p *Postgres) SelectSensorByID(ctx context.Context, id string) (*types.Sens
return sensor, nil return sensor, nil
} }
// SelectTemperatures returns temperature values
func (p *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) { func (p *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectTemperatures.sql", postgresAssetPath) queryFile := fmt.Sprintf("%v/selectTemperatures.sql", postgresAssetPath)
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeTemperature, queryFile, nil) measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeTemperature, queryFile, nil)
@ -554,6 +598,7 @@ func (p *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredVal
return measuredValues, nil return measuredValues, nil
} }
// SelectTemperatureByID returns a temperature value by his ID
func (p *Postgres) SelectTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) { func (p *Postgres) SelectTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectTemperatureByID.sql", postgresAssetPath) queryFile := fmt.Sprintf("%v/selectTemperatureByID.sql", postgresAssetPath)
args := []interface{}{id} args := []interface{}{id}
@ -597,10 +642,12 @@ func (p *Postgres) selectMeasuredValues(ctx context.Context, measuredValueType t
return measuredValues, nil return measuredValues, nil
} }
// UpdateDevices updates all specified devices into the database
func (p *Postgres) UpdateDevices(ctx context.Context, devices []*types.Device) error { func (p *Postgres) UpdateDevices(ctx context.Context, devices []*types.Device) error {
return nil 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 { func (p *Postgres) UpdateInfo(ctx context.Context, key string, value string) error {
asset := fmt.Sprintf("%v/updateInfo.sql", postgresAssetPath) asset := fmt.Sprintf("%v/updateInfo.sql", postgresAssetPath)
queryBytes, err := Asset(asset) queryBytes, err := Asset(asset)
@ -623,10 +670,12 @@ func (p *Postgres) UpdateInfo(ctx context.Context, key string, value string) err
return nil return nil
} }
// UpdateMeasuredValues updates the measured values which are stored in the database
func (p *Postgres) UpdateMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error { func (p *Postgres) UpdateMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
return nil return nil
} }
// UpdateSensors updates the sensors which are stored in the database
func (p *Postgres) UpdateSensors(ctx context.Context, sensots []*types.Sensor) error { func (p *Postgres) UpdateSensors(ctx context.Context, sensots []*types.Sensor) error {
return nil return nil
} }

View File

@ -2,11 +2,11 @@ package db_test
import ( import (
"context" "context"
"net/url"
"strings" "strings"
"testing" "testing"
"github.com/Masterminds/semver" "github.com/Masterminds/semver"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/storage/db" "github.com/go-flucky/flucky/pkg/storage/db"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/test/goldenfiles" "github.com/go-flucky/flucky/test/goldenfiles"
@ -23,14 +23,7 @@ var (
postgresContainerImage string = "docker.io/postgres/postgres" postgresContainerImage string = "docker.io/postgres/postgres"
postgresSettings = &config.DatabaseSettings{ storageEndpointString string = "postgres://flucky:flucky@markus-pc.trier.cryptic.systems/postgres?sslmode=disable"
Vendor: config.VendorPostgreSQL,
Host: "localhost",
Port: "5432",
User: "postgres",
Password: "postgres",
Database: "postgres",
}
goldenDevicesFilePath string = "test/goldenfiles/json/goldenDevices.json" goldenDevicesFilePath string = "test/goldenfiles/json/goldenDevices.json"
goldenSensorsFilePath string = "test/goldenfiles/json/goldenSensors.json" goldenSensorsFilePath string = "test/goldenfiles/json/goldenSensors.json"
@ -81,7 +74,10 @@ func TestPostgres(t *testing.T) {
load(t) load(t)
db, err := db.New(postgresSettings) storageEndpoint, err := url.Parse(storageEndpointString)
require.Nil(err)
db, err := db.New(storageEndpoint)
database = db database = db
require.Nil(err) require.Nil(err)

View File

@ -0,0 +1,3 @@
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;

View File

@ -1,10 +1,15 @@
package storage package storage
import ( import (
"context"
"fmt"
"math" "math"
"net/url"
"sort" "sort"
"github.com/go-flucky/flucky/pkg/internal/format" "github.com/go-flucky/flucky/pkg/internal/format"
"github.com/go-flucky/flucky/pkg/storage/db"
"github.com/go-flucky/flucky/pkg/storage/logfile"
"github.com/go-flucky/flucky/pkg/types" "github.com/go-flucky/flucky/pkg/types"
) )
@ -63,8 +68,49 @@ func Compression(measuredValues []*types.MeasuredValue) []*types.MeasuredValue {
return compressedMeasuredValues return compressedMeasuredValues
} }
// 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()
case "postgres":
database, err := db.New(storageEndpoint)
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) { func Round(measuredValues []*types.MeasuredValue, round float64) {
for _, measuredValue := range measuredValues { for _, measuredValue := range measuredValues {
measuredValue.Value = math.Round(measuredValue.Value/round) * round measuredValue.Value = math.Round(measuredValue.Value/round) * round
} }
} }
// Write measured values to 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 Write(ctx context.Context, measuredValues []*types.MeasuredValue, storageEndpoint *url.URL) error {
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()
if err := database.InsertMeasuredValues(ctx, measuredValues); err != nil {
return err
}
}
return fmt.Errorf("No supported scheme")
}