diff --git a/api/rest-api.go b/api/rest-api.go index e5bc412..45ed2c4 100644 --- a/api/rest-api.go +++ b/api/rest-api.go @@ -31,9 +31,10 @@ func NewRestAPI(connection string, weatherStorage storage.WeatherStorage, sensor //Start a new Rest-API instance func (api *weatherRestApi) Start() error { handler := api.handleRequests() - return http.ListenAndServe(api.connection, handler) // http.ListenAndServe(api.connection, handler) + return http.ListenAndServe(api.connection, handler) } +//Close the rest api func (api *weatherRestApi) Close() { } @@ -77,24 +78,24 @@ func (api *weatherRestApi) getData(w http.ResponseWriter, r *http.Request) { data, err := api.weaterStorage.GetData(query) if err != nil { - http.Error(w, "", http.StatusBadRequest) + http.Error(w, "error executing query", http.StatusBadRequest) return } + res := storage.ToMap(storage.GetOnlyQueriedFields(data, query)) json.NewEncoder(w).Encode(res) } func (api *weatherRestApi) randomWeatherHandler(w http.ResponseWriter, r *http.Request) { - datapoint := storage.NewRandomWeatherData(uuid.Nil) - w.Header().Add("content-type", "application/json") - json.NewEncoder(w).Encode(datapoint) + json.NewEncoder(w).Encode(storage.NewRandomWeatherData()) } func (api *weatherRestApi) randomWeatherListHandler(w http.ResponseWriter, r *http.Request) { - var datapoints = make([]storage.WeatherData, 0) + var datapoints = make([]*storage.WeatherData, 0) + for i := 0; i < 10; i++ { - datapoints = append(datapoints, storage.NewRandomWeatherData(uuid.Nil)) + datapoints = append(datapoints, storage.NewRandomWeatherData()) } w.Header().Add("content-type", "application/json") diff --git a/storage/inmemory-storage.go b/storage/inmemory-sensor-registry.go similarity index 92% rename from storage/inmemory-storage.go rename to storage/inmemory-sensor-registry.go index da528cd..1f77f44 100644 --- a/storage/inmemory-storage.go +++ b/storage/inmemory-sensor-registry.go @@ -1,7 +1,7 @@ package storage import ( - "fmt" + "errors" "github.com/google/uuid" ) @@ -21,7 +21,7 @@ func (registry *inmemorySensorRegistry) RegisterSensorByName(name string) (*Weat return nil, err } if exist { - return nil, fmt.Errorf("Sensorname already exists") + return nil, errors.New("sensorname already exists") } sensor := new(WeatherSensor) sensor.Name = name @@ -45,7 +45,7 @@ func (registry *inmemorySensorRegistry) ResolveSensorById(sensorId uuid.UUID) (* return s, nil } } - return nil, fmt.Errorf("sensor does not exist") + return nil, errors.New("sensor does not exist") } func (registry *inmemorySensorRegistry) ExistSensor(sensor *WeatherSensor) (bool, error) { diff --git a/storage/mongodb-storage.go b/storage/mongodb-storage.go index ddc7869..4c02bd0 100644 --- a/storage/mongodb-storage.go +++ b/storage/mongodb-storage.go @@ -2,7 +2,7 @@ package storage import ( "context" - "fmt" + "errors" "log" "time" "weather-data/config" @@ -59,7 +59,7 @@ func (registry *mongodbSensorRegistry) RegisterSensorByName(name string) (*Weath return nil, err } if exist { - return nil, fmt.Errorf("Sensorname already exists") + return nil, errors.New("sensorname already exists") } sensor := new(WeatherSensor) sensor.Name = name @@ -96,7 +96,7 @@ func (registry *mongodbSensorRegistry) ResolveSensorById(sensorId uuid.UUID) (*W return s, nil } } - return nil, fmt.Errorf("sensor does not exist") + return nil, errors.New("sensor does not exist") } func (registry *mongodbSensorRegistry) ExistSensor(sensor *WeatherSensor) (bool, error) { diff --git a/storage/sensor-registry.go b/storage/sensor-registry.go new file mode 100644 index 0000000..6cc88a4 --- /dev/null +++ b/storage/sensor-registry.go @@ -0,0 +1,20 @@ +package storage + +import "github.com/google/uuid" + +type SensorRegistry interface { + RegisterSensorByName(string) (*WeatherSensor, error) + ExistSensor(*WeatherSensor) (bool, error) + ResolveSensorById(uuid.UUID) (*WeatherSensor, error) + GetSensors() ([]*WeatherSensor, error) + Close() error +} + +//WeatherSensor is the data for a new Sensorregistration +type WeatherSensor struct { + Name string + Id uuid.UUID + Location string + Longitude float64 + Latitude float64 +} diff --git a/storage/weather-data.go b/storage/weather-data.go index bd29c3e..6b2ec66 100644 --- a/storage/weather-data.go +++ b/storage/weather-data.go @@ -3,8 +3,6 @@ package storage import ( "fmt" "math/rand" - "net/url" - "strconv" "time" "github.com/google/uuid" @@ -28,28 +26,35 @@ func GetSensorValueTypes() []SensorValueType { return []SensorValueType{Temperature, Pressure, Humidity, Co2Level} } -//WeatherStorage interface for different storage-implementations of weather data -type WeatherStorage interface { - Save(WeatherData) error - GetData(*WeatherQuery) ([]*WeatherData, error) - Close() error -} - -type SensorRegistry interface { - RegisterSensorByName(string) (*WeatherSensor, error) - ExistSensor(*WeatherSensor) (bool, error) - ResolveSensorById(uuid.UUID) (*WeatherSensor, error) - GetSensors() ([]*WeatherSensor, error) - Close() error -} - //WeatherData type type WeatherData struct { Values map[SensorValueType]float64 - SensorId uuid.UUID `json:"sensorId"` - TimeStamp time.Time `json:"timestamp"` + SensorId uuid.UUID + TimeStamp time.Time } +//NewRandomWeatherData creates random WeatherData +func NewRandomWeatherData() *WeatherData { + rand.Seed(time.Now().UnixNano()) + var data = new(WeatherData) + data.Values = make(map[SensorValueType]float64) + data.Values[Humidity] = rand.Float64() * 100 + data.Values[Pressure] = rand.Float64()*80 + 960 + data.Values[Temperature] = rand.Float64()*40 - 5 + data.Values[Co2Level] = rand.Float64()*50 + 375 + data.SensorId = uuid.New() + data.TimeStamp = time.Now() + return data +} + +//NewRandomWeatherData creates random WeatherData +func NewWeatherData() *WeatherData { + var data = new(WeatherData) + data.Values = make(map[SensorValueType]float64) + return data +} + +//OnlyQueriedValues remove all values not contained by the WeatherQuery func (data *WeatherData) OnlyQueriedValues(query *WeatherQuery) *WeatherData { for _, sensorValueType := range GetSensorValueTypes() { if !query.Values[sensorValueType] { @@ -59,19 +64,21 @@ func (data *WeatherData) OnlyQueriedValues(query *WeatherQuery) *WeatherData { return data } +//ToMap converts WeatherData to a map[string]interface{} func (data *WeatherData) ToMap() map[string]interface{} { mappedData := map[string]interface{}{ - "sensorId": data.SensorId.String(), - "timeStamp": data.TimeStamp.String(), + SensorId: data.SensorId.String(), + TimeStamp: data.TimeStamp.String(), } for sensorValueType, value := range data.Values { - mappedData[string(sensorValueType)] = value //strconv.FormatFloat(value, 'f', -1, 64) + mappedData[string(sensorValueType)] = value } return mappedData } +//FromMap converts a map[string]interface{} to WeatherData func FromMap(value map[string]interface{}) (*WeatherData, error) { var data = new(WeatherData) data.Values = make(map[SensorValueType]float64) @@ -113,6 +120,7 @@ func FromMap(value map[string]interface{}) (*WeatherData, error) { return data, nil } +//GetOnlyQueriedFields execute onlyQueriedValues on WeatherData slice an return this func GetOnlyQueriedFields(dataPoints []*WeatherData, query *WeatherQuery) []*WeatherData { for _, data := range dataPoints { data.OnlyQueriedValues(query) @@ -120,6 +128,7 @@ func GetOnlyQueriedFields(dataPoints []*WeatherData, query *WeatherQuery) []*Wea return dataPoints } +//ToMap mapps all WeatherData of a slice ToMap func ToMap(dataPoints []*WeatherData) []map[string]interface{} { var result = make([]map[string]interface{}, 0) for _, data := range dataPoints { @@ -127,77 +136,3 @@ func ToMap(dataPoints []*WeatherData) []map[string]interface{} { } return result } - -//WeatherSensor is the data for a new Sensorregistration -type WeatherSensor struct { - Name string - Id uuid.UUID - Location string - Longitude float64 - Latitude float64 -} - -type WeatherQuery struct { - Start time.Time - End time.Time - SensorId uuid.UUID - Values map[SensorValueType]bool -} - -func (query *WeatherQuery) Init() { - query.Start = time.Now().Add(-1 * time.Hour * 24 * 14) - query.End = time.Now() - query.SensorId = uuid.Nil - query.Values = make(map[SensorValueType]bool) - for _, sensorValueType := range GetSensorValueTypes() { - query.Values[sensorValueType] = true - } -} - -func ParseFromUrlQuery(query url.Values) (*WeatherQuery, error) { - result := new(WeatherQuery) - result.Init() - - start := query.Get("start") - end := query.Get("end") - - if len(start) != 0 { - if tval, err := time.Parse(time.RFC3339, start); err == nil { - result.Start = tval - } else if err != nil { - fmt.Println(err) - return nil, err - } - } - - if len(end) != 0 { - if tval, err := time.Parse(time.RFC3339, end); err == nil { - result.End = tval - } else if err != nil { - fmt.Println(err) - return nil, err - } - } - - for _, sensorValueType := range GetSensorValueTypes() { - queryParam := query.Get(string(sensorValueType)) - if bval, err := strconv.ParseBool(queryParam); err == nil { - result.Values[sensorValueType] = bval - } - } - - return result, nil -} - -//NewRandomWeatherData creates random WeatherData with given Location -func NewRandomWeatherData(sensorId uuid.UUID) WeatherData { - rand.Seed(time.Now().UnixNano()) - var data WeatherData - data.Values[Humidity] = rand.Float64() * 100 - data.Values[Pressure] = rand.Float64()*80 + 960 - data.Values[Temperature] = rand.Float64()*40 - 5 - data.Values[Co2Level] = rand.Float64()*50 + 375 - data.SensorId = sensorId - data.TimeStamp = time.Now() - return data -} diff --git a/storage/weather-query.go b/storage/weather-query.go new file mode 100644 index 0000000..69897fc --- /dev/null +++ b/storage/weather-query.go @@ -0,0 +1,68 @@ +package storage + +import ( + "fmt" + "net/url" + "strconv" + "time" + + "github.com/google/uuid" +) + +type WeatherQuery struct { + Start time.Time + End time.Time + SensorId uuid.UUID + Values map[SensorValueType]bool +} + +//NewWeatherQuery creates a new empty WeatherQuery +func NewWeatherQuery() *WeatherQuery { + query := new(WeatherQuery) + query.Values = make(map[SensorValueType]bool) + return query +} + +func (query *WeatherQuery) Init() { + query.Start = time.Now().Add(-1 * time.Hour * 24 * 14) + query.End = time.Now() + query.SensorId = uuid.Nil + for _, sensorValueType := range GetSensorValueTypes() { + query.Values[sensorValueType] = true + } +} + +func ParseFromUrlQuery(query url.Values) (*WeatherQuery, error) { + result := NewWeatherQuery() + result.Init() + + start := query.Get("start") + end := query.Get("end") + + if len(start) != 0 { + if tval, err := time.Parse(time.RFC3339, start); err == nil { + result.Start = tval + } else if err != nil { + fmt.Println(err) + return nil, err + } + } + + if len(end) != 0 { + if tval, err := time.Parse(time.RFC3339, end); err == nil { + result.End = tval + } else if err != nil { + fmt.Println(err) + return nil, err + } + } + + for _, sensorValueType := range GetSensorValueTypes() { + queryParam := query.Get(string(sensorValueType)) + if bval, err := strconv.ParseBool(queryParam); err == nil { + result.Values[sensorValueType] = bval + } + } + + return result, nil +} diff --git a/storage/weather-storage.go b/storage/weather-storage.go new file mode 100644 index 0000000..c274fa2 --- /dev/null +++ b/storage/weather-storage.go @@ -0,0 +1,8 @@ +package storage + +//WeatherStorage interface for different storage-implementations of weather data +type WeatherStorage interface { + Save(WeatherData) error + GetData(*WeatherQuery) ([]*WeatherData, error) + Close() error +} diff --git a/weathersource/mqtt-source.go b/weathersource/mqtt-source.go index 79ef072..e941598 100644 --- a/weathersource/mqtt-source.go +++ b/weathersource/mqtt-source.go @@ -77,8 +77,7 @@ func (source *mqttWeatherSource) mqttMessageHandler() mqtt.MessageHandler { lastWeatherData, found := source.getUnwrittenDatapoints(sensorId) if !found { - lastWeatherData = new(storage.WeatherData) - lastWeatherData.Values = make(map[storage.SensorValueType]float64) + lastWeatherData = storage.NewWeatherData() lastWeatherData.SensorId = sensorId source.lastWeatherDataPoints = append(source.lastWeatherDataPoints, lastWeatherData) } @@ -91,15 +90,6 @@ func (source *mqttWeatherSource) mqttMessageHandler() mqtt.MessageHandler { sensorValueType := storage.SensorValueType(regexTopic.FindStringSubmatch(msg.Topic())[3]) lastWeatherData.Values[sensorValueType] = value lastWeatherData.TimeStamp = time.Now() - - /* only use predefined sensorValueTypes - for _, sensorValueType := range storage.GetSensorValueTypes() { - if strings.HasSuffix(msg.Topic(), string(sensorValueType)) { - lastWeatherData.Values[sensorValueType], _ = strconv.ParseFloat(string(msg.Payload()), 64) - lastWeatherData.TimeStamp = time.Now() - } - } - */ } } @@ -107,7 +97,7 @@ func (source *mqttWeatherSource) publishDataValues() { for { for len(source.lastWeatherDataPoints) != 0 { current := *source.lastWeatherDataPoints[0] - diff := time.Now().Sub(current.TimeStamp) + diff := time.Since(current.TimeStamp) if diff >= source.config.MinDistToLastValue { if err := source.newWeatherData(current); err != nil { log.Fatal(err) @@ -120,7 +110,6 @@ func (source *mqttWeatherSource) publishDataValues() { } time.Sleep(source.config.PublishInterval) } - } func (source *mqttWeatherSource) getUnwrittenDatapoints(sensorId uuid.UUID) (*storage.WeatherData, bool) { diff --git a/weathersource/weather-source-base.go b/weathersource/weather-source-base.go new file mode 100644 index 0000000..86559f9 --- /dev/null +++ b/weathersource/weather-source-base.go @@ -0,0 +1,24 @@ +package weathersource + +import "weather-data/storage" + +//WeatherSourceBase is the lowlevel-implementation of the WeatherSource interface, intended to used by highlevel-implementations +type WeatherSourceBase struct { + newWeatherDataCallbackFuncs []NewWeatherDataCallbackFunc +} + +//AddNewWeatherDataCallback adds a new callbackMethod for incoming weather data +func (source *WeatherSourceBase) AddNewWeatherDataCallback(callback NewWeatherDataCallbackFunc) { + source.newWeatherDataCallbackFuncs = append(source.newWeatherDataCallbackFuncs, callback) +} + +//NewWeatherData executes all newWeatherDataCallbackFuncs for this datapoint +func (source *WeatherSourceBase) NewWeatherData(datapoint storage.WeatherData) error { + for _, callback := range source.newWeatherDataCallbackFuncs { + err := callback(datapoint) + if err != nil { + return err + } + } + return nil +} diff --git a/weathersource/weather-source.go b/weathersource/weather-source.go index 18a6772..bc60d19 100644 --- a/weathersource/weather-source.go +++ b/weathersource/weather-source.go @@ -10,24 +10,3 @@ type WeatherSource interface { AddNewWeatherDataCallback(NewWeatherDataCallbackFunc) Close() } - -//WeatherSourceBase is the lowlevel-implementation of the WeatherSource interface, intended to used by highlevel-implementations -type WeatherSourceBase struct { - newWeatherDataCallbackFuncs []NewWeatherDataCallbackFunc -} - -//AddNewWeatherDataCallback adds a new callbackMethod for incoming weather data -func (source *WeatherSourceBase) AddNewWeatherDataCallback(callback NewWeatherDataCallbackFunc) { - source.newWeatherDataCallbackFuncs = append(source.newWeatherDataCallbackFuncs, callback) -} - -//NewWeatherData executes all newWeatherDataCallbackFuncs for this datapoint -func (source *WeatherSourceBase) NewWeatherData(datapoint storage.WeatherData) error { - for _, callback := range source.newWeatherDataCallbackFuncs { - err := callback(datapoint) - if err != nil { - return err - } - } - return nil -}