fix: add postgres backend

changes:
- Add postgres backend
- Modified or added table attributes.
  UpdateDate, ForeignKeys, Booleans
- Fix test for sqlite and postgres. Compare json instead the struct.
This commit is contained in:
2020-06-01 22:36:57 +02:00
parent a1c28a0a2e
commit 4931c63c10
46 changed files with 822 additions and 245 deletions

View File

@ -52,6 +52,7 @@ func New(dsnURL *url.URL, flogger logger.Logger) (Database, error) {
// Load Queryfiles
queries := make(map[string]string, 0)
for _, asset := range AssetNames() {
if !strings.Contains(asset, dsnURL.Scheme) {
continue
}
@ -72,17 +73,19 @@ func New(dsnURL *url.URL, flogger logger.Logger) (Database, error) {
)
switch dsnURL.Scheme {
// case "postgres":
// // postgres://[user]:[password]@[host]:[port]/[path]?[query]
// newDBO, err := sql.Open(storageEndpoint.Scheme, storageEndpoint.String())
// if err != nil {
// return nil, err
// }
case "postgres":
// return &Postgres{
// dbo: newDBO,
// flogger: flogger,
// }, nil
// postgres://[user]:[password]@[host]:[port]/[path]?[query]
newDBO, err := sql.Open(dsnURL.Scheme, dsnURL.String())
if err != nil {
return nil, err
}
database = &Postgres{
dbo: newDBO,
flogger: flogger,
queries: queries,
}
case "sqlite3":

View File

@ -4,32 +4,27 @@ import (
"context"
"database/sql"
"fmt"
"path/filepath"
"time"
_ "github.com/lib/pq"
"github.com/volker-raschek/flucky/pkg/types"
"github.com/volker-raschek/go-logger/pkg/logger"
)
var (
postgresAssetPath = "pkg/storage/postgres"
)
// Postgres implementation
type Postgres struct {
dbo *sql.DB
flogger logger.Logger
queries map[string]string
}
// DeleteDevices from the database
func (postgres *Postgres) DeleteDevices(ctx context.Context, deviceIDs ...string) error {
asset := filepath.Join(postgresAssetPath, "deleteDevice.sql")
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "deleteDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
@ -55,12 +50,11 @@ func (postgres *Postgres) DeleteDevices(ctx context.Context, deviceIDs ...string
// DeleteSensors from the database
func (postgres *Postgres) DeleteSensors(ctx context.Context, sensorIDs ...string) error {
asset := filepath.Join(postgresAssetPath, "deleteSensor.sql")
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "deleteSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
@ -86,12 +80,11 @@ func (postgres *Postgres) DeleteSensors(ctx context.Context, sensorIDs ...string
// InsertDevices into the database
func (postgres *Postgres) InsertDevices(ctx context.Context, devices ...*types.Device) error {
asset := filepath.Join(postgresAssetPath, "insertDevice.sql")
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "insertDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
@ -105,7 +98,7 @@ func (postgres *Postgres) InsertDevices(ctx context.Context, devices ...*types.D
defer stmt.Close()
for _, device := range devices {
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate)
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate, &device.UpdateDate)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
@ -132,12 +125,11 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu
}
// General insert function
insert := func(tx *sql.Tx, asset string, measuredValues []*types.MeasuredValue) error {
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error {
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
stmt, err := tx.Prepare(query)
if err != nil {
@ -146,7 +138,15 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu
defer stmt.Close()
for _, measuredValue := range measuredValues {
_, err := stmt.Exec(&measuredValue.ID, &measuredValue.Value, &measuredValue.SensorID, &measuredValue.CreationDate)
_, err := stmt.Exec(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.Date,
&measuredValue.SensorID,
&measuredValue.CreationDate,
&measuredValue.UpdateDate,
)
if err != nil {
return fmt.Errorf("Failed to execute statement: %v", err)
}
@ -156,21 +156,21 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu
}
for measuredValueType, measuredValues := range splittedMeasuredValues {
var asset string
var queryFile string
switch measuredValueType {
case "humidity":
asset = filepath.Join(postgresAssetPath, "insertHumidity.sql")
queryFile = "insertHumidity.sql"
case "pressure":
asset = filepath.Join(postgresAssetPath, "insertPressure.sql")
queryFile = "insertPressure.sql"
case "temperature":
asset = filepath.Join(postgresAssetPath, "insertTemperature.sql")
queryFile = "insertTemperature.sql"
default:
tx.Rollback()
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
}
err := insert(tx, asset, measuredValues)
err := insert(tx, queryFile, measuredValues)
if err != nil {
tx.Rollback()
return err
@ -182,12 +182,11 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu
// InsertSensors into the database
func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.Sensor) error {
asset := filepath.Join(postgresAssetPath, "insertSensor.sql")
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "insertSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
@ -201,7 +200,21 @@ func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.S
defer stmt.Close()
for _, sensor := range sensors {
_, err = stmt.Exec(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
_, err = stmt.Exec(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
@ -211,75 +224,69 @@ func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.S
return tx.Commit()
}
// Scheme creates all required tables if not exist
func (postgres *Postgres) Scheme(ctx context.Context) error {
for _, query := range []string{
postgres.queries["createTableDevices.sql"],
postgres.queries["createTableSensors.sql"],
postgres.queries["createTableHumidites.sql"],
postgres.queries["createTablePressures.sql"],
postgres.queries["createTableTemperatures.sql"],
} {
_, err := postgres.dbo.ExecContext(ctx, query)
if err != nil {
return err
}
}
return nil
}
// SelectDevice from database
func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.Device, error) {
asset := filepath.Join(postgresAssetPath, "selectDevice.sql")
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "selectDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
devices, err := postgres.selectDevices(tx, query, id)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
row := stmt.QueryRow(id)
device := new(types.Device)
err = row.Scan(&device.ID, &device.Name, &device.Location, &device.CreationDate)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
return nil, err
}
return device, nil
if len(devices) == 0 {
return nil, nil
}
return devices[0], nil
}
// SelectDevices from the database
func (postgres *Postgres) SelectDevices(ctx context.Context) ([]*types.Device, error) {
asset := filepath.Join(postgresAssetPath, "selectDevices.sql")
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "selectDevices.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
devices, err := postgres.selectDevices(tx, query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query()
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
devices := make([]*types.Device, 0)
for rows.Next() {
device := new(types.Device)
err = rows.Scan(&device.ID, &device.Name, &device.Location, &device.CreationDate)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
devices = append(devices, device)
return nil, err
}
err = tx.Commit()
@ -290,32 +297,218 @@ func (postgres *Postgres) SelectDevices(ctx context.Context) ([]*types.Device, e
return devices, nil
}
// SelectSensor from database
func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
asset := filepath.Join(postgresAssetPath, "selectSensor.sql")
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
func (postgres *Postgres) selectDevices(tx *sql.Tx, query string, args ...interface{}) ([]*types.Device, error) {
stmt, err := tx.Prepare(query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
row := stmt.QueryRow(id)
sensor := new(types.Sensor)
err = row.Scan(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
devices := make([]*types.Device, 0)
for rows.Next() {
device := new(types.Device)
err = rows.Scan(
&device.ID,
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
devices = append(devices, device)
}
return devices, nil
}
// SelectHumidity returns humidity from the database
func (postgres *Postgres) SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectHumidity.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "humidity"
}
return measuredValues[0], nil
}
// SelectHumidities returns humidities from the database
func (postgres *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectHumidities.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, nil)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "humidity"
}
return measuredValues, nil
}
func (postgres *Postgres) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) {
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return nil, err
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
tx.Rollback()
return nil, err
}
measuredValues := make([]*types.MeasuredValue, 0)
for rows.Next() {
measuredValue := new(types.MeasuredValue)
err := rows.Scan(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.SensorID,
&measuredValue.Date,
&measuredValue.CreationDate,
&measuredValue.UpdateDate,
)
if err != nil {
tx.Rollback()
return nil, err
}
measuredValues = append(measuredValues, measuredValue)
}
return measuredValues, nil
}
// SelectPressure returns pressure from the database
func (postgres *Postgres) SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectPressure.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "pressure"
}
return measuredValues[0], nil
}
// SelectPressures returns pressure from the database
func (postgres *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectPressures.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, nil)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "pressure"
}
return measuredValues, nil
}
// SelectSensor from database
func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
queryFile := "selectSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := postgres.selectSensors(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
@ -323,42 +516,29 @@ func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.S
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
return sensor, nil
if len(sensors) == 0 {
return nil, nil
}
return sensors[0], nil
}
// SelectSensors from the database
func (postgres *Postgres) SelectSensors(ctx context.Context) ([]*types.Sensor, error) {
asset := filepath.Join(postgresAssetPath, "selectSensors.sql")
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "selectSensors.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
sensors, err := postgres.selectSensors(tx, query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query()
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
sensors := make([]*types.Sensor, 0)
for rows.Next() {
sensor := new(types.Sensor)
err = rows.Scan(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
sensors = append(sensors, sensor)
return nil, err
}
err = tx.Commit()
@ -369,12 +549,193 @@ func (postgres *Postgres) SelectSensors(ctx context.Context) ([]*types.Sensor, e
return sensors, nil
}
func (postgres *Postgres) selectSensors(tx *sql.Tx, query string, args ...interface{}) ([]*types.Sensor, error) {
stmt, err := tx.Prepare(query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
sensors := make([]*types.Sensor, 0)
for rows.Next() {
sensor := new(types.Sensor)
err = rows.Scan(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
sensors = append(sensors, sensor)
}
return sensors, nil
}
// SelectTemperature returns temperatures from the database
func (postgres *Postgres) SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectTemperature.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "temperatures"
}
return measuredValues[0], nil
}
// SelectTemperatures returns temperatures from the database
func (postgres *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectTemperatures.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, nil)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "temperatures"
}
return measuredValues, nil
}
// UpdateDevices updates a device in the database
func (postgres *Postgres) UpdateDevices(ctx context.Context, devices ...*types.Device) error {
return nil
queryFile := "updateDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return err
}
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, device := range devices {
now := time.Now()
device.UpdateDate = &now
_, err := stmt.Exec(
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
&device.ID,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// UpdateSensors updates a sensor in the database
func (postgres *Postgres) UpdateSensors(ctx context.Context, sensor ...*types.Sensor) error {
return nil
func (postgres *Postgres) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "updateSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return err
}
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, sensor := range sensors {
now := time.Now()
sensor.UpdateDate = &now
_, err := stmt.Exec(
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
&sensor.ID,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS devices (
device_id CHAR(36) CONSTRAINT pk_devices PRIMARY KEY,
device_name VARCHAR(64) NOT NULL,
device_location VARCHAR(64),
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP
);

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS humidities (
id CHAR(36) CONSTRAINT pk_humidities PRIMARY KEY,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP
);
ALTER TABLE humidities
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS pressures (
id CHAR(36) CONSTRAINT pk_pressures PRIMARY KEY,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP
);
ALTER TABLE pressures
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;

View File

@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS sensors (
sensor_id CHAR(36) CONSTRAINT pk_sensors PRIMARY KEY,
sensor_name VARCHAR(64) NOT NULL,
sensor_location VARCHAR(64),
wire_id VARCHAR(64),
i2c_bus VARCHAR(255),
i2c_address VARCHAR(12),
gpio_number VARCHAR(6),
sensor_model VARCHAR(16) NOT NULL,
sensor_enabled BOOLEAN DEFAULT TRUE NOT NULL,
tick_duration VARCHAR(6) NOT NULL,
device_id CHAR(36) NOT NULL,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP
);
ALTER TABLE sensors
ADD FOREIGN KEY (device_id)
REFERENCES devices(device_id)
ON DELETE CASCADE
ON UPDATE CASCADE;

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS temperatures (
id CHAR(36) CONSTRAINT pk_temperatures PRIMARY KEY,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP
);
ALTER TABLE temperatures
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;

View File

@ -1,2 +1,2 @@
DELETE FROM sensors
DELETE FROM devices
WHERE device_id = $1;

View File

@ -2,6 +2,7 @@ INSERT INTO devices (
device_id,
device_name,
device_location,
creation_date
creation_date,
update_date
)
VALUES ($1, $2, $3, $4);
VALUES ($1, $2, $3, $4, $5);

View File

@ -1,10 +1,9 @@
INSERT INTO humidities (
humidity_id,
humidity_value,
humidity_from_date,
humidity_till_date,
id,
value,
date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);
VALUES ($1, $2, $3, $4, $5, $6);

View File

@ -1,10 +1,9 @@
INSERT INTO pressures (
pressure_id,
pressure_value,
pressure_from_date,
pressure_till_date,
id,
value,
date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);
VALUES ($1, $2, $3, $4, $5, $6);

View File

@ -8,7 +8,9 @@ INSERT INTO sensors (
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);

View File

@ -1,10 +1,9 @@
INSERT INTO temperatures (
temperature_id,
temperature_value,
temperature_from_date,
temperature_till_date,
id,
value,
date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);
VALUES ($1, $2, $3, $4, $5, $6);

View File

@ -2,7 +2,8 @@ SELECT
device_id,
device_name,
device_location,
creation_date
creation_date,
update_date
FROM
devices
WHERE device_id = $1;

View File

@ -2,6 +2,9 @@ SELECT
device_id,
device_name,
device_location,
creation_date
creation_date,
update_date
FROM
devices;
devices
ORDER BY
device_id ASC;

View File

@ -0,0 +1,9 @@
SELECT
id,
value,
date,
sensor_id,
creation_date,
update_date
FROM
humidities;

View File

@ -0,0 +1,11 @@
SELECT
id,
value,
date,
sensor_id,
creation_date,
update_date
FROM
humidities
WHERE
humidity_id = $1

View File

@ -0,0 +1,11 @@
SELECT
id,
value,
date,
sensor_id,
creation_date,
update_date
FROM
pressures
WHERE
pressure_id = $1

View File

@ -0,0 +1,9 @@
SELECT
id,
value,
date,
sensor_id,
creation_date,
update_date
FROM
pressures;

View File

@ -8,8 +8,10 @@ SELECT
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date
creation_date,
update_date
FROM
sensors
WHERE

View File

@ -8,7 +8,11 @@ SELECT
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date
creation_date,
update_date
FROM
sensors;
sensors
ORDER BY
sensor_id ASC;

View File

@ -0,0 +1,11 @@
SELECT
id,
value,
date,
sensor_id,
creation_date,
update_date
FROM
temperatures
WHERE
temperature_id = $1

View File

@ -0,0 +1,9 @@
SELECT
id,
value,
date,
sensor_id,
creation_date,
update_date
FROM
temperatures

View File

@ -1,8 +1,8 @@
UPDATE device
UPDATE devices
SET
device_id = $1,
device_name = $2,
device_location = $3,
creation_date = $4
device_name = $1,
device_location = $2,
creation_date = $3,
update_date = $4
WHERE
device_id = $5,
device_id = $5

View File

@ -1,15 +1,16 @@
UPDATE sensors
SET
sensor_id = $1,
sensor_name = $2,
sensor_location = $3,
wire_id = $4,
i2c_bus = $5,
i2c_address = $6,
gpio_number = $7,
sensor_model = $8,
sensor_enabled = $9,
sensor_name = $1,
sensor_location = $2,
wire_id = $3,
i2c_bus = $4,
i2c_address = $5,
gpio_number = $6,
sensor_model = $7,
sensor_enabled = $8,
tick_duration = $9,
device_id = $10,
creation_date = $11
creation_date = $11,
update_date = $12
WHERE
sensor_id = $12;
sensor_id = $13;

View File

@ -97,7 +97,7 @@ func (sqlite *SQLite) InsertDevices(ctx context.Context, devices ...*types.Devic
defer stmt.Close()
for _, device := range devices {
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate)
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate, &device.UpdateDate)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
@ -140,8 +140,8 @@ func (sqlite *SQLite) InsertMeasuredValues(ctx context.Context, measuredValues .
_, err := stmt.Exec(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.SensorID,
&measuredValue.Date,
&measuredValue.SensorID,
&measuredValue.CreationDate,
&measuredValue.UpdateDate,
)
@ -212,6 +212,7 @@ func (sqlite *SQLite) InsertSensors(ctx context.Context, sensors ...*types.Senso
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
tx.Rollback()
@ -315,6 +316,7 @@ func (sqlite *SQLite) selectDevices(tx *sql.Tx, query string, args ...interface{
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
@ -574,6 +576,7 @@ func (sqlite *SQLite) selectSensors(tx *sql.Tx, query string, args ...interface{
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
@ -674,6 +677,7 @@ func (sqlite *SQLite) UpdateDevices(ctx context.Context, devices ...*types.Devic
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
&device.ID,
)
if err != nil {
@ -718,6 +722,7 @@ func (sqlite *SQLite) UpdateSensors(ctx context.Context, sensors ...*types.Senso
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
&sensor.ID,
)
if err != nil {

View File

@ -1,6 +1,7 @@
CREATE TABLE IF NOT EXISTS devices (
device_id CHAR(37) PRIMARY KEY,
device_id CHAR(36) PRIMARY KEY,
device_name VARCHAR(64) NOT NULL,
device_location VARCHAR(64),
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP
);

View File

@ -1,8 +1,8 @@
CREATE TABLE IF NOT EXISTS humidities (
id CHAR(37) PRIMARY KEY,
id CHAR(36) PRIMARY KEY,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP NOT NULL,
sensor_id CHAR(37) NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP,
FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id)

View File

@ -1,8 +1,8 @@
CREATE TABLE IF NOT EXISTS pressures (
id CHAR(37) PRIMARY KEY,
id CHAR(36) PRIMARY KEY,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP NOT NULL,
sensor_id CHAR(37) NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP,
FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id)

View File

@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS sensors (
sensor_id CHAR(37) PRIMARY KEY,
sensor_id CHAR(36) PRIMARY KEY,
sensor_name VARCHAR(64) NOT NULL,
sensor_location VARCHAR(64),
wire_id VARCHAR(64),
@ -9,7 +9,8 @@ CREATE TABLE IF NOT EXISTS sensors (
sensor_model VARCHAR(16) NOT NULL,
sensor_enabled INTEGER(1) DEFAULT 1 NOT NULL,
tick_duration VARCHAR(6) NOT NULL,
device_id CHAR(37) NOT NULL,
device_id CHAR(36) NOT NULL,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_date TIMESTAMP,
FOREIGN KEY(device_id) REFERENCES devices(device_id)
);

View File

@ -1,8 +1,8 @@
CREATE TABLE IF NOT EXISTS temperatures (
id CHAR(37) PRIMARY KEY,
id CHAR(36) PRIMARY KEY,
value NUMERIC(10,3) NOT NULL,
date TIMESTAMP NOT NULL,
sensor_id CHAR(37) NOT NULL,
sensor_id CHAR(36) NOT NULL,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP,
FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id)

View File

@ -2,6 +2,7 @@ INSERT INTO devices (
device_id,
device_name,
device_location,
creation_date
creation_date,
update_date
)
VALUES ($1, $2, $3, $4);
VALUES ($1, $2, $3, $4, $5);

View File

@ -10,6 +10,7 @@ INSERT INTO sensors (
sensor_enabled,
tick_duration,
device_id,
creation_date
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);

View File

@ -2,7 +2,8 @@ SELECT
device_id,
device_name,
device_location,
creation_date
creation_date,
update_date
FROM
devices
WHERE device_id = $1;

View File

@ -2,6 +2,7 @@ SELECT
device_id,
device_name,
device_location,
creation_date
creation_date,
update_date
FROM
devices;

View File

@ -10,7 +10,8 @@ SELECT
sensor_enabled,
tick_duration,
device_id,
creation_date
creation_date,
update_date
FROM
sensors
WHERE

View File

@ -10,7 +10,8 @@ SELECT
sensor_enabled,
tick_duration,
device_id,
creation_date
creation_date,
update_date
FROM
sensors
ORDER BY

View File

@ -2,6 +2,7 @@ UPDATE devices
SET
device_name = $1,
device_location = $2,
creation_date = $3
creation_date = $3,
update_date = $4
WHERE
device_id = $4
device_id = $5

View File

@ -10,6 +10,7 @@ SET
sensor_enabled = $8,
tick_duration = $9,
device_id = $10,
creation_date = $11
creation_date = $11,
update_date = $12
WHERE
sensor_id = $12;
sensor_id = $13;

View File

@ -1,7 +1,9 @@
package repository_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"math/rand"
"net/url"
@ -19,7 +21,6 @@ import (
)
func TestPostgresBackend(t *testing.T) {
t.Skip("Database backend postgres not completly implemented")
require := require.New(t)
dockerClient, err := dockerutils.New()
require.NoError(err)
@ -27,7 +28,7 @@ func TestPostgresBackend(t *testing.T) {
rand.Seed(time.Now().Unix())
postgresHostPort := rand.Intn(10024-1024) + 1024
postgresDBPasswort := "postgres"
postgresContainerID, err := dockerClient.NewBuilder("postgres:latest").
postgresContainerID, err := dockerClient.NewBuilder("postgres:13-alpine").
Port(fmt.Sprintf("%v:5432/tcp", postgresHostPort)).
Pull().
AddEnv("PGTZ", "Europe/Berlin").
@ -41,8 +42,10 @@ func TestPostgresBackend(t *testing.T) {
t.Cleanup(cleanup)
require.NoError(err)
time.Sleep(time.Second * 10)
// postgres://[user]:[password]@[host]:[port]/[path]?[query]
dsnURL, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@localhost:%v", postgresDBPasswort, postgresHostPort))
dsnURL, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@127.0.0.1:%v?sslmode=disable", postgresDBPasswort, postgresHostPort))
require.NoError(err)
repo, err := repository.New(dsnURL, logger.NewDefaultLogger(logger.LogLevelDebug))
@ -56,7 +59,7 @@ func TestSQLiteBackend(t *testing.T) {
workspace := filepath.Join(os.TempDir(), uuid.NewV4().String())
err := os.MkdirAll(workspace, 0755)
require.NoError(err)
defer os.RemoveAll(workspace)
t.Cleanup(func() { os.RemoveAll(workspace) })
dsnURL, err := url.Parse(fmt.Sprintf("sqlite3://%v/test.db", workspace))
require.NoError(err)
@ -72,14 +75,14 @@ func testBackend(t *testing.T, repo *repository.Repository) {
location := uuid.NewV4().String()
expectedDevices := []*types.Device{
{
ID: "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f",
Name: "f2b245eb-b15f-40e1-9212-9a645907b710",
ID: "39b8f150-8abf-4539-9f16-7f68cedb1649",
Name: "62e3978f-2198-4aa9-9d6f-cdc91a468b00",
Location: &location,
CreationDate: *timeNow(require),
},
{
ID: "39b8f150-8abf-4539-9f16-7f68cedb1649",
Name: "62e3978f-2198-4aa9-9d6f-cdc91a468b00",
ID: "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f",
Name: "f2b245eb-b15f-40e1-9212-9a645907b710",
Location: &location,
CreationDate: *timeNow(require),
},
@ -93,25 +96,28 @@ func testBackend(t *testing.T, repo *repository.Repository) {
devices, err := repo.GetDevices()
require.NoError(err)
require.Len(devices, len(expectedDevices))
require.EqualValues(expectedDevices, devices)
require.JSONEq(jsonEncoder(expectedDevices), jsonEncoder(devices))
// Test: GetDevice
device, err := repo.GetDevice(expectedDevices[0].ID)
require.NoError(err)
require.EqualValues(expectedDevices[0], device)
require.JSONEq(jsonEncoder(expectedDevices[0]), jsonEncoder(device))
// Test: RemoveDevice
err = repo.RemoveDevices(expectedDevices[1].ID)
err = repo.RemoveDevices(expectedDevices[0].ID)
require.NoError(err)
devices, err = repo.GetDevices()
require.NoError(err)
require.Len(devices, 1)
device, err = repo.GetDevice(expectedDevices[1].ID)
device, err = repo.GetDevice(expectedDevices[0].ID)
require.NoError(err)
require.Nil(device)
err = repo.AddDevices(expectedDevices[0])
require.NoError(err)
// Test: Update Devices
location = "MyLocation"
expectedDevice := &types.Device{
@ -126,7 +132,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
device, err = repo.GetDevice(expectedDevice.ID)
require.NoError(err)
require.EqualValues(expectedDevice, device)
// require.JSONEq(jsonEncoder(expectedDevice), jsonEncoder(device))
var (
wireID = "50473fdc-f6ef-4227-b3c4-484d8e9c1323"
@ -178,18 +184,17 @@ func testBackend(t *testing.T, repo *repository.Repository) {
sensors, err := repo.GetSensors()
require.NoError(err)
require.Len(sensors, len(expectedSensors))
require.Equal(expectedSensors, sensors)
// Test: GetSensor
sensor, err := repo.GetSensor(expectedSensors[0].ID)
require.NoError(err)
require.Equal(expectedSensors[0], sensor)
require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensor))
// Test: GetSensorsByDeviceID
sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
require.NoError(err)
require.Len(sensors, 2)
require.Equal(expectedSensors[0:2], sensors)
require.JSONEq(jsonEncoder(expectedSensors[0:2]), jsonEncoder(sensors))
// Test: RemoveSensors
err = repo.RemoveSensors(expectedSensors[0].ID)
@ -263,7 +268,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
sensor, err = repo.GetSensor(expectedSensor.ID)
require.NoError(err)
require.NotNil(sensor)
require.EqualValues(expectedSensor, sensor)
// require.JSONEq(jsonEncoder(expectedSensor), jsonEncoder(sensor))
var (
expectedMeasuredValues = []*types.MeasuredValue{
@ -273,7 +278,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
ValueType: "temperature",
Date: *timeNow(require),
SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a",
CreationDate: timeNow(require),
CreationDate: *timeNow(require),
UpdateDate: nil,
},
{
@ -282,7 +287,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
ValueType: "temperature",
Date: *timeNow(require),
SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a",
CreationDate: timeNow(require),
CreationDate: *timeNow(require),
UpdateDate: nil,
},
{
@ -291,7 +296,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
ValueType: "temperature",
Date: *timeNow(require),
SensorID: "8c74397f-8e60-4c9d-960d-3197747cef9a",
CreationDate: timeNow(require),
CreationDate: *timeNow(require),
UpdateDate: nil,
},
}
@ -302,8 +307,17 @@ func testBackend(t *testing.T, repo *repository.Repository) {
require.NoError(err)
}
func jsonEncoder(v interface{}) string {
body := make([]byte, 0)
buffer := bytes.NewBuffer(body)
jsonEncoder := json.NewEncoder(buffer)
jsonEncoder.SetIndent("", " ")
jsonEncoder.Encode(v)
return buffer.String()
}
func timeNow(require *require.Assertions) *time.Time {
now, err := time.Parse("2006-01-02 15:04:05", time.Now().Format("2006-01-02 15:04:05"))
now, err := time.Parse("2006-01-02 15:04:05.999999Z", time.Now().Format("2006-01-02 15:04:05.999999Z"))
require.NoError(err)
return &now
}

View File

@ -4,8 +4,9 @@ import "time"
// Device represent a device with all his settings.
type Device struct {
ID string `json:"id" xml:"id"`
Name string `json:"name" xml:"name"`
Location *string `json:"location" xml:"location"`
CreationDate time.Time `json:"creation_date" xml:"creation_date"`
ID string `json:"id" xml:"id"`
Name string `json:"name" xml:"name"`
Location *string `json:"location" xml:"location"`
CreationDate time.Time `json:"creation_date" xml:"creation_date"`
UpdateDate *time.Time `json:"update_date" xml:"update_date"`
}

View File

@ -13,6 +13,6 @@ type MeasuredValue struct {
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"`
CreationDate time.Time `json:"creation_date" xml:"creation_date"`
UpdateDate *time.Time `json:"update_date" xml:"update_date"`
}

View File

@ -7,18 +7,19 @@ import (
// Sensor represents a sensor with all his settings. The struct does not
// contains any read method.
type Sensor struct {
ID string `json:"id" xml:"id"`
Name string `json:"name" xml:"name"`
Location string `json:"location" xml:"location"`
WireID *string `json:"wire_id" xml:"wire_id"`
I2CBus *int `json:"i2c_bus" xml:"i2c_bus"`
I2CAddress *uint8 `json:"i2c_address" xml:"i2c_address"`
GPIONumber string `json:"gpio_number" xml:"gpio_number"`
Model string `json:"model" xml:"model"`
Enabled bool `json:"enabled" xml:"enabled"`
TickDuration string `json:"tick_duration" xml:"tick_duration"`
DeviceID string `json:"device_id" xml:"device_id"`
CreationDate time.Time `json:"creation_date" xml:"creation_date"`
ID string `json:"id" xml:"id"`
Name string `json:"name" xml:"name"`
Location string `json:"location" xml:"location"`
WireID *string `json:"wire_id" xml:"wire_id"`
I2CBus *int `json:"i2c_bus" xml:"i2c_bus"`
I2CAddress *uint8 `json:"i2c_address" xml:"i2c_address"`
GPIONumber string `json:"gpio_number" xml:"gpio_number"`
Model string `json:"model" xml:"model"`
Enabled bool `json:"enabled" xml:"enabled"`
TickDuration string `json:"tick_duration" xml:"tick_duration"`
DeviceID string `json:"device_id" xml:"device_id"`
CreationDate time.Time `json:"creation_date" xml:"creation_date"`
UpdateDate *time.Time `json:"update_date" xml:"update_date"`
}
// GetID returns the UUID of the sensor.