Files
go-telldus-matter/pkg/datastore/datastore.go
Mats Tornberg 87660c7d1d update
2025-11-22 17:35:24 +01:00

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
}
}
}
}