package services import ( "database/sql" "fmt" "time" ) type SensorValuesService struct { ctx *Context } type sensorValue struct { Timestamp int64 `json:"timestamp"` Value float64 `json:"value"` } func (s *SensorValuesService) Push(sensorId int64, value float64) (int64, error) { res, err := s.ctx.DB.Exec("INSERT INTO sensor_values (timestamp, sensor_id, value) VALUES (?, ?, ?)", time.Now().Unix(), sensorId, value) if err != nil { return 0, fmt.Errorf("failed to insert sensor value: %v", err) } _, err = s.ctx.DB.Exec("UPDATE sensors SET last_contact_at = ? WHERE id = ?", time.Now().Unix(), sensorId) if err != nil { return 0, fmt.Errorf("failed to update last_contact_at: %v", err) } return res.LastInsertId() } func (s *SensorValuesService) GetList(sensorId int64, from int64, to int64) ([]sensorValue, error) { return s.getValueItems(sensorId, from, to, getAutoSensorValuesInterval(from, to)) } func (s *SensorValuesService) GetLatest(sensorId int64, to int64) (*sensorValue, error) { var value = sensorValue{} row := s.ctx.DB.QueryRow("SELECT timestamp, value FROM sensor_values WHERE sensor_id = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT 1", sensorId, to) err := row.Scan(&value.Timestamp, &value.Value) if err != nil { return nil, err } return &value, nil } func (s *SensorValuesService) Cleanup(retentionInDays int64) error { if retentionInDays <= 0 { return fmt.Errorf("retentionInDays must be greater than 0") } _, err := s.ctx.DB.Exec("DELETE FROM sensor_values WHERE timestamp < ?", time.Now().Unix()-(retentionInDays*24*60*60)) if err != nil { return err } return nil } func (s *SensorValuesService) getValueListQuery(sensorId int64, from int64, to int64, divide int64) (*sql.Rows, error) { if divide == 1 { return s.ctx.DB.Query("SELECT timestamp, value FROM sensor_values WHERE sensor_id = ? AND timestamp > ? AND timestamp < ? ORDER BY timestamp ASC", sensorId, from, to) } return s.ctx.DB.Query( "SELECT (timestamp/?)*?, AVG(value) FROM sensor_values WHERE sensor_id = ? AND timestamp > ? AND timestamp < ? GROUP BY (timestamp/?) ORDER BY timestamp ASC", divide, divide, sensorId, from, to, divide, ) } func getAutoSensorValuesInterval(from int64, to int64) int64 { diff := to - from // 360 days -> 1 day interval if diff > 360*24*60*60 { return 24 * 60 * 60 } // 160 days -> 1 hour interval if diff > 160*24*60*60 { return 60 * 60 } // 80 days -> 10 minutes if diff > 80*24*60*60 { return 10 * 60 } return 1 } func (s *SensorValuesService) getValueItems(sensorId int64, from int64, to int64, divide int64) ([]sensorValue, error) { values := make([]sensorValue, 0) rows, err := s.getValueListQuery(sensorId, from, to, divide) if err != nil { if err == sql.ErrNoRows { return values, nil } return nil, err } defer rows.Close() for rows.Next() { item := sensorValue{} err := rows.Scan(&item.Timestamp, &item.Value) if err != nil { return nil, err } values = append(values, item) } err = rows.Err() if err != nil { return nil, err } return values, nil }