update
This commit is contained in:
794
main.go
794
main.go
@@ -1,89 +1,783 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"app/telldus"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.k6n.net/mats/go-telldus-matter/telldus"
|
||||
"github.com/tom-code/gomat"
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
const (
|
||||
fabricID = 0x100
|
||||
adminUser = 5
|
||||
deviceID = 10
|
||||
matterIP = "192.168.1.100" // Replace with actual Matter device IP
|
||||
matterPort = 5540
|
||||
httpPort = ":8080"
|
||||
)
|
||||
|
||||
var fabric *gomat.Fabric
|
||||
var mqttDev *mqttDevice
|
||||
var db *sql.DB
|
||||
|
||||
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
|
||||
|
||||
const maxEvents = 1000
|
||||
|
||||
func main() {
|
||||
// Initialize Telldus
|
||||
telldus.Init()
|
||||
defer telldus.Close()
|
||||
|
||||
// Initialize SQLite DB
|
||||
var err error
|
||||
db, err = sql.Open("sqlite3", "./db/telldus.db")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.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)
|
||||
}
|
||||
_, 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
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)
|
||||
|
||||
// Publish Home Assistant discovery
|
||||
mqttDev.publishDiscovery()
|
||||
|
||||
// Subscribe to command topics
|
||||
mqttDev.subscribeCommands()
|
||||
|
||||
// List devices and sensors
|
||||
listDevices()
|
||||
listSensors()
|
||||
|
||||
// Register callbacks
|
||||
telldus.RegisterDeviceEvent(deviceEventHandler)
|
||||
telldus.RegisterSensorEvent(sensorEventHandler)
|
||||
telldus.RegisterRawDeviceEvent(rawDeviceEventHandler)
|
||||
|
||||
// Initialize Matter certificate manager
|
||||
cm := gomat.NewFileCertManager(fabricID)
|
||||
cm.Load()
|
||||
// Setup graceful shutdown
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-c
|
||||
log.Println("Shutting down gracefully...")
|
||||
mqttDev.client.Disconnect(250)
|
||||
telldus.Close()
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
fabric = gomat.NewFabric(fabricID, cm)
|
||||
|
||||
// Keep the program running
|
||||
fmt.Println("Telldus-Matter bridge running. Press Ctrl+C to exit.")
|
||||
select {}
|
||||
// Start HTTP server
|
||||
log.Println("Server starting on", httpPort)
|
||||
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)
|
||||
|
||||
// Example: If a Telldus device is turned on, turn on a Matter device
|
||||
if method == telldus.MethodTurnOn {
|
||||
sendMatterCommand("on")
|
||||
// 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)
|
||||
|
||||
// Example: Log sensor data, or react to temperature, etc.
|
||||
// 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 sendMatterCommand(command string) {
|
||||
secureChannel, err := gomat.StartSecureChannel(net.ParseIP(matterIP), matterPort, 55555)
|
||||
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("Failed to start secure channel: %v", err)
|
||||
return
|
||||
log.Printf("Error storing potential device: %v", err)
|
||||
}
|
||||
defer secureChannel.Close()
|
||||
|
||||
secureChannel, err = gomat.SigmaExchange(fabric, adminUser, deviceID, secureChannel)
|
||||
if err != nil {
|
||||
log.Printf("Failed to exchange sigma: %v", err)
|
||||
return
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
var invokeCommand []byte
|
||||
switch command {
|
||||
case "on":
|
||||
// invokeCommand = gomat.EncodeInvokeCommand(1, 6, 1, []byte{})
|
||||
invokeCommand = []byte{} // Placeholder
|
||||
case "off":
|
||||
// invokeCommand = gomat.EncodeInvokeCommand(1, 6, 0, []byte{})
|
||||
invokeCommand = []byte{} // Placeholder
|
||||
// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(devices)
|
||||
}
|
||||
|
||||
func renameDevice(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid device ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, err = db.Exec("UPDATE devices SET name = ? WHERE id = ?", req.Name, id)
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Republish discovery for this device
|
||||
mqttDev.republishDevice(id)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func renameSensor(w http.ResponseWriter, r *http.Request) {
|
||||
sensorIdStr := r.PathValue("sensor_id")
|
||||
sensorId, err := strconv.Atoi(sensorIdStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid sensor ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
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)
|
||||
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 {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Republish discovery for this sensor
|
||||
mqttDev.republishSensor(protocol, model, id)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func hideSensor(w http.ResponseWriter, r *http.Request) {
|
||||
sensorIdStr := r.PathValue("sensor_id")
|
||||
sensorId, err := strconv.Atoi(sensorIdStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid sensor ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, err = db.Exec("UPDATE sensors SET hidden = 1 WHERE sensor_id = ?", sensorId)
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func unhideSensor(w http.ResponseWriter, r *http.Request) {
|
||||
sensorIdStr := r.PathValue("sensor_id")
|
||||
sensorId, err := strconv.Atoi(sensorIdStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid sensor ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, err = db.Exec("UPDATE sensors SET hidden = 0 WHERE sensor_id = ?", sensorId)
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func setupRoutes() *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/devices", getDevices)
|
||||
mux.HandleFunc("POST /api/devices/{id}/turnon", turnOnDevice)
|
||||
mux.HandleFunc("POST /api/devices/{id}/learn", learnDevice)
|
||||
mux.HandleFunc("POST /api/devices/{id}/turnoff", turnOffDevice)
|
||||
mux.HandleFunc("/api/sensors", getSensors)
|
||||
mux.HandleFunc("/api/devices/{id}", getDevice)
|
||||
mux.HandleFunc("PUT /api/devices/{id}", renameDevice)
|
||||
mux.HandleFunc("PUT /api/sensors/{sensor_id}", renameSensor)
|
||||
mux.HandleFunc("PUT /api/sensors/{sensor_id}/hide", hideSensor)
|
||||
mux.HandleFunc("PUT /api/sensors/{sensor_id}/unhide", unhideSensor)
|
||||
mux.HandleFunc("/api/sensors/{sensor_id}", getSensor)
|
||||
mux.HandleFunc("/api/events/raw", getRawEvents)
|
||||
mux.HandleFunc("/api/events/sensor", getSensorEvents)
|
||||
mux.HandleFunc("/api/potential_devices", getPotentialDevices)
|
||||
// Serve static files for the frontend
|
||||
mux.Handle("/", http.FileServer(http.Dir("./dist")))
|
||||
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,
|
||||
})
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(devices)
|
||||
}
|
||||
|
||||
type CommandResult struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Code int `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
func turnOnDevice(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid device ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
status := telldus.TurnOn(id)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(CommandResult{Success: status == 0, Message: "turned on", Code: status})
|
||||
}
|
||||
|
||||
func learnDevice(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid device ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
status := telldus.Learn(id)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(CommandResult{Success: status == 0, Message: "learning started", Code: status})
|
||||
}
|
||||
|
||||
func turnOffDevice(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid device ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
status := telldus.TurnOff(id)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(CommandResult{Success: status == 0, Message: "turned off", Code: status})
|
||||
}
|
||||
|
||||
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 = append(sensors, sensor)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(sensors)
|
||||
}
|
||||
|
||||
func getDevice(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid device ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var name string
|
||||
err = db.QueryRow("SELECT name FROM devices WHERE id = ?", id).Scan(&name)
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
func getSensor(w http.ResponseWriter, r *http.Request) {
|
||||
sensorIdStr := r.PathValue("sensor_id")
|
||||
sensorId, err := strconv.Atoi(sensorIdStr)
|
||||
if err != nil {
|
||||
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)
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
secureChannel.Send(invokeCommand)
|
||||
resp, err := secureChannel.Receive()
|
||||
if err != nil {
|
||||
log.Printf("Failed to receive response: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Matter command response: %v\n", resp)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user