diff --git a/cmd/cmd.go b/cmd/cmd.go index 4356583..8823c2c 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -49,7 +49,7 @@ var rootCmd = &cobra.Command{ DeviceName: hostname, CreationDate: t, }, - Logfile: "/var/log/flucky/logfile.csv", + StorageEndpoint: "file:///var/log/flucky/logfile.csv", } err = config.Write(&cnf, configFile) diff --git a/cmd/daemon/daemon.go b/cmd/daemon/daemon.go index b2ddc25..d56b696 100644 --- a/cmd/daemon/daemon.go +++ b/cmd/daemon/daemon.go @@ -50,5 +50,5 @@ func InitCmd(cmd *cobra.Command, cnfFile *string, sversion *semver.Version) { cmd.AddCommand(daemonCmd) 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().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") } diff --git a/cmd/db/db.go b/cmd/db/db.go index e497245..0fb6f6b 100644 --- a/cmd/db/db.go +++ b/cmd/db/db.go @@ -1,13 +1,7 @@ package db import ( - "context" - "log" - "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" ) @@ -22,33 +16,33 @@ var dbCmd = &cobra.Command{ Short: "Operates with the configured database", Run: func(cmd *cobra.Command, args []string) { - // read configuration - cnf, err := config.Read(*configFile) - if err != nil { - log.Fatalln(err) - } + // // read configuration + // cnf, err := config.Read(*configFile) + // if err != nil { + // log.Fatalln(err) + // } - postgresDB, err := database.New(cnf.DatabaseSettings) - if err != nil { - log.Fatalf("%v", err) - } + // postgresDB, err := database.New(cnf.DatabaseSettings) + // if err != nil { + // log.Fatalf("%v", err) + // } - ctx := context.Background() + // ctx := context.Background() - devices := []*types.Device{ - &types.Device{ - DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c", - DeviceName: "raspberr-pi", - }, - &types.Device{ - DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c", - DeviceName: "raspberr-pi", - }, - } + // devices := []*types.Device{ + // &types.Device{ + // DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c", + // DeviceName: "raspberr-pi", + // }, + // &types.Device{ + // DeviceID: "1684df26-bc72-4435-a4f9-74b24bdb286c", + // DeviceName: "raspberr-pi", + // }, + // } - if err := postgresDB.InsertDevices(ctx, devices); err != nil { - log.Fatalln(err) - } + // if err := postgresDB.InsertDevices(ctx, devices); err != nil { + // log.Fatalln(err) + // } }, } diff --git a/cmd/humidity/list.go b/cmd/humidity/list.go index bc8fea8..b20efc9 100644 --- a/cmd/humidity/list.go +++ b/cmd/humidity/list.go @@ -1,16 +1,16 @@ package humidity import ( + "context" "fmt" "log" "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/cli" "github.com/go-flucky/flucky/pkg/config" - "github.com/go-flucky/flucky/pkg/rgbled" "github.com/spf13/cobra" ) @@ -26,19 +26,14 @@ var listTemperatureCmd = &cobra.Command{ log.Fatalln(err) } - logfile := logfile.New(cnf.Logfile) - - rgbLEDs := cnf.GetRGBLEDs(config.ENABLED) - if err := rgbled.Logfile(rgbLEDs); err != nil { - log.Fatalln(err) - } - - measuredValues, err := logfile.Read() + ctx := context.Background() + storageEndpoint, err := cnf.GetStorageEndpointURL() if err != nil { log.Fatalln(err) } - if err := rgbled.Off(rgbLEDs); err != nil { + measuredValues, err := storage.Read(ctx, storageEndpoint) + if err != nil { log.Fatalln(err) } diff --git a/cmd/humidity/read.go b/cmd/humidity/read.go index faeb692..ed064b7 100644 --- a/cmd/humidity/read.go +++ b/cmd/humidity/read.go @@ -5,7 +5,7 @@ import ( "log" "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/cli" @@ -56,8 +56,12 @@ var readHumidityCmd = &cobra.Command{ cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout) if logs { - measuredValuesLogfile := logfile.New(cnf.Logfile) - err := logfile.Append(measuredValuesLogfile, measuredValues) + storageEndpoint, err := cnf.GetStorageEndpointURL() + if err != nil { + log.Fatalln(err) + } + + err = storage.Write(ctx, measuredValues, storageEndpoint) if err != nil { log.Fatalln(err) } diff --git a/cmd/pressure/list.go b/cmd/pressure/list.go index 4987b81..503b709 100644 --- a/cmd/pressure/list.go +++ b/cmd/pressure/list.go @@ -1,14 +1,14 @@ package pressure import ( + "context" "fmt" "log" "os" "github.com/go-flucky/flucky/pkg/cli" "github.com/go-flucky/flucky/pkg/config" - "github.com/go-flucky/flucky/pkg/rgbled" - "github.com/go-flucky/flucky/pkg/storage/logfile" + "github.com/go-flucky/flucky/pkg/storage" "github.com/go-flucky/flucky/pkg/types" "github.com/spf13/cobra" ) @@ -25,19 +25,14 @@ var listTemperatureCmd = &cobra.Command{ log.Fatalln(err) } - logfile := logfile.New(cnf.Logfile) - - rgbLEDs := cnf.GetRGBLEDs(config.ENABLED) - if err := rgbled.Logfile(rgbLEDs); err != nil { - log.Fatalln(err) - } - - measuredValues, err := logfile.Read() + ctx := context.Background() + storageEndpoint, err := cnf.GetStorageEndpointURL() if err != nil { log.Fatalln(err) } - if err := rgbled.Off(rgbLEDs); err != nil { + measuredValues, err := storage.Read(ctx, storageEndpoint) + if err != nil { log.Fatalln(err) } diff --git a/cmd/pressure/read.go b/cmd/pressure/read.go index 5dddfc4..b9e938d 100644 --- a/cmd/pressure/read.go +++ b/cmd/pressure/read.go @@ -5,7 +5,7 @@ import ( "log" "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/cli" @@ -56,8 +56,12 @@ var readPressureCmd = &cobra.Command{ cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout) if logs { - measuredValuesLogfile := logfile.New(cnf.Logfile) - err := logfile.Append(measuredValuesLogfile, measuredValues) + storageEndpoint, err := cnf.GetStorageEndpointURL() + if err != nil { + log.Fatalln(err) + } + + err = storage.Write(ctx, measuredValues, storageEndpoint) if err != nil { log.Fatalln(err) } diff --git a/cmd/temperature/list.go b/cmd/temperature/list.go index 1c725a4..d16e144 100644 --- a/cmd/temperature/list.go +++ b/cmd/temperature/list.go @@ -1,16 +1,16 @@ package temperature import ( + "context" "fmt" "log" "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/cli" "github.com/go-flucky/flucky/pkg/config" - "github.com/go-flucky/flucky/pkg/rgbled" "github.com/spf13/cobra" ) @@ -26,19 +26,14 @@ var listTemperatureCmd = &cobra.Command{ log.Fatalln(err) } - logfile := logfile.New(cnf.Logfile) - - rgbLEDs := cnf.GetRGBLEDs(config.ENABLED) - if err := rgbled.Logfile(rgbLEDs); err != nil { - log.Fatalln(err) - } - - measuredValues, err := logfile.Read() + ctx := context.Background() + storageEndpoint, err := cnf.GetStorageEndpointURL() if err != nil { log.Fatalln(err) } - if err := rgbled.Off(rgbLEDs); err != nil { + measuredValues, err := storage.Read(ctx, storageEndpoint) + if err != nil { log.Fatalln(err) } diff --git a/cmd/temperature/read.go b/cmd/temperature/read.go index 1881730..89127f8 100644 --- a/cmd/temperature/read.go +++ b/cmd/temperature/read.go @@ -6,8 +6,7 @@ import ( "log" "os" - "github.com/go-flucky/flucky/pkg/rgbled" - "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/cli" @@ -42,15 +41,9 @@ var readTemperatureCmd = &cobra.Command{ return } - rgbLEDs := cnf.GetRGBLEDs(config.ENABLED) - if err := rgbled.Run(rgbLEDs); err != nil { - log.Fatalln(err) - } - ctx := context.Background() measuredValues, err := sensor.Read(ctx, sensors) if err != nil { - rgbled.Error(rgbLEDs) log.Fatalln(err) } @@ -60,14 +53,16 @@ var readTemperatureCmd = &cobra.Command{ cli.PrintMeasuredValues(measuredValues, cnf, os.Stdout) if logs { - measuredValuesLogfile := logfile.New(cnf.Logfile) - err := logfile.Append(measuredValuesLogfile, measuredValues) + storageEndpoint, err := cnf.GetStorageEndpointURL() + if err != nil { + log.Fatalln(err) + } + + err = storage.Write(ctx, measuredValues, storageEndpoint) if err != nil { - rgbled.Error(rgbLEDs) log.Fatalln(err) } } - rgbled.Off(rgbLEDs) }, } diff --git a/docker-compose.yml b/docker-compose.yml index b966653..679572f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,13 +4,13 @@ services: container_name: postgres environment: - PGTZ=${TZ} - - POSTGRES_PASSWORD=${PG_PASSWORD} - - POSTGRES_USER=${PG_USER} - - POSTGRES_DB=${PG_NAME} + - POSTGRES_DB=${POSTGRES_DB_NAME} + - POSTGRES_USER=${POSTGRES_DB_USER} + - POSTGRES_PASSWORD=${POSTGRES_DB_PASS} - TZ=${TZ} image: postgres:11.5-alpine ports: - - ${PG_EXTERN_PORT}:${PG_INTERN_PORT}/tcp + - 5432:5432 restart: always volumes: - /etc/localtime:/etc/localtime:ro diff --git a/pkg/config/config.go b/pkg/config/config.go index 00cd42b..173f453 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,59 +1,600 @@ package config import ( - "encoding/json" "fmt" - "os" - "path/filepath" - "regexp" + "net/url" + + "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}$") - -// 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 - +// Configuration of flucky +type Configuration struct { + Device *types.Device `json:"device"` + StorageEndpoint string `json:"storage_endpoint"` + RGBLEDs []*types.RGBLED `json:"rgb_leds"` + Sensors []*types.Sensor `json:"sensors"` } -// Write the configuration into a file, specified by the configuration filepath -func Write(cfg *Configuration, configFile string) error { +// AddRGBLED add a new RGBLED +func (c *Configuration) AddRGBLED(rgbLED *types.RGBLED) 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) + // 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 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) - if err != nil { - return fmt.Errorf("Can not write config file: %v", err) + // check if sensor has a valid device id + if sensor.DeviceID != c.Device.DeviceID { + sensor.DeviceID = c.Device.DeviceID } - 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) + // 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) + } +} + +// 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 } diff --git a/pkg/config/dbsettings.go b/pkg/config/dbsettings.go deleted file mode 100644 index c041493..0000000 --- a/pkg/config/dbsettings.go +++ /dev/null @@ -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" -) diff --git a/pkg/config/flucky.go b/pkg/config/flucky.go deleted file mode 100644 index de6f847..0000000 --- a/pkg/config/flucky.go +++ /dev/null @@ -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 -} diff --git a/pkg/config/io.go b/pkg/config/io.go new file mode 100644 index 0000000..00cd42b --- /dev/null +++ b/pkg/config/io.go @@ -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 + +} diff --git a/pkg/config/sensors.go b/pkg/config/sensors.go new file mode 100644 index 0000000..5519803 --- /dev/null +++ b/pkg/config/sensors.go @@ -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, + } +) diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 2c5100d..d688071 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -9,11 +9,9 @@ import ( "github.com/Masterminds/semver" "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/storage" "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/volker-raschek/go-logger/pkg/logger" ) @@ -25,12 +23,8 @@ var ( postgresUser = "postgres" postgresPassword = "postgres" - flogger logger.Logger -) - -func init() { flogger = logger.NewSilentLogger() -} +) func SetLogger(logger logger.Logger) { flogger = logger @@ -39,69 +33,63 @@ func SetLogger(logger logger.Logger) { // Start the daemon 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 flogger.Info("Use clean-cache-interval: %v", cleanCacheInterval.String()) flogger.Info("Use compression: %v", compression) flogger.Info("Round: %v", round) 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) + // measuredValuesLogfile := logfile.New(cnf.Logfile) - var postgres db.Database - if cnf.DatabaseSettings != nil { - 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() - } + // Producer + go sensor.ReadContinuously(ctx, cnf.GetSensors(config.ENABLED), measuredValueChannel, errorChannel) - rgbLEDs := cnf.GetRGBLEDs(config.ENABLED) - - go sensor.ReadContinuously(childContext, cnf.GetSensors(config.ENABLED), measuredValuesChannel, errorChannel) + // Distributor + //measuredValueChannels := distribute.MeasuredValues(ctx, 5, measuredValueChannel) for { - - err := rgbled.Run(rgbLEDs) - if err != nil { - flogger.Error("Can not turn on green info light: %v", err) - } - 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) - - err = rgbled.Error(rgbLEDs) - if err != nil { - flogger.Error("Can not turn on red info light: %v", err) - } - - time.Sleep(time.Second * 2) + case fatal, _ := <-fatalChannel: + flogger.Fatal("Received a fatal error: %v", fatal) + case interrupt := <-interruptChannel: + flogger.Info("Received OS Signal: %v", interrupt) + flogger.Info("Close context") + cancel() + flogger.Info("Close channels") + close(debugChannel) + close(infoChannel) + close(warnChannel) + close(errorChannel) + close(interruptChannel) + return case <-ticker: - err := rgbled.Logfile(rgbLEDs) - if err != nil { - flogger.Error("Can not turn on blue info light: %v", err) - } if round != 0 { storage.Round(measuredValuesCache, round) @@ -111,46 +99,14 @@ func Start(cnf *config.Configuration, cleanCacheInterval time.Duration, compress measuredValuesCache = storage.Compression(measuredValuesCache) } - if err := logfile.Append(measuredValuesLogfile, measuredValuesCache); err != nil { - err2 := rgbled.Error(rgbLEDs) - 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) - } - } + // if err := logfile.Append(measuredValuesLogfile, measuredValuesCache); err != nil { + // flogger.Error("Can not save caches measured values in logfile: %v", err) + // } measuredValuesCache = make([]*types.MeasuredValue, 0) - case measuredValues, _ := <-measuredValuesChannel: - measuredValuesCache = append(measuredValuesCache, measuredValues...) - - 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 + case measuredValue, _ := <-measuredValueChannel: + measuredValuesCache = append(measuredValuesCache, measuredValue) } } } diff --git a/pkg/internal/collect/measuredValues.go b/pkg/internal/collect/measuredValues.go index ce3b8e1..92b34cd 100644 --- a/pkg/internal/collect/measuredValues.go +++ b/pkg/internal/collect/measuredValues.go @@ -4,13 +4,13 @@ import ( "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) for { select { - case measuredValues, more := <-measuredValuesChannel: + case measuredValue, more := <-measuredValueChannel: if more { - cachedMeasuredValues = append(cachedMeasuredValues, measuredValues...) + cachedMeasuredValues = append(cachedMeasuredValues, measuredValue) continue } default: diff --git a/pkg/internal/distribute/distribute.go b/pkg/internal/distribute/distribute.go new file mode 100644 index 0000000..7b5ddae --- /dev/null +++ b/pkg/internal/distribute/distribute.go @@ -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 +} diff --git a/pkg/sensor/bme280.go b/pkg/sensor/bme280.go index 938244b..27ae6de 100644 --- a/pkg/sensor/bme280.go +++ b/pkg/sensor/bme280.go @@ -94,7 +94,7 @@ func (s *BME280) Read() ([]*types.MeasuredValue, error) { // ReadChannel reads the measured values from the sensor and writes them to a // 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 { defer wg.Done() } @@ -105,20 +105,22 @@ func (s *BME280) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue return } - measuredValuesChannel <- measuredValues + for _, measuredValue := range measuredValues { + measuredValueChannel <- measuredValue + } } // ReadContinously reads the measured values continously from the sensor and // 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 { select { case <-ctx.Done(): errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err()) return default: - s.ReadChannel(measuredValuesChannel, errorChannel, nil) + s.ReadChannel(measuredValueChannel, errorChannel, nil) } } } diff --git a/pkg/sensor/dht11.go b/pkg/sensor/dht11.go index cf02196..9a62e1c 100644 --- a/pkg/sensor/dht11.go +++ b/pkg/sensor/dht11.go @@ -68,7 +68,7 @@ func (s *DHT11) Read() ([]*types.MeasuredValue, error) { // ReadChannel reads the measured values from the sensor and writes them to a // 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 { defer wg.Done() } @@ -79,20 +79,22 @@ func (s *DHT11) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue, return } - measuredValuesChannel <- measuredValues + for _, measuredValue := range measuredValues { + measuredValueChannel <- measuredValue + } } // ReadContinously reads the measured values continously from the sensor and // 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 { select { case <-ctx.Done(): errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err()) return default: - s.ReadChannel(measuredValuesChannel, errorChannel, nil) + s.ReadChannel(measuredValueChannel, errorChannel, nil) } } } diff --git a/pkg/sensor/dht22.go b/pkg/sensor/dht22.go index ad45482..734822e 100644 --- a/pkg/sensor/dht22.go +++ b/pkg/sensor/dht22.go @@ -68,7 +68,7 @@ func (s *DHT22) Read() ([]*types.MeasuredValue, error) { // ReadChannel reads the measured values from the sensor and writes them to a // 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 { defer wg.Done() } @@ -79,20 +79,22 @@ func (s *DHT22) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue, return } - measuredValuesChannel <- measuredValues + for _, measuredValue := range measuredValues { + measuredValueChannel <- measuredValue + } } // ReadContinously reads the measured values continously from the sensor and // 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 { select { case <-ctx.Done(): errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err()) return default: - s.ReadChannel(measuredValuesChannel, errorChannel, nil) + s.ReadChannel(measuredValueChannel, errorChannel, nil) } } } diff --git a/pkg/sensor/ds18b20.go b/pkg/sensor/ds18b20.go index f18f220..6c254b6 100644 --- a/pkg/sensor/ds18b20.go +++ b/pkg/sensor/ds18b20.go @@ -67,7 +67,7 @@ func (s *DS18B20) Read() ([]*types.MeasuredValue, error) { // ReadChannel reads the measured values from the sensor and writes them to a // 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 { defer wg.Done() } @@ -78,20 +78,22 @@ func (s *DS18B20) ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValu return } - measuredValuesChannel <- measuredValues + for _, measuredValue := range measuredValues { + measuredValueChannel <- measuredValue + } } // ReadContinously reads the measured values continously from the sensor and // 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 { select { case <-ctx.Done(): errorChannel <- fmt.Errorf("%v: Context closed: %v", s.SensorName, ctx.Err()) return default: - s.ReadChannel(measuredValuesChannel, errorChannel, nil) + s.ReadChannel(measuredValueChannel, errorChannel, nil) } } } diff --git a/pkg/sensor/interfaces.go b/pkg/sensor/interfaces.go index 62c80da..f7f7203 100644 --- a/pkg/sensor/interfaces.go +++ b/pkg/sensor/interfaces.go @@ -10,6 +10,6 @@ import ( type Sensor interface { GetSensorModel() types.SensorModel Read() ([]*types.MeasuredValue, error) - ReadChannel(measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) - ReadContinously(ctx context.Context, measuredValuesChannel chan<- []*types.MeasuredValue, errorChannel chan<- error) + ReadChannel(measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error, wg *sync.WaitGroup) + ReadContinously(ctx context.Context, measuredValueChannel chan<- *types.MeasuredValue, errorChannel chan<- error) } diff --git a/pkg/sensor/sensor.go b/pkg/sensor/sensor.go index 5ffc0dd..4219885 100644 --- a/pkg/sensor/sensor.go +++ b/pkg/sensor/sensor.go @@ -14,30 +14,30 @@ import ( // Read measured values from sensors 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)) - ReadChannel(ctx, sensors, measuredValuesChannel, errorChannel) + ReadChannel(ctx, sensors, measuredValueChannel, errorChannel) errors := collect.Errors(errorChannel) if len(errors) > 0 { return nil, prittyprint.FormatErrors(errors) } - measuredValues := collect.MeasuredValues(measuredValuesChannel) + measuredValues := collect.MeasuredValues(measuredValueChannel) return measuredValues, nil } // ReadChannel reads the measured values from sensors and writes them to a // 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.Add(len(sensors)) for _, sensor := range sensors { - go sensor.ReadChannel(measuredValuesChannel, errorChannel, wg) + go sensor.ReadChannel(measuredValueChannel, errorChannel, wg) } 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 // 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 { select { case <-ctx.Done(): errorChannel <- fmt.Errorf("Context closed: %v", ctx.Err()) return default: - ReadChannel(ctx, sensors, measuredValuesChannel, errorChannel) + ReadChannel(ctx, sensors, measuredValueChannel, errorChannel) } } } diff --git a/pkg/storage/db/db.go b/pkg/storage/db/db.go index 310cdcf..2e5608f 100644 --- a/pkg/storage/db/db.go +++ b/pkg/storage/db/db.go @@ -3,8 +3,8 @@ package db import ( "database/sql" "fmt" + "net/url" - "github.com/go-flucky/flucky/pkg/config" _ "github.com/lib/pq" "github.com/volker-raschek/go-logger/pkg/logger" ) @@ -13,15 +13,14 @@ var ( flogger = logger.NewSilentLogger() ) -func New(databaseSettings *config.DatabaseSettings) (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(databaseSettings.Vendor.String(), connStr) +func New(storageEndpoint *url.URL) (Database, error) { + newDBO, err := sql.Open(storageEndpoint.Scheme, storageEndpoint.String()) if err != nil { return nil, err } - switch databaseSettings.Vendor { - case config.VendorPostgreSQL: + switch storageEndpoint.Scheme { + case "postgres": return &Postgres{ dbo: newDBO, }, nil diff --git a/pkg/storage/db/interfaces.go b/pkg/storage/db/interfaces.go index 3f3c23b..75b3fa8 100644 --- a/pkg/storage/db/interfaces.go +++ b/pkg/storage/db/interfaces.go @@ -29,6 +29,7 @@ type Database interface { 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) + 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) diff --git a/pkg/storage/db/postgres.go b/pkg/storage/db/postgres.go index 78b891e..7489fdc 100644 --- a/pkg/storage/db/postgres.go +++ b/pkg/storage/db/postgres.go @@ -11,6 +11,8 @@ import ( "github.com/Masterminds/semver" "github.com/go-flucky/flucky/pkg/types" + + // PostgreSQL lib _ "github.com/lib/pq" ) @@ -18,17 +20,19 @@ 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 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 { - schemaFunc := func(ctx context.Context, fromVersion *semver.Version, toVersion *semver.Version) error { 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) } - } +// 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) @@ -121,6 +126,8 @@ func (p *Postgres) DeleteDevices(ctx context.Context, devices []*types.Device) e 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) @@ -145,6 +152,7 @@ func (p *Postgres) DeleteSensors(ctx context.Context, sensors []*types.Sensor) e 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 { @@ -214,6 +222,7 @@ func (p *Postgres) DeleteMeasuredValues(ctx context.Context, measuredValues []*t 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) @@ -238,6 +247,7 @@ func (p *Postgres) InsertDevices(ctx context.Context, devices []*types.Device) e 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) @@ -260,6 +270,7 @@ func (p *Postgres) InsertInfo(ctx context.Context, key string, value string) 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) @@ -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) 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 @@ -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) 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 } +// 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) @@ -403,6 +415,7 @@ func (p *Postgres) InsertSensors(ctx context.Context, sensors []*types.Sensor) e 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) @@ -430,6 +443,7 @@ func (p *Postgres) SelectDeviceByID(ctx context.Context, id string) (*types.Devi 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) @@ -457,6 +471,7 @@ func (p *Postgres) SelectInfo(ctx context.Context, key string) (string, error) { 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) @@ -466,6 +481,7 @@ func (p *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue 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} @@ -481,6 +497,30 @@ func (p *Postgres) SelectHumidityByID(ctx context.Context, id string) (*types.Me 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: @@ -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) { queryFile := fmt.Sprintf("%v/selectPressures.sql", postgresAssetPath) 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 } +// 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} @@ -518,6 +560,7 @@ func (p *Postgres) SelectPressureByID(ctx context.Context, id string) (*types.Me 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) @@ -545,6 +588,7 @@ func (p *Postgres) SelectSensorByID(ctx context.Context, id string) (*types.Sens 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) @@ -554,6 +598,7 @@ func (p *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredVal 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} @@ -597,10 +642,12 @@ func (p *Postgres) selectMeasuredValues(ctx context.Context, measuredValueType t 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) @@ -623,10 +670,12 @@ func (p *Postgres) UpdateInfo(ctx context.Context, key string, value string) err 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 } diff --git a/pkg/storage/db/postgres_test.go b/pkg/storage/db/postgres_test.go index a2afc3f..79ff6fc 100644 --- a/pkg/storage/db/postgres_test.go +++ b/pkg/storage/db/postgres_test.go @@ -2,11 +2,11 @@ package db_test import ( "context" + "net/url" "strings" "testing" "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/types" "github.com/go-flucky/flucky/test/goldenfiles" @@ -23,14 +23,7 @@ var ( postgresContainerImage string = "docker.io/postgres/postgres" - postgresSettings = &config.DatabaseSettings{ - Vendor: config.VendorPostgreSQL, - Host: "localhost", - Port: "5432", - User: "postgres", - Password: "postgres", - Database: "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" @@ -81,7 +74,10 @@ func TestPostgres(t *testing.T) { load(t) - db, err := db.New(postgresSettings) + storageEndpoint, err := url.Parse(storageEndpointString) + require.Nil(err) + + db, err := db.New(storageEndpoint) database = db require.Nil(err) diff --git a/pkg/storage/db/sql/psql/schema/v0.1.1.sql b/pkg/storage/db/sql/psql/schema/v0.1.1.sql new file mode 100644 index 0000000..a15f2de --- /dev/null +++ b/pkg/storage/db/sql/psql/schema/v0.1.1.sql @@ -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; \ No newline at end of file diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 4cba3fc..4cf1e6a 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -1,10 +1,15 @@ package storage import ( + "context" + "fmt" "math" + "net/url" "sort" "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" ) @@ -63,8 +68,49 @@ func Compression(measuredValues []*types.MeasuredValue) []*types.MeasuredValue { 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) { for _, measuredValue := range measuredValues { 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") +}