From 3a090d190e9e874bdaa23a3a3ad36ad1ac8ce6d5 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Mon, 21 Sep 2020 19:36:42 +0200 Subject: [PATCH] fix: cli temperature read changes: - fix: read temperature values without daemon Add subcommand to read temperature values without starting the daemon - fix: implement measured value types Replace measured value types with constants - fix: add sensor pipelines Add functions which returns a channel with measured values - fix: filter measured values from a channel Add functions to filter measured values by sensor id or measured value types. --- cli/root.go | 2 + cli/temperature/temperature.go | 133 ++++++++++++++++++++++++++++++ pkg/cli/cli.go | 24 +++++- pkg/daemon/daemon.go | 35 ++++---- pkg/repository/db/postgres.go | 20 ++--- pkg/repository/db/sqlite.go | 20 ++--- pkg/repository/repository.go | 24 +++++- pkg/repository/repository_test.go | 26 +++++- pkg/sensor/bme280.go | 6 +- pkg/sensor/dht11.go | 4 +- pkg/sensor/dht22.go | 4 +- pkg/sensor/ds18b20.go | 2 +- pkg/sensor/interfaces.go | 3 +- pkg/sensor/sensor.go | 119 ++++++++++++++++++++++++++ pkg/types/measuredValue.go | 22 +++-- pkg/types/sensor.go | 47 +++++++---- pkg/types/sensor_test.go | 67 +++++++++++++++ 17 files changed, 481 insertions(+), 77 deletions(-) create mode 100644 cli/temperature/temperature.go create mode 100644 pkg/types/sensor_test.go diff --git a/cli/root.go b/cli/root.go index adfddf4..c1e6dd0 100644 --- a/cli/root.go +++ b/cli/root.go @@ -10,6 +10,7 @@ import ( "git.cryptic.systems/volker.raschek/flucky/cli/daemon" "git.cryptic.systems/volker.raschek/flucky/cli/sensor" + "git.cryptic.systems/volker.raschek/flucky/cli/temperature" "git.cryptic.systems/volker.raschek/flucky/pkg/config" "github.com/Masterminds/semver" @@ -38,6 +39,7 @@ func Execute(version *semver.Version) error { subCommands := []func(cmd *cobra.Command) error{ daemon.InitCmd, sensor.InitCmd, + temperature.InitCmd, } for _, subCommand := range subCommands { diff --git a/cli/temperature/temperature.go b/cli/temperature/temperature.go new file mode 100644 index 0000000..d5eebfe --- /dev/null +++ b/cli/temperature/temperature.go @@ -0,0 +1,133 @@ +package temperature + +import ( + "context" + "fmt" + "net/url" + "os" + + "git.cryptic.systems/volker.raschek/flucky/pkg/cli" + "git.cryptic.systems/volker.raschek/flucky/pkg/config" + "git.cryptic.systems/volker.raschek/flucky/pkg/repository" + "git.cryptic.systems/volker.raschek/flucky/pkg/sensor" + "git.cryptic.systems/volker.raschek/flucky/pkg/types" + "git.cryptic.systems/volker.raschek/go-logger" + "github.com/spf13/cobra" +) + +func InitCmd(cmd *cobra.Command) error { + temperatureCmd := &cobra.Command{ + Use: "temperature", + Short: "Read and list temperature values", + } + + readTemperatureCmd := &cobra.Command{ + Use: "read", + Short: "Read temperature values from sensors", + RunE: readTemperature, + } + readTemperatureCmd.Flags().Bool("persist", true, "Persist measured values to the repository") + + temperatureCmd.AddCommand(readTemperatureCmd) + cmd.AddCommand(temperatureCmd) + + return nil +} + +func readTemperature(cmd *cobra.Command, args []string) error { + configFile, err := cmd.Flags().GetString("config") + if err != nil { + return fmt.Errorf("No config file defined") + } + + persist, err := cmd.Flags().GetBool("persist") + if err != nil { + return fmt.Errorf("Flag persist not defined: %v", err) + } + + cnf, err := config.Read(configFile) + if err != nil { + return err + } + + dsnURL, err := url.Parse(cnf.DSN) + if err != nil { + return err + } + + logLevelString, err := cmd.Flags().GetString("loglevel") + if err != nil { + return err + } + + logLevel, err := logger.ParseLogLevel(logLevelString) + if err != nil { + return err + } + + flogger := logger.NewLogger(logLevel) + + repo, err := repository.New(dsnURL, flogger) + if err != nil { + return err + } + + sensorTypes, err := repo.GetSensors() + if err != nil { + return err + } + + sensorTypes, err = types.FilterSensorByMeasuredValueTypes(sensorTypes, types.Temperature) + if err != nil { + return err + } + + sensors := make([]sensor.Sensor, 0) + for i := range sensorTypes { + s, err := sensor.New(sensorTypes[i]) + if err != nil { + return err + } + sensors = append(sensors, s) + } + + measuredValueChannel, errorChannel := sensor.ReadPipeline(context.TODO(), sensors...) + + go func() { + for { + select { + case err, open := <-errorChannel: + if !open { + return + } + flogger.Error("%v", err) + } + } + }() + + measuredValues := make([]*types.MeasuredValue, 0) +LOOP: + for { + select { + case measuredValue, open := <-measuredValueChannel: + if !open { + break LOOP + } + measuredValues = append(measuredValues, measuredValue) + } + } + + err = cli.PrintMeasuredValues(measuredValues, os.Stdout) + if err != nil { + return err + } + + if persist { + err = repo.AddMeasuredValues(measuredValues...) + if err != nil { + return err + } + } + + return nil +} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c521bf4..dbd58d1 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -8,6 +8,25 @@ import ( "git.cryptic.systems/volker.raschek/flucky/pkg/types" ) +func PrintMeasuredValues(measuredValues []*types.MeasuredValue, w io.Writer) error { + + // declar tabwriter + tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0) + + fmt.Fprint(tw, "timestamp\ttype\tvalue\n") + + for i := range measuredValues { + fmt.Fprintf(tw, "%v\t%v\t%v\n", measuredValues[i].Date.String(), measuredValues[i].ValueType, measuredValues[i].Value) + } + + err := tw.Flush() + if err != nil { + return err + } + + return nil +} + // PrintSensors displays a list with all configured sensors func PrintSensors(sensors []*types.Sensor, w io.Writer) error { @@ -42,7 +61,10 @@ func PrintSensors(sensors []*types.Sensor, w io.Writer) error { fmt.Fprintf(tw, "%v\t%v\n", sensor.TickDuration, sensor.Enabled) } - tw.Flush() + err := tw.Flush() + if err != nil { + return err + } return nil } diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index ff718f6..f618b8f 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -16,8 +16,6 @@ import ( func Start(cnf *config.Config, flogger logger.Logger) error { - measuredValueChannel := make(chan *types.MeasuredValue, 0) - // load data source name (dsn) dsnURL, err := url.Parse(cnf.DSN) if err != nil { @@ -69,7 +67,7 @@ func Start(cnf *config.Config, flogger logger.Logger) error { continue } - flogger.Debug("Found sensor %v", repoSensor.GetName()) + flogger.Debug("Found sensor %v", repoSensor.Name) sensor, err := sensor.New(repoSensor) if err != nil { @@ -85,25 +83,23 @@ func Start(cnf *config.Config, flogger logger.Logger) error { parentCtx := context.Background() ctx, cancel := context.WithCancel(parentCtx) - for _, s := range sensors { - go func(sensor sensor.Sensor) { - for { - select { - case <-ctx.Done(): + measuredValueChannel, errorChannel := sensor.ReadTickingPipeline(ctx, sensors...) + + go func() { + for { + select { + case <-ctx.Done(): + return + case err, open := <-errorChannel: + if !open { return - case <-sensor.GetTicker().C: - measuredValues, err := sensor.Read() - if err != nil { - flogger.Error("%v", err) - continue - } - for _, measuredValue := range measuredValues { - measuredValueChannel <- measuredValue - } + } + if err != nil { + flogger.Error("%v", err) } } - }(s) - } + } + }() measuredValues := make([]*types.MeasuredValue, 0, 10) for { @@ -123,7 +119,6 @@ func Start(cnf *config.Config, flogger logger.Logger) error { case signal := <-interruptChannel: cancel() - close(measuredValueChannel) flogger.Info("Stopping daemon: Received process signal %v", signal.String()) diff --git a/pkg/repository/db/postgres.go b/pkg/repository/db/postgres.go index 3b40f7f..4a977c4 100644 --- a/pkg/repository/db/postgres.go +++ b/pkg/repository/db/postgres.go @@ -115,7 +115,7 @@ func (postgres *Postgres) InsertDevices(ctx context.Context, devices ...*types.D // InsertMeasuredValues into the database func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { - splittedMeasuredValues := make(map[string][]*types.MeasuredValue, 0) + splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) for _, measuredValue := range measuredValues { if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { @@ -164,11 +164,11 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu var queryFile string switch measuredValueType { - case "humidity": + case types.Humidity: queryFile = "insertHumidity.sql" - case "pressure": + case types.Pressure: queryFile = "insertPressure.sql" - case "temperature": + case types.Temperature: queryFile = "insertTemperature.sql" default: tx.Rollback() @@ -361,7 +361,7 @@ func (postgres *Postgres) SelectHumidity(ctx context.Context, id string) (*types } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "humidity" + measuredValue.ValueType = types.Humidity } return measuredValues[0], nil @@ -391,7 +391,7 @@ func (postgres *Postgres) SelectHumidities(ctx context.Context) ([]*types.Measur } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "humidity" + measuredValue.ValueType = types.Humidity } return measuredValues, nil @@ -462,7 +462,7 @@ func (postgres *Postgres) SelectPressure(ctx context.Context, id string) (*types } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "pressure" + measuredValue.ValueType = types.Pressure } return measuredValues[0], nil @@ -492,7 +492,7 @@ func (postgres *Postgres) SelectPressures(ctx context.Context) ([]*types.Measure } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "pressure" + measuredValue.ValueType = types.Pressure } return measuredValues, nil @@ -622,7 +622,7 @@ func (postgres *Postgres) SelectTemperature(ctx context.Context, id string) (*ty } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "temperatures" + measuredValue.ValueType = types.Temperature } return measuredValues[0], nil @@ -652,7 +652,7 @@ func (postgres *Postgres) SelectTemperatures(ctx context.Context) ([]*types.Meas } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "temperatures" + measuredValue.ValueType = types.Temperature } return measuredValues, nil diff --git a/pkg/repository/db/sqlite.go b/pkg/repository/db/sqlite.go index 82fa668..bfd03bf 100644 --- a/pkg/repository/db/sqlite.go +++ b/pkg/repository/db/sqlite.go @@ -114,7 +114,7 @@ func (sqlite *SQLite) InsertDevices(ctx context.Context, devices ...*types.Devic // InsertMeasuredValues into the database func (sqlite *SQLite) InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { - splittedMeasuredValues := make(map[string][]*types.MeasuredValue, 0) + splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) for _, measuredValue := range measuredValues { if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { @@ -163,11 +163,11 @@ func (sqlite *SQLite) InsertMeasuredValues(ctx context.Context, measuredValues . var queryFile string switch measuredValueType { - case "humidity": + case types.Humidity: queryFile = "insertHumidity.sql" - case "pressure": + case types.Pressure: queryFile = "insertPressure.sql" - case "temperature": + case types.Temperature: queryFile = "insertTemperature.sql" default: tx.Rollback() @@ -360,7 +360,7 @@ func (sqlite *SQLite) SelectHumidity(ctx context.Context, id string) (*types.Mea } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "humidity" + measuredValue.ValueType = types.Humidity } return measuredValues[0], nil @@ -390,7 +390,7 @@ func (sqlite *SQLite) SelectHumidities(ctx context.Context) ([]*types.MeasuredVa } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "humidity" + measuredValue.ValueType = types.Humidity } return measuredValues, nil @@ -461,7 +461,7 @@ func (sqlite *SQLite) SelectPressure(ctx context.Context, id string) (*types.Mea } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "pressure" + measuredValue.ValueType = types.Pressure } return measuredValues[0], nil @@ -491,7 +491,7 @@ func (sqlite *SQLite) SelectPressures(ctx context.Context) ([]*types.MeasuredVal } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "pressure" + measuredValue.ValueType = types.Pressure } return measuredValues, nil @@ -621,7 +621,7 @@ func (sqlite *SQLite) SelectTemperature(ctx context.Context, id string) (*types. } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "temperatures" + measuredValue.ValueType = types.Temperature } return measuredValues[0], nil @@ -651,7 +651,7 @@ func (sqlite *SQLite) SelectTemperatures(ctx context.Context) ([]*types.Measured } for _, measuredValue := range measuredValues { - measuredValue.ValueType = "temperatures" + measuredValue.ValueType = types.Temperature } return measuredValues, nil diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 21d6379..a1ac95e 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -98,8 +98,28 @@ func (repo *Repository) GetSensor(sensorID string) (*types.Sensor, error) { // GetSensors returns all sensors. If no sensors has been found, the function // returns nil. -func (repo *Repository) GetSensors() ([]*types.Sensor, error) { - return repo.database.SelectSensors(context.Background()) +func (repo *Repository) GetSensors(models ...string) ([]*types.Sensor, error) { + sensors, err := repo.database.SelectSensors(context.Background()) + switch { + case err != nil: + return nil, err + case len(models) > 0: + cachedSensors := make([]*types.Sensor, 0) + LOOP: + for i := range sensors { + for j := range models { + if strings.ToLower(sensors[i].Model) == strings.ToLower(models[j]) { + cachedSensors = append(cachedSensors, sensors[i]) + continue LOOP + } + } + } + return cachedSensors, nil + case len(models) <= 0: + fallthrough + default: + return sensors, err + } } // GetSensorsByDeviceID returns all sensors by a device id. If no sensor has diff --git a/pkg/repository/repository_test.go b/pkg/repository/repository_test.go index afe88d4..58d7199 100644 --- a/pkg/repository/repository_test.go +++ b/pkg/repository/repository_test.go @@ -188,6 +188,26 @@ func testBackend(t *testing.T, repo *repository.Repository) { require.NoError(err) require.Len(sensors, len(expectedSensors)) + sensors, err = repo.GetSensors("BME280") + require.NoError(err) + require.Len(sensors, 1) + require.JSONEq(jsonEncoder(expectedSensors[2]), jsonEncoder(sensors[0])) + + sensors, err = repo.GetSensors("DS18B20") + require.NoError(err) + require.Len(sensors, 1) + require.JSONEq(jsonEncoder(expectedSensors[1]), jsonEncoder(sensors[0])) + + sensors, err = repo.GetSensors("DHT11") + require.NoError(err) + require.Len(sensors, 1) + require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensors[0])) + + sensors, err = repo.GetSensors("DHT11", "DS18B20") + require.NoError(err) + require.Len(sensors, 2) + require.ElementsMatch(expectedSensors[0:2], sensors) + // Test: GetSensor sensor, err := repo.GetSensor(expectedSensors[0].ID) require.NoError(err) @@ -278,7 +298,7 @@ func testBackend(t *testing.T, repo *repository.Repository) { { ID: "2e5a297a-3da0-46ae-89d2-0fcab0f1d5f7", Value: 32, - ValueType: "humidity", + ValueType: types.Humidity, Date: *timeNow(require), SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a", CreationDate: *timeNow(require), @@ -287,7 +307,7 @@ func testBackend(t *testing.T, repo *repository.Repository) { { ID: "d69f1b62-0c6c-4058-b42c-4a2821bd220c", Value: 38, - ValueType: "pressure", + ValueType: types.Pressure, Date: *timeNow(require), SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a", CreationDate: *timeNow(require), @@ -296,7 +316,7 @@ func testBackend(t *testing.T, repo *repository.Repository) { { ID: "ea945ae0-412b-4561-a191-1f8f1f909fa4", Value: 35.4, - ValueType: "temperature", + ValueType: types.Temperature, Date: *timeNow(require), SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a", CreationDate: *timeNow(require), diff --git a/pkg/sensor/bme280.go b/pkg/sensor/bme280.go index 318e867..48d3b64 100644 --- a/pkg/sensor/bme280.go +++ b/pkg/sensor/bme280.go @@ -66,21 +66,21 @@ func (bme280 *BME280) Read() ([]*types.MeasuredValue, error) { { ID: uuid.NewV4().String(), Value: float64(humidityValue), - ValueType: "humidity", + ValueType: types.Humidity, Date: format.FormatedTime(), SensorID: bme280.ID, }, { ID: uuid.NewV4().String(), Value: float64(pressureValue), - ValueType: "pressure", + ValueType: types.Pressure, Date: format.FormatedTime(), SensorID: bme280.ID, }, { ID: uuid.NewV4().String(), Value: float64(temperatureValue), - ValueType: "temperature", + ValueType: types.Temperature, Date: format.FormatedTime(), SensorID: bme280.ID, }, diff --git a/pkg/sensor/dht11.go b/pkg/sensor/dht11.go index 5116354..e77a046 100644 --- a/pkg/sensor/dht11.go +++ b/pkg/sensor/dht11.go @@ -42,14 +42,14 @@ func (dht11 *DHT11) Read() ([]*types.MeasuredValue, error) { { ID: uuid.NewV4().String(), Value: float64(humidityValue), - ValueType: "humidity", + ValueType: types.Humidity, Date: format.FormatedTime(), SensorID: dht11.ID, }, { ID: uuid.NewV4().String(), Value: float64(temperatureValue), - ValueType: "temperature", + ValueType: types.Temperature, Date: format.FormatedTime(), SensorID: dht11.ID, }, diff --git a/pkg/sensor/dht22.go b/pkg/sensor/dht22.go index d25dee9..8facfc3 100644 --- a/pkg/sensor/dht22.go +++ b/pkg/sensor/dht22.go @@ -42,14 +42,14 @@ func (dht22 *DHT22) Read() ([]*types.MeasuredValue, error) { { ID: uuid.NewV4().String(), Value: float64(humidityValue), - ValueType: "humidity", + ValueType: types.Humidity, Date: format.FormatedTime(), SensorID: dht22.ID, }, { ID: uuid.NewV4().String(), Value: float64(temperatureValue), - ValueType: "temperature", + ValueType: types.Temperature, Date: format.FormatedTime(), SensorID: dht22.ID, }, diff --git a/pkg/sensor/ds18b20.go b/pkg/sensor/ds18b20.go index c8ac21e..16c5918 100644 --- a/pkg/sensor/ds18b20.go +++ b/pkg/sensor/ds18b20.go @@ -59,7 +59,7 @@ func (ds18b20 *DS18B20) Read() ([]*types.MeasuredValue, error) { { ID: uuid.NewV4().String(), Value: float64(temperatureValue), - ValueType: "temperature", + ValueType: types.Temperature, Date: format.FormatedTime(), SensorID: ds18b20.ID, }, diff --git a/pkg/sensor/interfaces.go b/pkg/sensor/interfaces.go index 4acdb3b..745d760 100644 --- a/pkg/sensor/interfaces.go +++ b/pkg/sensor/interfaces.go @@ -7,7 +7,8 @@ import ( ) type Sensor interface { - GetID() string GetTicker() *time.Ticker + + // Read single measured values from sensor Read() ([]*types.MeasuredValue, error) } diff --git a/pkg/sensor/sensor.go b/pkg/sensor/sensor.go index aa00abb..87b5742 100644 --- a/pkg/sensor/sensor.go +++ b/pkg/sensor/sensor.go @@ -1,6 +1,7 @@ package sensor import ( + "context" "errors" "sync" @@ -11,6 +12,124 @@ var ( ErrSensorModelNotMatched = errors.New("Sensor model not matched") ) +// FilterMeasuredValuesByTypes filters measured values by type +func FilterMeasuredValuesByTypes(ctx context.Context, inChannel <-chan *types.MeasuredValue, measuredValueTypes ...types.MeasuredValueType) <-chan *types.MeasuredValue { + outChannel := make(chan *types.MeasuredValue, 0) + go func() { + LOOP: + for { + select { + case <-ctx.Done(): + return + case measuredValue, open := <-inChannel: + if !open { + return + } + + for i := range measuredValueTypes { + if measuredValueTypes[i] == measuredValue.ValueType { + outChannel <- measuredValue + continue LOOP + } + } + } + } + }() + return outChannel +} + +// FilterMeasuredValuesBySensorIDs filters measured values by sensor id +func FilterMeasuredValuesBySensorIDs(ctx context.Context, inChannel <-chan *types.MeasuredValue, sensorIDs ...string) <-chan *types.MeasuredValue { + outChannel := make(chan *types.MeasuredValue, 0) + go func() { + LOOP: + for { + select { + case <-ctx.Done(): + return + case measuredValue, open := <-inChannel: + if !open { + return + } + + for i := range sensorIDs { + if sensorIDs[i] == measuredValue.SensorID { + outChannel <- measuredValue + continue LOOP + } + } + } + } + }() + return outChannel +} + +// ReadPipeline pipes for each sensor measured values until the context has been +// closed. The returned channels will be closed +func ReadPipeline(ctx context.Context, sensors ...Sensor) (<-chan *types.MeasuredValue, <-chan error) { + var ( + errorChannel = make(chan error, 0) + measuredValueChannel = make(chan *types.MeasuredValue, 0) + ) + + go func() { + wg := new(sync.WaitGroup) + for i := range sensors { + wg.Add(1) + + go func(s Sensor) { + defer wg.Done() + measuredValues, err := s.Read() + if err != nil { + errorChannel <- err + return + } + for i := range measuredValues { + measuredValueChannel <- measuredValues[i] + } + }(sensors[i]) + } + + wg.Wait() + close(errorChannel) + close(measuredValueChannel) + }() + + return measuredValueChannel, errorChannel +} + +// ReadTickingPipeline pipes for every tick on each sensor measured values until +// the context has been closed +func ReadTickingPipeline(ctx context.Context, sensors ...Sensor) (<-chan *types.MeasuredValue, <-chan error) { + var ( + errorChannel = make(chan error, 0) + measuredValueChannel = make(chan *types.MeasuredValue, 0) + ) + + for i := range sensors { + go func(s Sensor) { + for { + select { + case <-ctx.Done(): + return + case <-s.GetTicker().C: + measuredValues, err := s.Read() + if err != nil { + errorChannel <- err + break + } + + for i := range measuredValues { + measuredValueChannel <- measuredValues[i] + } + } + } + }(sensors[i]) + } + + return measuredValueChannel, errorChannel +} + // New returns a new sensor func New(sensor *types.Sensor) (Sensor, error) { switch sensor.Model { diff --git a/pkg/types/measuredValue.go b/pkg/types/measuredValue.go index 70435e6..eb10659 100644 --- a/pkg/types/measuredValue.go +++ b/pkg/types/measuredValue.go @@ -8,11 +8,19 @@ import ( // example from a sensor. It can contains different types, for example humidity // or temperature. type MeasuredValue struct { - ID string `json:"id" xml:"id"` - Value float64 `json:"value,string" xml:"value,string"` - ValueType string `json:"value_type" xml:"value_type"` - Date time.Time `json:"date" xml:"date"` - SensorID string `json:"sensor_id" xml:"sensor_id"` - CreationDate time.Time `json:"creation_date" xml:"creation_date"` - UpdateDate *time.Time `json:"update_date" xml:"update_date"` + ID string `json:"id" xml:"id"` + Value float64 `json:"value,string" xml:"value,string"` + ValueType MeasuredValueType `json:"value_type" xml:"value_type"` + Date time.Time `json:"date" xml:"date"` + SensorID string `json:"sensor_id" xml:"sensor_id"` + CreationDate time.Time `json:"creation_date" xml:"creation_date"` + UpdateDate *time.Time `json:"update_date" xml:"update_date"` } + +type MeasuredValueType string + +const ( + Humidity MeasuredValueType = "humidity" + Pressure = "pressure" + Temperature = "temperature" +) diff --git a/pkg/types/sensor.go b/pkg/types/sensor.go index 5f532fb..fc92a83 100644 --- a/pkg/types/sensor.go +++ b/pkg/types/sensor.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "time" ) @@ -22,21 +23,6 @@ type Sensor struct { UpdateDate *time.Time `json:"update_date" xml:"update_date"` } -// GetID returns the UUID of the sensor. -func (s *Sensor) GetID() string { - return s.ID -} - -// GetDeviceID returns the UUID of the configured device. -func (s *Sensor) GetDeviceID() string { - return s.DeviceID -} - -// GetName returns the name of the sensor. -func (s *Sensor) GetName() string { - return s.Name -} - // GetTicker returns a new ticker, which tick every when the sensor should be // read func (s *Sensor) GetTicker() *time.Ticker { @@ -46,3 +32,34 @@ func (s *Sensor) GetTicker() *time.Ticker { } return time.NewTicker(duration) } + +// FilterSensorByMeasuredValueTypes filters sensors by the measured values types +// which they measure +func FilterSensorByMeasuredValueTypes(sensors []*Sensor, measuredValueTypes ...MeasuredValueType) ([]*Sensor, error) { + cachedSensors := make([]*Sensor, 0) + + mapping := map[MeasuredValueType][]string{ + Humidity: {"BME280", "DHT11", "DHT22"}, + Pressure: {"BME280"}, + Temperature: {"BME280", "DHT11", "DHT22", "DS18B20"}, + } + + for i := range measuredValueTypes { + mappedSensors, ok := mapping[measuredValueTypes[i]] + if !ok { + return nil, fmt.Errorf("No mapping for measured values type %v available", measuredValueTypes[i]) + } + + LOOP: + for j := range sensors { + for k := range mappedSensors { + if sensors[j].Model == mappedSensors[k] { + cachedSensors = append(cachedSensors, sensors[j]) + continue LOOP + } + } + } + } + + return cachedSensors, nil +} diff --git a/pkg/types/sensor_test.go b/pkg/types/sensor_test.go new file mode 100644 index 0000000..0369d7c --- /dev/null +++ b/pkg/types/sensor_test.go @@ -0,0 +1,67 @@ +package types_test + +import ( + "testing" + + "git.cryptic.systems/volker.raschek/flucky/pkg/types" + "github.com/stretchr/testify/require" +) + +func TestFilterSensorByMeasuredValueTypes(t *testing.T) { + require := require.New(t) + + testCases := []struct { + sensors []*types.Sensor + expectedSensors []*types.Sensor + measuredValueTypes []types.MeasuredValueType + }{ + { + sensors: []*types.Sensor{ + {ID: "af7f845f-9a79-4638-8005-292754367d33", Model: "BME280"}, + {ID: "2d2c20e4-2628-4660-9f8c-a301b4e5933b", Model: "DHT11"}, + {ID: "30e69d52-c0cd-48a7-841a-3918b2ed5941", Model: "DHT22"}, + {ID: "2d1ecf2e-2dc6-48f6-8158-822cab6e561b", Model: "DS18B20"}, + }, + expectedSensors: []*types.Sensor{ + {ID: "af7f845f-9a79-4638-8005-292754367d33", Model: "BME280"}, + {ID: "2d2c20e4-2628-4660-9f8c-a301b4e5933b", Model: "DHT11"}, + {ID: "30e69d52-c0cd-48a7-841a-3918b2ed5941", Model: "DHT22"}, + {ID: "2d1ecf2e-2dc6-48f6-8158-822cab6e561b", Model: "DS18B20"}, + }, + measuredValueTypes: []types.MeasuredValueType{types.Temperature}, + }, + { + sensors: []*types.Sensor{ + {ID: "af7f845f-9a79-4638-8005-292754367d33", Model: "BME280"}, + {ID: "2d2c20e4-2628-4660-9f8c-a301b4e5933b", Model: "DHT11"}, + {ID: "30e69d52-c0cd-48a7-841a-3918b2ed5941", Model: "DHT22"}, + {ID: "2d1ecf2e-2dc6-48f6-8158-822cab6e561b", Model: "DS18B20"}, + }, + expectedSensors: []*types.Sensor{ + {ID: "af7f845f-9a79-4638-8005-292754367d33", Model: "BME280"}, + {ID: "2d2c20e4-2628-4660-9f8c-a301b4e5933b", Model: "DHT11"}, + {ID: "30e69d52-c0cd-48a7-841a-3918b2ed5941", Model: "DHT22"}, + }, + measuredValueTypes: []types.MeasuredValueType{types.Humidity}, + }, + { + sensors: []*types.Sensor{ + {ID: "af7f845f-9a79-4638-8005-292754367d33", Model: "BME280"}, + {ID: "2d2c20e4-2628-4660-9f8c-a301b4e5933b", Model: "DHT11"}, + {ID: "30e69d52-c0cd-48a7-841a-3918b2ed5941", Model: "DHT22"}, + {ID: "2d1ecf2e-2dc6-48f6-8158-822cab6e561b", Model: "DS18B20"}, + }, + expectedSensors: []*types.Sensor{ + {ID: "af7f845f-9a79-4638-8005-292754367d33", Model: "BME280"}, + }, + measuredValueTypes: []types.MeasuredValueType{types.Pressure}, + }, + } + + for i := range testCases { + s, err := types.FilterSensorByMeasuredValueTypes(testCases[i].sensors, testCases[i].measuredValueTypes...) + require.NoError(err) + require.ElementsMatch(testCases[i].expectedSensors, s) + require.Equal(len(testCases[i].expectedSensors), len(s)) + } +}