update more stuff

This commit is contained in:
Mats Tornberg
2025-11-22 16:22:42 +00:00
parent 790b174a9e
commit 0596fe60fa
10 changed files with 1230 additions and 717 deletions

817
main.go
View File

@@ -1,522 +1,137 @@
package main
import (
"app/datastore"
"app/devices"
"app/mqtt"
"app/telldus"
"bufio"
"database/sql"
daemon "app/telldus-daemon"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
_ "github.com/mattn/go-sqlite3"
"github.com/fsnotify/fsnotify"
)
const (
httpPort = ":8080"
)
var mqttDev *mqttDevice
var db *sql.DB
var telldusCmd *exec.Cmd
var telldusMu sync.Mutex
type mqttDevice struct {
client mqtt.Client
}
func newMqttDevice() *mqttDevice {
opts := mqtt.NewClientOptions().AddBroker(os.Getenv("MQTT_URL"))
opts.SetUsername(os.Getenv("MQTT_USER"))
opts.SetPassword(os.Getenv("MQTT_PASSWORD"))
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
log.Fatal(token.Error())
}
return &mqttDevice{client: client}
}
func (m *mqttDevice) publishDiscovery() {
// Publish discovery for devices
numDevices := telldus.GetNumberOfDevices()
for i := 0; i < numDevices; i++ {
deviceId := telldus.GetDeviceId(i)
var name, uniqueId string
err := db.QueryRow("SELECT name, unique_id FROM devices WHERE id = ?", deviceId).Scan(&name, &uniqueId)
if err != nil {
log.Printf("Device %d not in DB, skipping", deviceId)
continue
}
topic := fmt.Sprintf("homeassistant/switch/%s/config", uniqueId)
payload := fmt.Sprintf(`{
"name": "%s",
"command_topic": "telldus/device/%d/set",
"state_topic": "telldus/device/%d/state",
"unique_id": "%s",
"device": {
"identifiers": ["telldus_%d"],
"name": "%s",
"manufacturer": "Telldus"
}
}`, name, deviceId, deviceId, uniqueId, deviceId, name)
m.client.Publish(topic, 0, true, payload)
}
// Publish discovery for sensors
var protocol, model string
var id, dataTypes int
ret := telldus.Sensor(&protocol, &model, &id, &dataTypes)
for ret == 0 {
var sensorName, tempUniqueId, humUniqueId string
err := db.QueryRow("SELECT name, temperature_unique_id, humidity_unique_id FROM sensors WHERE protocol = ? AND model = ? AND id = ? AND hidden = 0", protocol, model, id).Scan(&sensorName, &tempUniqueId, &humUniqueId)
if err != nil {
log.Printf("Sensor %s %s %d not in DB, skipping", protocol, model, id)
ret = telldus.Sensor(&protocol, &model, &id, &dataTypes)
continue
}
if dataTypes&telldus.DataTypeTemperature != 0 && tempUniqueId != "" {
topic := fmt.Sprintf("homeassistant/sensor/%s/config", tempUniqueId)
payload := fmt.Sprintf(`{
"name": "%s Temperature",
"state_topic": "telldus/sensor/%s/%s/%d/temperature",
"unit_of_measurement": "°C",
"device_class": "temperature",
"unique_id": "%s",
"device": {
"identifiers": ["telldus_sensor_%s_%s_%d"],
"name": "%s",
"manufacturer": "Telldus"
}
}`, sensorName, protocol, model, id, tempUniqueId, protocol, model, id, sensorName)
m.client.Publish(topic, 0, true, payload)
}
if dataTypes&telldus.DataTypeHumidity != 0 && humUniqueId != "" {
topic := fmt.Sprintf("homeassistant/sensor/%s/config", humUniqueId)
payload := fmt.Sprintf(`{
"name": "%s Humidity",
"state_topic": "telldus/sensor/%s/%s/%d/humidity",
"unit_of_measurement": "%%",
"device_class": "humidity",
"unique_id": "%s",
"device": {
"identifiers": ["telldus_sensor_%s_%s_%d"],
"name": "%s",
"manufacturer": "Telldus"
}
}`, sensorName, protocol, model, id, humUniqueId, protocol, model, id, sensorName)
m.client.Publish(topic, 0, true, payload)
}
// Add other sensor types if needed
ret = telldus.Sensor(&protocol, &model, &id, &dataTypes)
}
}
func (m *mqttDevice) subscribeCommands() {
numDevices := telldus.GetNumberOfDevices()
for i := 0; i < numDevices; i++ {
deviceId := telldus.GetDeviceId(i)
topic := fmt.Sprintf("telldus/device/%d/set", deviceId)
m.client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) {
payload := string(msg.Payload())
if payload == "ON" {
telldus.TurnOn(deviceId)
} else if payload == "OFF" {
telldus.TurnOff(deviceId)
}
})
}
}
func (m *mqttDevice) republishDevice(deviceId int) {
var name, uniqueId string
err := db.QueryRow("SELECT name, unique_id FROM devices WHERE id = ?", deviceId).Scan(&name, &uniqueId)
if err != nil {
log.Printf("Error querying device %d: %v", deviceId, err)
return
}
topic := fmt.Sprintf("homeassistant/switch/%s/config", uniqueId)
payload := fmt.Sprintf(`{
"name": "%s",
"command_topic": "telldus/device/%d/set",
"state_topic": "telldus/device/%d/state",
"unique_id": "%s",
"device": {
"identifiers": ["telldus_%d"],
"name": "%s",
"manufacturer": "Telldus"
}
}`, name, deviceId, deviceId, uniqueId, deviceId, name)
m.client.Publish(topic, 0, true, payload)
}
func (m *mqttDevice) republishSensor(protocol, model string, id int) {
var sensorName, tempUniqueId, humUniqueId string
err := db.QueryRow("SELECT name, temperature_unique_id, humidity_unique_id FROM sensors WHERE protocol = ? AND model = ? AND id = ?", protocol, model, id).Scan(&sensorName, &tempUniqueId, &humUniqueId)
if err != nil {
log.Printf("Error querying sensor %s %s %d: %v", protocol, model, id, err)
return
}
if tempUniqueId != "" {
topic := fmt.Sprintf("homeassistant/sensor/%s/config", tempUniqueId)
payload := fmt.Sprintf(`{
"name": "%s Temperature",
"state_topic": "telldus/sensor/%s/%s/%d/temperature",
"unit_of_measurement": "°C",
"device_class": "temperature",
"unique_id": "%s",
"device": {
"identifiers": ["telldus_sensor_%s_%s_%d"],
"name": "%s",
"manufacturer": "Telldus"
}
}`, sensorName, protocol, model, id, tempUniqueId, protocol, model, id, sensorName)
m.client.Publish(topic, 0, true, payload)
}
if humUniqueId != "" {
topic := fmt.Sprintf("homeassistant/sensor/%s/config", humUniqueId)
payload := fmt.Sprintf(`{
"name": "%s Humidity",
"state_topic": "telldus/sensor/%s/%s/%d/humidity",
"unit_of_measurement": "%%",
"device_class": "humidity",
"unique_id": "%s",
"device": {
"identifiers": ["telldus_sensor_%s_%s_%d"],
"name": "%s",
"manufacturer": "Telldus"
}
}`, sensorName, protocol, model, id, humUniqueId, protocol, model, id, sensorName)
m.client.Publish(topic, 0, true, payload)
}
}
type RawEvent struct {
Timestamp time.Time `json:"timestamp"`
ControllerId int `json:"controller_id"`
Data string `json:"data"`
}
type SensorEvent struct {
Timestamp time.Time `json:"timestamp"`
Protocol string `json:"protocol"`
Model string `json:"model"`
Id int `json:"id"`
DataType int `json:"data_type"`
Value string `json:"value"`
}
var rawEvents []RawEvent
var sensorEvents []SensorEvent
var mu sync.Mutex
var mqttClient *mqtt.Client
var store *datastore.DataStore
var daemonMgr *daemon.Manager
var eventMgr *devices.EventManager
const maxEvents = 1000
// startTelldusd starts the telldusd daemon and captures its output
func startTelldusd() error {
telldusMu.Lock()
defer telldusMu.Unlock()
if telldusCmd != nil && telldusCmd.Process != nil {
log.Println("Telldusd already running")
return nil
}
log.Println("Starting telldusd...")
cmd := exec.Command("/usr/local/sbin/telldusd", "--nodaemon")
// Capture stdout
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to get stdout pipe: %v", err)
}
// Capture stderr
stderr, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("failed to get stderr pipe: %v", err)
}
// Start the command
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to start telldusd: %v", err)
}
telldusCmd = cmd
// Log stdout in a goroutine
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
log.Printf("[telldusd] %s", scanner.Text())
}
}()
// Log stderr in a goroutine
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
log.Printf("[telldusd] ERROR: %s", scanner.Text())
}
}()
// Monitor process in a goroutine
go func() {
err := cmd.Wait()
telldusMu.Lock()
telldusCmd = nil
telldusMu.Unlock()
if err != nil {
log.Printf("Telldusd exited with error: %v", err)
} else {
log.Println("Telldusd exited normally")
}
}()
// Give telldusd a moment to start
time.Sleep(500 * time.Millisecond)
log.Println("Telldusd started successfully")
return nil
}
// stopTelldusd stops the telldusd daemon
func stopTelldusd() error {
telldusMu.Lock()
defer telldusMu.Unlock()
if telldusCmd == nil || telldusCmd.Process == nil {
log.Println("Telldusd not running")
return nil
}
log.Println("Stopping telldusd...")
// Send SIGTERM
if err := telldusCmd.Process.Signal(syscall.SIGTERM); err != nil {
log.Printf("Failed to send SIGTERM to telldusd: %v", err)
// Try SIGKILL as fallback
if err := telldusCmd.Process.Kill(); err != nil {
return fmt.Errorf("failed to kill telldusd: %v", err)
}
}
// Wait for process to exit (with timeout)
done := make(chan error, 1)
go func() {
done <- telldusCmd.Wait()
}()
select {
case <-done:
log.Println("Telldusd stopped successfully")
case <-time.After(5 * time.Second):
log.Println("Telldusd did not stop gracefully, killing...")
telldusCmd.Process.Kill()
}
telldusCmd = nil
return nil
}
// restartTelldusd restarts the telldusd daemon
func restartTelldusd() error {
log.Println("Restarting telldusd due to configuration change...")
if err := stopTelldusd(); err != nil {
log.Printf("Error stopping telldusd: %v", err)
}
// Give it a moment to fully stop
time.Sleep(1 * time.Second)
// Close and reinitialize telldus library
telldus.Close()
time.Sleep(500 * time.Millisecond)
if err := startTelldusd(); err != nil {
return err
}
// Reinitialize telldus library
telldus.Init()
log.Println("Telldusd restarted successfully")
return nil
}
// watchConfigFile watches for changes to the tellstick.conf file
func watchConfigFile(configPath string) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Printf("Failed to create file watcher: %v", err)
return
}
defer watcher.Close()
// Watch the parent directory since file operations might replace the file
configDir := filepath.Dir(configPath)
if err := watcher.Add(configDir); err != nil {
log.Printf("Failed to watch config directory: %v", err)
return
}
log.Printf("Watching for changes to %s", configPath)
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
// Check if the event is for our config file
if event.Name == configPath && (event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create) {
log.Printf("Configuration file changed: %s", event.Op.String())
if err := restartTelldusd(); err != nil {
log.Printf("Failed to restart telldusd: %v", err)
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Printf("File watcher error: %v", err)
}
}
}
func main() {
// Start telldusd daemon
if err := startTelldusd(); err != nil {
// Initialize daemon manager
daemonMgr = daemon.New()
if err := daemonMgr.Start(); err != nil {
log.Fatalf("Failed to start telldusd: %v", err)
}
defer stopTelldusd()
// Start watching config file
configPath := "/etc/tellstick.conf"
go watchConfigFile(configPath)
defer daemonMgr.Stop()
// Initialize Telldus
telldus.Init()
defer telldus.Close()
// Initialize SQLite DB
// Initialize DataStore
var err error
db, err = sql.Open("sqlite3", "./db/telldus.db")
store, err = datastore.New("./db/telldus.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
defer store.Close()
// Create tables
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS devices (
id INTEGER PRIMARY KEY,
name TEXT,
unique_id TEXT UNIQUE
)`)
if err != nil {
log.Fatal(err)
// Sync devices and sensors
syncer := devices.NewSyncer(store)
if err := syncer.SyncDevices(); err != nil {
log.Printf("Error syncing devices: %v", err)
}
_, err = db.Exec(`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)
)`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`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
)`)
if err != nil {
log.Fatal(err)
if err := syncer.SyncSensors(); err != nil {
log.Printf("Error syncing sensors: %v", err)
}
// Sync devices to DB
numDevices := telldus.GetNumberOfDevices()
for i := 0; i < numDevices; i++ {
deviceId := telldus.GetDeviceId(i)
name := telldus.GetName(deviceId)
uniqueId := fmt.Sprintf("telldus_device_%d", deviceId)
_, err = db.Exec("INSERT OR IGNORE INTO devices (id, name, unique_id) VALUES (?, ?, ?)", deviceId, name, uniqueId)
if err != nil {
log.Printf("Error inserting device %d: %v", deviceId, err)
// Device reload function for config file changes
reloadDevices := func() error {
log.Println("Configuration file changed, restarting telldusd...")
if err := daemonMgr.Restart(); err != nil {
log.Printf("Failed to restart telldusd: %v", err)
return err
}
log.Println("Reloading devices and sensors after config change...")
if err := syncer.SyncDevices(); err != nil {
log.Printf("Error syncing devices: %v", err)
return err
}
if err := syncer.SyncSensors(); err != nil {
log.Printf("Error syncing sensors: %v", err)
return err
}
if err := mqttClient.PublishAllDiscovery(); err != nil {
log.Printf("Error republishing discovery: %v", err)
return err
}
if err := mqttClient.SubscribeToDeviceCommands(); err != nil {
log.Printf("Error resubscribing to commands: %v", err)
return err
}
log.Println("Successfully reloaded devices and sensors")
return nil
}
// Sync sensors to DB
var protocol, model string
var id, dataTypes int
ret := telldus.Sensor(&protocol, &model, &id, &dataTypes)
for ret == 0 {
sensorName := fmt.Sprintf("%s %s %d", protocol, model, id)
tempUniqueId := ""
humUniqueId := ""
if dataTypes&telldus.DataTypeTemperature != 0 {
tempUniqueId = fmt.Sprintf("telldus_sensor_%s_%s_%d_temperature", protocol, model, id)
// Start watching config file
configPath := "/etc/tellstick.conf"
watcher := daemon.NewWatcher(configPath, reloadDevices)
go func() {
if err := watcher.Watch(); err != nil {
log.Printf("Config watcher error: %v", err)
}
if dataTypes&telldus.DataTypeHumidity != 0 {
humUniqueId = fmt.Sprintf("telldus_sensor_%s_%s_%d_humidity", protocol, model, id)
}
_, err = db.Exec("INSERT OR IGNORE INTO sensors (protocol, model, id, name, temperature_unique_id, humidity_unique_id) VALUES (?, ?, ?, ?, ?, ?)", protocol, model, id, sensorName, tempUniqueId, humUniqueId)
if err != nil {
log.Printf("Error inserting sensor %s %s %d: %v", protocol, model, id, err)
}
ret = telldus.Sensor(&protocol, &model, &id, &dataTypes)
}
}()
// Initialize MQTT
mqttDev = newMqttDevice()
defer mqttDev.client.Disconnect(250)
mqttConfig := mqtt.Config{
BrokerURL: os.Getenv("MQTT_URL"),
Username: os.Getenv("MQTT_USER"),
Password: os.Getenv("MQTT_PASSWORD"),
}
mqttClient, err = mqtt.New(mqttConfig, store)
if err != nil {
log.Fatalf("Failed to connect to MQTT: %v", err)
}
defer mqttClient.Close()
// Publish Home Assistant discovery
mqttDev.publishDiscovery()
if err := mqttClient.PublishAllDiscovery(); err != nil {
log.Printf("Error publishing discovery: %v", err)
}
// Subscribe to command topics
mqttDev.subscribeCommands()
if err := mqttClient.SubscribeToDeviceCommands(); err != nil {
log.Printf("Error subscribing to commands: %v", err)
}
// List devices and sensors
listDevices()
listSensors()
syncer.ListDevices()
syncer.ListSensors()
// Register callbacks
telldus.RegisterDeviceEvent(deviceEventHandler)
telldus.RegisterSensorEvent(sensorEventHandler)
telldus.RegisterRawDeviceEvent(rawDeviceEventHandler)
// Initialize event manager and register callbacks
eventMgr = devices.NewEventManager(store, mqttClient, maxEvents)
eventMgr.RegisterCallbacks()
// Setup graceful shutdown
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
signal.Notify(c, os.Interrupt)
go func() {
<-c
log.Println("Shutting down gracefully...")
mqttDev.client.Disconnect(250)
mqttClient.Close()
telldus.Close()
stopTelldusd()
daemonMgr.Stop()
os.Exit(0)
}()
@@ -525,145 +140,22 @@ func main() {
log.Fatal(http.ListenAndServe(httpPort, setupRoutes()))
}
func deviceEventHandler(deviceId, method int, data string, callbackId int) {
fmt.Printf("Device event: ID=%d, Method=%d, Data=%s\n", deviceId, method, data)
// Publish state to MQTT
var state string
switch method {
case telldus.MethodTurnOn:
state = "ON"
case telldus.MethodTurnOff:
state = "OFF"
}
if state != "" {
topic := fmt.Sprintf("telldus/device/%d/state", deviceId)
mqttDev.client.Publish(topic, 0, false, state)
}
// Log or handle locally, no MQTT
}
func sensorEventHandler(protocol, model string, id, dataType int, value string, timestamp, callbackId int) {
fmt.Printf("Sensor event: Protocol=%s, Model=%s, ID=%d, Type=%d, Value=%s, Timestamp=%d\n",
protocol, model, id, dataType, value, timestamp)
// Publish to MQTT
var topic string
switch dataType {
case telldus.DataTypeTemperature:
topic = fmt.Sprintf("telldus/sensor/%s/%s/%d/temperature", protocol, model, id)
case telldus.DataTypeHumidity:
topic = fmt.Sprintf("telldus/sensor/%s/%s/%d/humidity", protocol, model, id)
}
if topic != "" {
mqttDev.client.Publish(topic, 0, false, value)
}
// Store in history
mu.Lock()
sensorEvents = append(sensorEvents, SensorEvent{time.Now(), protocol, model, id, dataType, value})
if len(sensorEvents) > maxEvents {
sensorEvents = sensorEvents[1:]
}
mu.Unlock()
// Update last value in DB
var column string
switch dataType {
case telldus.DataTypeTemperature:
column = "last_temperature"
case telldus.DataTypeHumidity:
column = "last_humidity"
}
if column != "" {
_, err := db.Exec(fmt.Sprintf("UPDATE sensors SET %s = ?, last_timestamp = ? WHERE protocol = ? AND model = ? AND id = ?", column), value, time.Now().Unix(), protocol, model, id)
if err != nil {
log.Printf("Error updating sensor %s %s %d: %v", protocol, model, id, err)
}
}
// Log or handle locally, no MQTT
}
func rawDeviceEventHandler(data string, controllerId, callbackId int) {
fmt.Printf("Raw device event: ControllerID=%d, Data=%s\n", controllerId, data)
// Parse data
fields := strings.Split(data, ";")
var class, protocol, model, deviceId string
for _, field := range fields {
kv := strings.SplitN(field, ":", 2)
if len(kv) == 2 {
key, val := kv[0], kv[1]
switch key {
case "class":
class = val
case "protocol":
protocol = val
case "model":
model = val
case "id":
deviceId = val
}
}
}
// Store in potential_devices
_, err := db.Exec("INSERT OR REPLACE INTO potential_devices (class, protocol, model, device_id, last_data, last_seen) VALUES (?, ?, ?, ?, ?, ?)", class, protocol, model, deviceId, data, time.Now().Unix())
if err != nil {
log.Printf("Error storing potential device: %v", err)
}
// If sensor, ensure in sensors table
if class == "sensor" {
idInt, _ := strconv.Atoi(deviceId)
sensorName := fmt.Sprintf("%s %s %s", protocol, model, deviceId)
tempUniqueId := fmt.Sprintf("telldus_sensor_%s_%s_%s_temperature", protocol, model, deviceId)
humUniqueId := fmt.Sprintf("telldus_sensor_%s_%s_%s_humidity", protocol, model, deviceId)
_, err = db.Exec("INSERT OR IGNORE INTO sensors (protocol, model, id, name, temperature_unique_id, humidity_unique_id) VALUES (?, ?, ?, ?, ?, ?)", protocol, model, idInt, sensorName, tempUniqueId, humUniqueId)
if err != nil {
log.Printf("Error inserting sensor from raw: %v", err)
}
}
// Log the raw event data
mu.Lock()
rawEvents = append(rawEvents, RawEvent{time.Now(), controllerId, data})
if len(rawEvents) > maxEvents {
rawEvents = rawEvents[1:]
}
mu.Unlock()
}
func getRawEvents(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(rawEvents)
json.NewEncoder(w).Encode(eventMgr.GetRawEvents())
}
func getSensorEvents(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(sensorEvents)
json.NewEncoder(w).Encode(eventMgr.GetSensorEvents())
}
func getPotentialDevices(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query("SELECT class, protocol, model, device_id, last_data, last_seen FROM potential_devices ORDER BY last_seen DESC")
if err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
defer rows.Close()
devices := []map[string]interface{}{}
for rows.Next() {
var class, protocol, model, deviceId, lastData string
var lastSeen int
err := rows.Scan(&class, &protocol, &model, &deviceId, &lastData, &lastSeen)
if err != nil {
http.Error(w, "Scan error", http.StatusInternalServerError)
return
}
devices = append(devices, map[string]interface{}{
"class": class,
"protocol": protocol,
"model": model,
"device_id": deviceId,
"last_data": lastData,
"last_seen": lastSeen,
})
devices := []*datastore.PotentialDevice{}
for device := range store.ListPotentialDevices() {
devices = append(devices, device)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(devices)
@@ -683,13 +175,14 @@ func renameDevice(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
_, err = db.Exec("UPDATE devices SET name = ? WHERE id = ?", req.Name, id)
if err != nil {
if err := store.UpdateDeviceName(id, req.Name); err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
// Republish discovery for this device
mqttDev.republishDevice(id)
if err := mqttClient.PublishDeviceDiscovery(id); err != nil {
log.Printf("Error republishing device discovery: %v", err)
}
w.WriteHeader(http.StatusOK)
}
@@ -707,20 +200,19 @@ func renameSensor(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
var protocol, model string
var id int
err = db.QueryRow("SELECT protocol, model, id FROM sensors WHERE sensor_id = ?", sensorId).Scan(&protocol, &model, &id)
sensor, err := store.GetSensor(sensorId)
if err != nil {
http.Error(w, "Sensor not found", http.StatusNotFound)
return
}
_, err = db.Exec("UPDATE sensors SET name = ? WHERE sensor_id = ?", req.Name, sensorId)
if err != nil {
if err := store.UpdateSensorName(sensorId, req.Name); err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
// Republish discovery for this sensor
mqttDev.republishSensor(protocol, model, id)
if err := mqttClient.PublishSensorDiscovery(sensor.Protocol, sensor.Model, sensor.ID); err != nil {
log.Printf("Error republishing sensor discovery: %v", err)
}
w.WriteHeader(http.StatusOK)
}
@@ -731,8 +223,7 @@ func hideSensor(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Invalid sensor ID", http.StatusBadRequest)
return
}
_, err = db.Exec("UPDATE sensors SET hidden = 1 WHERE sensor_id = ?", sensorId)
if err != nil {
if err := store.SetSensorHidden(sensorId, true); err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
@@ -746,8 +237,7 @@ func unhideSensor(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Invalid sensor ID", http.StatusBadRequest)
return
}
_, err = db.Exec("UPDATE sensors SET hidden = 0 WHERE sensor_id = ?", sensorId)
if err != nil {
if err := store.SetSensorHidden(sensorId, false); err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
@@ -775,25 +265,9 @@ func setupRoutes() *http.ServeMux {
return mux
}
func getDevices(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query("SELECT id, name FROM devices ORDER BY id")
if err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
defer rows.Close()
devices := []map[string]interface{}{}
for rows.Next() {
var id int
var name string
err := rows.Scan(&id, &name)
if err != nil {
http.Error(w, "Scan error", http.StatusInternalServerError)
return
}
devices = append(devices, map[string]interface{}{
"id": id,
"name": name,
})
devices := []*datastore.Device{}
for device := range store.ListDevices() {
devices = append(devices, device)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(devices)
@@ -842,39 +316,8 @@ func turnOffDevice(w http.ResponseWriter, r *http.Request) {
}
func getSensors(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query("SELECT sensor_id, protocol, model, id, name, last_temperature, last_humidity, last_timestamp, hidden FROM sensors ORDER BY sensor_id")
if err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
defer rows.Close()
sensors := []map[string]interface{}{}
for rows.Next() {
var sensorId int
var protocol, model, name, lastTemp, lastHum string
var id, lastTs, hidden int
err := rows.Scan(&sensorId, &protocol, &model, &id, &name, &lastTemp, &lastHum, &lastTs, &hidden)
if err != nil {
http.Error(w, "Scan error", http.StatusInternalServerError)
return
}
sensor := map[string]interface{}{
"sensor_id": sensorId,
"protocol": protocol,
"model": model,
"id": id,
"name": name,
"hidden": hidden != 0,
}
if lastTemp != "" {
sensor["last_temperature"] = lastTemp
}
if lastHum != "" {
sensor["last_humidity"] = lastHum
}
if lastTs > 0 {
sensor["last_timestamp"] = lastTs
}
sensors := []*datastore.Sensor{}
for sensor := range store.ListSensors() {
sensors = append(sensors, sensor)
}
w.Header().Set("Content-Type", "application/json")
@@ -888,17 +331,13 @@ func getDevice(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Invalid device ID", http.StatusBadRequest)
return
}
var name string
err = db.QueryRow("SELECT name FROM devices WHERE id = ?", id).Scan(&name)
device, err := store.GetDevice(id)
if err != nil {
http.Error(w, "Device not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"id": id,
"name": name,
})
json.NewEncoder(w).Encode(device)
}
func getSensor(w http.ResponseWriter, r *http.Request) {
@@ -908,65 +347,13 @@ func getSensor(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Invalid sensor ID", http.StatusBadRequest)
return
}
var protocol, model, name string
var id int
err = db.QueryRow("SELECT protocol, model, id, name FROM sensors WHERE sensor_id = ?", sensorId).Scan(&protocol, &model, &id, &name)
sensor, err := store.GetSensor(sensorId)
if err != nil {
http.Error(w, "Sensor not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"sensor_id": sensorId,
"protocol": protocol,
"model": model,
"id": id,
"name": name,
})
json.NewEncoder(w).Encode(sensor)
}
func listDevices() {
numDevices := telldus.GetNumberOfDevices()
if numDevices < 0 {
errStr := telldus.GetErrorString(numDevices)
fmt.Printf("Error fetching devices: %s\n", errStr)
return
}
fmt.Printf("Number of devices: %d\n", numDevices)
for i := 0; i < numDevices; i++ {
deviceId := telldus.GetDeviceId(i)
name := telldus.GetName(deviceId)
protocol := telldus.GetProtocol(deviceId)
model := telldus.GetModel(deviceId)
fmt.Printf("%d\t%s\tProtocol: %s\tModel: %s\n", deviceId, name, protocol, model)
}
}
func listSensors() {
fmt.Println("\nSENSORS:")
var protocol, model string
var id, dataTypes int
ret := telldus.Sensor(&protocol, &model, &id, &dataTypes)
if ret == 0 {
for {
fmt.Printf("Protocol: %s, Model: %s, ID: %d, DataTypes: %d\n", protocol, model, id, dataTypes)
// Fetch values if available
if dataTypes&telldus.DataTypeTemperature != 0 {
value, timestamp, _ := telldus.SensorValue(protocol, model, id, telldus.DataTypeTemperature)
fmt.Printf(" Temperature: %s°C at %d\n", value, timestamp)
}
if dataTypes&telldus.DataTypeHumidity != 0 {
value, timestamp, _ := telldus.SensorValue(protocol, model, id, telldus.DataTypeHumidity)
fmt.Printf(" Humidity: %s%% at %d\n", value, timestamp)
}
// Add other types as needed
ret = telldus.Sensor(&protocol, &model, &id, &dataTypes)
if ret != 0 {
break
}
}
} else if ret != -6 { // Assuming -6 is TELLSTICK_ERROR_DEVICE_NOT_FOUND
errStr := telldus.GetErrorString(ret)
fmt.Printf("Error fetching sensors: %s\n", errStr)
}
}