314 lines
7.9 KiB
Go
314 lines
7.9 KiB
Go
package datastore
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"iter"
|
|
"time"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
// DataStore handles all database operations
|
|
type DataStore struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// New creates a new DataStore instance
|
|
func New(dbPath string) (*DataStore, error) {
|
|
db, err := sql.Open("sqlite3", dbPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ds := &DataStore{db: db}
|
|
if err := ds.initTables(); err != nil {
|
|
db.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return ds, nil
|
|
}
|
|
|
|
// Close closes the database connection
|
|
func (ds *DataStore) Close() error {
|
|
return ds.db.Close()
|
|
}
|
|
|
|
// initTables creates the necessary database tables
|
|
func (ds *DataStore) initTables() error {
|
|
tables := []string{
|
|
`CREATE TABLE IF NOT EXISTS devices (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT,
|
|
unique_id TEXT UNIQUE
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS sensors (
|
|
sensor_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
protocol TEXT,
|
|
model TEXT,
|
|
id INTEGER,
|
|
name TEXT,
|
|
temperature_unique_id TEXT,
|
|
humidity_unique_id TEXT,
|
|
last_temperature TEXT,
|
|
last_humidity TEXT,
|
|
last_timestamp INTEGER,
|
|
hidden INTEGER DEFAULT 0,
|
|
UNIQUE(protocol, model, id)
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS potential_devices (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
class TEXT,
|
|
protocol TEXT,
|
|
model TEXT,
|
|
device_id TEXT,
|
|
last_data TEXT,
|
|
last_seen INTEGER
|
|
)`,
|
|
}
|
|
|
|
for _, table := range tables {
|
|
if _, err := ds.db.Exec(table); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpsertDevice inserts or updates a device
|
|
func (ds *DataStore) UpsertDevice(device *Device) error {
|
|
_, err := ds.db.Exec(
|
|
"INSERT OR REPLACE INTO devices (id, name, unique_id) VALUES (?, ?, ?)",
|
|
device.ID, device.Name, device.UniqueID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// GetDevice retrieves a device by ID
|
|
func (ds *DataStore) GetDevice(id int) (*Device, error) {
|
|
device := &Device{}
|
|
err := ds.db.QueryRow(
|
|
"SELECT id, name, unique_id FROM devices WHERE id = ?",
|
|
id,
|
|
).Scan(&device.ID, &device.Name, &device.UniqueID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return device, nil
|
|
}
|
|
|
|
// ListDevices returns an iterator over all devices
|
|
func (ds *DataStore) ListDevices() iter.Seq[*Device] {
|
|
return func(yield func(*Device) bool) {
|
|
rows, err := ds.db.Query("SELECT id, name, unique_id FROM devices ORDER BY id")
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
device := &Device{}
|
|
if err := rows.Scan(&device.ID, &device.Name, &device.UniqueID); err != nil {
|
|
continue
|
|
}
|
|
if !yield(device) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// UpdateDeviceName updates a device's name
|
|
func (ds *DataStore) UpdateDeviceName(id int, name string) error {
|
|
_, err := ds.db.Exec("UPDATE devices SET name = ? WHERE id = ?", name, id)
|
|
return err
|
|
}
|
|
|
|
// UpsertSensor inserts or updates a sensor
|
|
func (ds *DataStore) UpsertSensor(sensor *Sensor) error {
|
|
_, err := ds.db.Exec(
|
|
`INSERT OR IGNORE INTO sensors
|
|
(protocol, model, id, name, temperature_unique_id, humidity_unique_id)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
sensor.Protocol, sensor.Model, sensor.ID, sensor.Name,
|
|
sensor.TemperatureUniqueID, sensor.HumidityUniqueID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// GetSensor retrieves a sensor by sensor_id
|
|
func (ds *DataStore) GetSensor(sensorID int) (*Sensor, error) {
|
|
sensor := &Sensor{}
|
|
var lastTemp, lastHum sql.NullString
|
|
var lastTs sql.NullInt64
|
|
var hidden int
|
|
|
|
err := ds.db.QueryRow(
|
|
`SELECT sensor_id, protocol, model, id, name,
|
|
temperature_unique_id, humidity_unique_id,
|
|
last_temperature, last_humidity, last_timestamp, hidden
|
|
FROM sensors WHERE sensor_id = ?`,
|
|
sensorID,
|
|
).Scan(
|
|
&sensor.SensorID, &sensor.Protocol, &sensor.Model, &sensor.ID, &sensor.Name,
|
|
&sensor.TemperatureUniqueID, &sensor.HumidityUniqueID,
|
|
&lastTemp, &lastHum, &lastTs, &hidden,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sensor.LastTemperature = lastTemp.String
|
|
sensor.LastHumidity = lastHum.String
|
|
sensor.LastTimestamp = lastTs.Int64
|
|
sensor.Hidden = hidden != 0
|
|
|
|
return sensor, nil
|
|
}
|
|
|
|
// GetSensorByIdentity retrieves a sensor by protocol, model, and id
|
|
func (ds *DataStore) GetSensorByIdentity(protocol, model string, id int) (*Sensor, error) {
|
|
sensor := &Sensor{}
|
|
var lastTemp, lastHum sql.NullString
|
|
var lastTs sql.NullInt64
|
|
var hidden int
|
|
|
|
err := ds.db.QueryRow(
|
|
`SELECT sensor_id, protocol, model, id, name,
|
|
temperature_unique_id, humidity_unique_id,
|
|
last_temperature, last_humidity, last_timestamp, hidden
|
|
FROM sensors WHERE protocol = ? AND model = ? AND id = ?`,
|
|
protocol, model, id,
|
|
).Scan(
|
|
&sensor.SensorID, &sensor.Protocol, &sensor.Model, &sensor.ID, &sensor.Name,
|
|
&sensor.TemperatureUniqueID, &sensor.HumidityUniqueID,
|
|
&lastTemp, &lastHum, &lastTs, &hidden,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sensor.LastTemperature = lastTemp.String
|
|
sensor.LastHumidity = lastHum.String
|
|
sensor.LastTimestamp = lastTs.Int64
|
|
sensor.Hidden = hidden != 0
|
|
|
|
return sensor, nil
|
|
}
|
|
|
|
// ListSensors returns an iterator over all sensors
|
|
func (ds *DataStore) ListSensors() iter.Seq[*Sensor] {
|
|
return func(yield func(*Sensor) bool) {
|
|
rows, err := ds.db.Query(
|
|
`SELECT sensor_id, protocol, model, id, name,
|
|
temperature_unique_id, humidity_unique_id,
|
|
last_temperature, last_humidity, last_timestamp, hidden
|
|
FROM sensors ORDER BY sensor_id`,
|
|
)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
sensor := &Sensor{}
|
|
var lastTemp, lastHum sql.NullString
|
|
var lastTs sql.NullInt64
|
|
var hidden int
|
|
|
|
if err := rows.Scan(
|
|
&sensor.SensorID, &sensor.Protocol, &sensor.Model, &sensor.ID, &sensor.Name,
|
|
&sensor.TemperatureUniqueID, &sensor.HumidityUniqueID,
|
|
&lastTemp, &lastHum, &lastTs, &hidden,
|
|
); err != nil {
|
|
continue
|
|
}
|
|
|
|
sensor.LastTemperature = lastTemp.String
|
|
sensor.LastHumidity = lastHum.String
|
|
sensor.LastTimestamp = lastTs.Int64
|
|
sensor.Hidden = hidden != 0
|
|
|
|
if !yield(sensor) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// UpdateSensorName updates a sensor's name
|
|
func (ds *DataStore) UpdateSensorName(sensorID int, name string) error {
|
|
_, err := ds.db.Exec("UPDATE sensors SET name = ? WHERE sensor_id = ?", name, sensorID)
|
|
return err
|
|
}
|
|
|
|
// UpdateSensorValue updates a sensor's last value
|
|
func (ds *DataStore) UpdateSensorValue(protocol, model string, id int, dataType int, value string) error {
|
|
column := ""
|
|
switch dataType {
|
|
case 1: // Temperature
|
|
column = "last_temperature"
|
|
case 2: // Humidity
|
|
column = "last_humidity"
|
|
default:
|
|
return fmt.Errorf("unsupported data type: %d", dataType)
|
|
}
|
|
|
|
query := fmt.Sprintf(
|
|
"UPDATE sensors SET %s = ?, last_timestamp = ? WHERE protocol = ? AND model = ? AND id = ?",
|
|
column,
|
|
)
|
|
_, err := ds.db.Exec(query, value, time.Now().Unix(), protocol, model, id)
|
|
return err
|
|
}
|
|
|
|
// SetSensorHidden updates a sensor's hidden status
|
|
func (ds *DataStore) SetSensorHidden(sensorID int, hidden bool) error {
|
|
hiddenVal := 0
|
|
if hidden {
|
|
hiddenVal = 1
|
|
}
|
|
_, err := ds.db.Exec("UPDATE sensors SET hidden = ? WHERE sensor_id = ?", hiddenVal, sensorID)
|
|
return err
|
|
}
|
|
|
|
// UpsertPotentialDevice inserts or updates a potential device
|
|
func (ds *DataStore) UpsertPotentialDevice(device *PotentialDevice) error {
|
|
_, err := ds.db.Exec(
|
|
`INSERT OR REPLACE INTO potential_devices
|
|
(class, protocol, model, device_id, last_data, last_seen)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
device.Class, device.Protocol, device.Model, device.DeviceID,
|
|
device.LastData, device.LastSeen,
|
|
)
|
|
return err
|
|
}
|
|
|
|
// ListPotentialDevices returns an iterator over all potential devices
|
|
func (ds *DataStore) ListPotentialDevices() iter.Seq[*PotentialDevice] {
|
|
return func(yield func(*PotentialDevice) bool) {
|
|
rows, err := ds.db.Query(
|
|
`SELECT id, class, protocol, model, device_id, last_data, last_seen
|
|
FROM potential_devices ORDER BY last_seen DESC`,
|
|
)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
device := &PotentialDevice{}
|
|
if err := rows.Scan(
|
|
&device.ID, &device.Class, &device.Protocol, &device.Model,
|
|
&device.DeviceID, &device.LastData, &device.LastSeen,
|
|
); err != nil {
|
|
continue
|
|
}
|
|
if !yield(device) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|