update
This commit is contained in:
313
pkg/datastore/datastore.go
Normal file
313
pkg/datastore/datastore.go
Normal file
@@ -0,0 +1,313 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
pkg/datastore/types.go
Normal file
53
pkg/datastore/types.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package datastore
|
||||
|
||||
import "time"
|
||||
|
||||
// Device represents a Telldus device
|
||||
type Device struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
UniqueID string `json:"unique_id"`
|
||||
}
|
||||
|
||||
// Sensor represents a Telldus sensor
|
||||
type Sensor struct {
|
||||
SensorID int `json:"sensor_id"`
|
||||
Protocol string `json:"protocol"`
|
||||
Model string `json:"model"`
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
TemperatureUniqueID string `json:"temperature_unique_id"`
|
||||
HumidityUniqueID string `json:"humidity_unique_id"`
|
||||
LastTemperature string `json:"last_temperature,omitempty"`
|
||||
LastHumidity string `json:"last_humidity,omitempty"`
|
||||
LastTimestamp int64 `json:"last_timestamp,omitempty"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
// PotentialDevice represents a detected but not yet configured device
|
||||
type PotentialDevice struct {
|
||||
ID int `json:"id"`
|
||||
Class string `json:"class"`
|
||||
Protocol string `json:"protocol"`
|
||||
Model string `json:"model"`
|
||||
DeviceID string `json:"device_id"`
|
||||
LastData string `json:"last_data"`
|
||||
LastSeen int64 `json:"last_seen"`
|
||||
}
|
||||
|
||||
// RawEvent represents a raw device event
|
||||
type RawEvent struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
ControllerID int `json:"controller_id"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// SensorEvent represents a sensor data event
|
||||
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"`
|
||||
}
|
||||
169
pkg/devices/manager.go
Normal file
169
pkg/devices/manager.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package devices
|
||||
|
||||
import (
|
||||
"app/pkg/datastore"
|
||||
"app/pkg/mqtt"
|
||||
"app/pkg/telldus"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EventManager handles telldus events and callbacks
|
||||
type EventManager struct {
|
||||
store *datastore.DataStore
|
||||
mqttClient *mqtt.Client
|
||||
rawEvents []datastore.RawEvent
|
||||
sensorEvents []datastore.SensorEvent
|
||||
mu sync.Mutex
|
||||
maxEvents int
|
||||
}
|
||||
|
||||
// NewEventManager creates a new event manager
|
||||
func NewEventManager(store *datastore.DataStore, mqttClient *mqtt.Client, maxEvents int) *EventManager {
|
||||
return &EventManager{
|
||||
store: store,
|
||||
mqttClient: mqttClient,
|
||||
maxEvents: maxEvents,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRawEvents returns a copy of raw events
|
||||
func (em *EventManager) GetRawEvents() []datastore.RawEvent {
|
||||
em.mu.Lock()
|
||||
defer em.mu.Unlock()
|
||||
events := make([]datastore.RawEvent, len(em.rawEvents))
|
||||
copy(events, em.rawEvents)
|
||||
return events
|
||||
}
|
||||
|
||||
// GetSensorEvents returns a copy of sensor events
|
||||
func (em *EventManager) GetSensorEvents() []datastore.SensorEvent {
|
||||
em.mu.Lock()
|
||||
defer em.mu.Unlock()
|
||||
events := make([]datastore.SensorEvent, len(em.sensorEvents))
|
||||
copy(events, em.sensorEvents)
|
||||
return events
|
||||
}
|
||||
|
||||
// HandleDeviceEvent handles device state change events
|
||||
func (em *EventManager) HandleDeviceEvent(deviceID, method int, data string, callbackID int) {
|
||||
fmt.Printf("Device event: ID=%d, Method=%d, Data=%s\n", deviceID, method, data)
|
||||
|
||||
var state string
|
||||
switch method {
|
||||
case telldus.MethodTurnOn:
|
||||
state = "ON"
|
||||
case telldus.MethodTurnOff:
|
||||
state = "OFF"
|
||||
}
|
||||
|
||||
if state != "" {
|
||||
em.mqttClient.PublishDeviceState(deviceID, state)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleSensorEvent handles sensor data events
|
||||
func (em *EventManager) HandleSensorEvent(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
|
||||
em.mqttClient.PublishSensorValue(protocol, model, id, dataType, value)
|
||||
|
||||
// Store in history
|
||||
em.mu.Lock()
|
||||
em.sensorEvents = append(em.sensorEvents, datastore.SensorEvent{
|
||||
Timestamp: time.Now(),
|
||||
Protocol: protocol,
|
||||
Model: model,
|
||||
ID: id,
|
||||
DataType: dataType,
|
||||
Value: value,
|
||||
})
|
||||
if len(em.sensorEvents) > em.maxEvents {
|
||||
em.sensorEvents = em.sensorEvents[1:]
|
||||
}
|
||||
em.mu.Unlock()
|
||||
|
||||
// Update last value in DB
|
||||
if err := em.store.UpdateSensorValue(protocol, model, id, dataType, value); err != nil {
|
||||
log.Printf("Error updating sensor %s %s %d: %v", protocol, model, id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleRawDeviceEvent handles raw device detection events
|
||||
func (em *EventManager) HandleRawDeviceEvent(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
|
||||
potentialDev := &datastore.PotentialDevice{
|
||||
Class: class,
|
||||
Protocol: protocol,
|
||||
Model: model,
|
||||
DeviceID: deviceID,
|
||||
LastData: data,
|
||||
LastSeen: time.Now().Unix(),
|
||||
}
|
||||
if err := em.store.UpsertPotentialDevice(potentialDev); err != nil {
|
||||
log.Printf("Error storing potential device: %v", err)
|
||||
}
|
||||
|
||||
// If sensor, ensure in sensors table
|
||||
if class == "sensor" {
|
||||
idInt, _ := strconv.Atoi(deviceID)
|
||||
sensor := &datastore.Sensor{
|
||||
Protocol: protocol,
|
||||
Model: model,
|
||||
ID: idInt,
|
||||
Name: fmt.Sprintf("%s %s %s", protocol, model, deviceID),
|
||||
TemperatureUniqueID: fmt.Sprintf("telldus_sensor_%s_%s_%s_temperature", protocol, model, deviceID),
|
||||
HumidityUniqueID: fmt.Sprintf("telldus_sensor_%s_%s_%s_humidity", protocol, model, deviceID),
|
||||
}
|
||||
if err := em.store.UpsertSensor(sensor); err != nil {
|
||||
log.Printf("Error inserting sensor from raw: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Log the raw event data
|
||||
em.mu.Lock()
|
||||
em.rawEvents = append(em.rawEvents, datastore.RawEvent{
|
||||
Timestamp: time.Now(),
|
||||
ControllerID: controllerID,
|
||||
Data: data,
|
||||
})
|
||||
if len(em.rawEvents) > em.maxEvents {
|
||||
em.rawEvents = em.rawEvents[1:]
|
||||
}
|
||||
em.mu.Unlock()
|
||||
}
|
||||
|
||||
// RegisterCallbacks registers all event callbacks with telldus
|
||||
func (em *EventManager) RegisterCallbacks() {
|
||||
telldus.RegisterDeviceEvent(em.HandleDeviceEvent)
|
||||
telldus.RegisterSensorEvent(em.HandleSensorEvent)
|
||||
telldus.RegisterRawDeviceEvent(em.HandleRawDeviceEvent)
|
||||
}
|
||||
110
pkg/devices/sync.go
Normal file
110
pkg/devices/sync.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package devices
|
||||
|
||||
import (
|
||||
"app/pkg/datastore"
|
||||
"app/pkg/telldus"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Syncer handles synchronization of devices and sensors to the database
|
||||
type Syncer struct {
|
||||
store *datastore.DataStore
|
||||
}
|
||||
|
||||
// NewSyncer creates a new device/sensor syncer
|
||||
func NewSyncer(store *datastore.DataStore) *Syncer {
|
||||
return &Syncer{store: store}
|
||||
}
|
||||
|
||||
// SyncDevices synchronizes all telldus devices to the database
|
||||
func (s *Syncer) SyncDevices() error {
|
||||
numDevices := telldus.GetNumberOfDevices()
|
||||
for i := 0; i < numDevices; i++ {
|
||||
deviceID := telldus.GetDeviceId(i)
|
||||
name := telldus.GetName(deviceID)
|
||||
device := &datastore.Device{
|
||||
ID: deviceID,
|
||||
Name: name,
|
||||
UniqueID: fmt.Sprintf("telldus_device_%d", deviceID),
|
||||
}
|
||||
if err := s.store.UpsertDevice(device); err != nil {
|
||||
log.Printf("Error upserting device %d: %v", deviceID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncSensors synchronizes all telldus sensors to the database
|
||||
func (s *Syncer) SyncSensors() error {
|
||||
var protocol, model string
|
||||
var id, dataTypes int
|
||||
ret := telldus.Sensor(&protocol, &model, &id, &dataTypes)
|
||||
|
||||
for ret == 0 {
|
||||
sensor := &datastore.Sensor{
|
||||
Protocol: protocol,
|
||||
Model: model,
|
||||
ID: id,
|
||||
Name: fmt.Sprintf("%s %s %d", protocol, model, id),
|
||||
}
|
||||
if dataTypes&telldus.DataTypeTemperature != 0 {
|
||||
sensor.TemperatureUniqueID = fmt.Sprintf("telldus_sensor_%s_%s_%d_temperature", protocol, model, id)
|
||||
}
|
||||
if dataTypes&telldus.DataTypeHumidity != 0 {
|
||||
sensor.HumidityUniqueID = fmt.Sprintf("telldus_sensor_%s_%s_%d_humidity", protocol, model, id)
|
||||
}
|
||||
if err := s.store.UpsertSensor(sensor); err != nil {
|
||||
log.Printf("Error upserting sensor %s %s %d: %v", protocol, model, id, err)
|
||||
}
|
||||
ret = telldus.Sensor(&protocol, &model, &id, &dataTypes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListDevices prints all devices to stdout
|
||||
func (s *Syncer) 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)
|
||||
}
|
||||
}
|
||||
|
||||
// ListSensors prints all sensors to stdout
|
||||
func (s *Syncer) 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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
159
pkg/mqtt/discovery.go
Normal file
159
pkg/mqtt/discovery.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package mqtt
|
||||
|
||||
import (
|
||||
"app/pkg/datastore"
|
||||
"app/pkg/telldus"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// DeviceDiscovery represents Home Assistant device discovery payload
|
||||
type DeviceDiscovery struct {
|
||||
Name string `json:"name"`
|
||||
CommandTopic string `json:"command_topic"`
|
||||
StateTopic string `json:"state_topic"`
|
||||
UniqueID string `json:"unique_id"`
|
||||
Device map[string]interface{} `json:"device"`
|
||||
}
|
||||
|
||||
// SensorDiscovery represents Home Assistant sensor discovery payload
|
||||
type SensorDiscovery struct {
|
||||
Name string `json:"name"`
|
||||
StateTopic string `json:"state_topic"`
|
||||
UnitOfMeasurement string `json:"unit_of_measurement,omitempty"`
|
||||
DeviceClass string `json:"device_class,omitempty"`
|
||||
UniqueID string `json:"unique_id"`
|
||||
Device map[string]interface{} `json:"device"`
|
||||
}
|
||||
|
||||
// PublishAllDiscovery publishes Home Assistant discovery messages for all devices and sensors
|
||||
func (c *Client) PublishAllDiscovery() error {
|
||||
if err := c.publishDeviceDiscovery(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.publishSensorDiscovery(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// publishDeviceDiscovery publishes discovery for all devices
|
||||
func (c *Client) publishDeviceDiscovery() error {
|
||||
for device := range c.store.ListDevices() {
|
||||
if err := c.PublishDeviceDiscovery(device.ID); err != nil {
|
||||
log.Printf("Error publishing discovery for device %d: %v", device.ID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishDeviceDiscovery publishes Home Assistant discovery for a single device
|
||||
func (c *Client) PublishDeviceDiscovery(deviceID int) error {
|
||||
device, err := c.store.GetDevice(deviceID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("device %d not found: %w", deviceID, err)
|
||||
}
|
||||
|
||||
topic := fmt.Sprintf("homeassistant/switch/%s/config", device.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"
|
||||
}
|
||||
}`, device.Name, deviceID, deviceID, device.UniqueID, deviceID, device.Name)
|
||||
|
||||
c.client.Publish(topic, 0, true, payload)
|
||||
return nil
|
||||
}
|
||||
|
||||
// publishSensorDiscovery publishes discovery for all sensors
|
||||
func (c *Client) publishSensorDiscovery() error {
|
||||
var protocol, model string
|
||||
var id, dataTypes int
|
||||
ret := telldus.Sensor(&protocol, &model, &id, &dataTypes)
|
||||
|
||||
for ret == 0 {
|
||||
sensor, err := c.store.GetSensorByIdentity(protocol, model, id)
|
||||
if err != nil || sensor.Hidden {
|
||||
log.Printf("Sensor %s %s %d not in DB or hidden, skipping", protocol, model, id)
|
||||
ret = telldus.Sensor(&protocol, &model, &id, &dataTypes)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.publishSensorDiscoveryForSensor(sensor, dataTypes); err != nil {
|
||||
log.Printf("Error publishing discovery for sensor %s %s %d: %v", protocol, model, id, err)
|
||||
}
|
||||
|
||||
ret = telldus.Sensor(&protocol, &model, &id, &dataTypes)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishSensorDiscovery publishes Home Assistant discovery for a single sensor
|
||||
func (c *Client) PublishSensorDiscovery(protocol, model string, id int) error {
|
||||
sensor, err := c.store.GetSensorByIdentity(protocol, model, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sensor %s %s %d not found: %w", protocol, model, id, err)
|
||||
}
|
||||
|
||||
// Get current data types from telldus
|
||||
var p, m string
|
||||
var sensorID, dataTypes int
|
||||
ret := telldus.Sensor(&p, &m, &sensorID, &dataTypes)
|
||||
|
||||
// Find matching sensor
|
||||
for ret == 0 {
|
||||
if p == protocol && m == model && sensorID == id {
|
||||
return c.publishSensorDiscoveryForSensor(sensor, dataTypes)
|
||||
}
|
||||
ret = telldus.Sensor(&p, &m, &sensorID, &dataTypes)
|
||||
}
|
||||
|
||||
return fmt.Errorf("sensor %s %s %d not found in telldus", protocol, model, id)
|
||||
}
|
||||
|
||||
// publishSensorDiscoveryForSensor publishes discovery messages for a sensor's data types
|
||||
func (c *Client) publishSensorDiscoveryForSensor(sensor *datastore.Sensor, dataTypes int) error {
|
||||
if dataTypes&telldus.DataTypeTemperature != 0 && sensor.TemperatureUniqueID != "" {
|
||||
topic := fmt.Sprintf("homeassistant/sensor/%s/config", sensor.TemperatureUniqueID)
|
||||
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"
|
||||
}
|
||||
}`, sensor.Name, sensor.Protocol, sensor.Model, sensor.ID, sensor.TemperatureUniqueID,
|
||||
sensor.Protocol, sensor.Model, sensor.ID, sensor.Name)
|
||||
c.client.Publish(topic, 0, true, payload)
|
||||
}
|
||||
|
||||
if dataTypes&telldus.DataTypeHumidity != 0 && sensor.HumidityUniqueID != "" {
|
||||
topic := fmt.Sprintf("homeassistant/sensor/%s/config", sensor.HumidityUniqueID)
|
||||
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"
|
||||
}
|
||||
}`, sensor.Name, sensor.Protocol, sensor.Model, sensor.ID, sensor.HumidityUniqueID,
|
||||
sensor.Protocol, sensor.Model, sensor.ID, sensor.Name)
|
||||
c.client.Publish(topic, 0, true, payload)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
102
pkg/mqtt/mqtt.go
Normal file
102
pkg/mqtt/mqtt.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package mqtt
|
||||
|
||||
import (
|
||||
"app/pkg/datastore"
|
||||
"app/pkg/telldus"
|
||||
"fmt"
|
||||
|
||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||
)
|
||||
|
||||
// Client handles all MQTT operations
|
||||
type Client struct {
|
||||
client mqtt.Client
|
||||
store *datastore.DataStore
|
||||
subscriptions []string
|
||||
}
|
||||
|
||||
// Config holds MQTT connection configuration
|
||||
type Config struct {
|
||||
BrokerURL string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// New creates a new MQTT client
|
||||
func New(cfg Config, store *datastore.DataStore) (*Client, error) {
|
||||
opts := mqtt.NewClientOptions().AddBroker(cfg.BrokerURL)
|
||||
opts.SetUsername(cfg.Username)
|
||||
opts.SetPassword(cfg.Password)
|
||||
|
||||
client := mqtt.NewClient(opts)
|
||||
if token := client.Connect(); token.Wait() && token.Error() != nil {
|
||||
return nil, token.Error()
|
||||
}
|
||||
|
||||
return &Client{
|
||||
client: client,
|
||||
store: store,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close disconnects the MQTT client
|
||||
func (c *Client) Close() {
|
||||
c.client.Disconnect(250)
|
||||
}
|
||||
|
||||
// PublishDeviceState publishes a device state change
|
||||
func (c *Client) PublishDeviceState(deviceID int, state string) {
|
||||
topic := fmt.Sprintf("telldus/device/%d/state", deviceID)
|
||||
c.client.Publish(topic, 0, false, state)
|
||||
}
|
||||
|
||||
// PublishSensorValue publishes a sensor value
|
||||
func (c *Client) PublishSensorValue(protocol, model string, id int, dataType int, value string) {
|
||||
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)
|
||||
default:
|
||||
return
|
||||
}
|
||||
c.client.Publish(topic, 0, false, value)
|
||||
}
|
||||
|
||||
// UnsubscribeFromDeviceCommands unsubscribes from all tracked command topics
|
||||
func (c *Client) UnsubscribeFromDeviceCommands() {
|
||||
if len(c.subscriptions) > 0 {
|
||||
if token := c.client.Unsubscribe(c.subscriptions...); token.Wait() && token.Error() != nil {
|
||||
// Log error but don't fail
|
||||
}
|
||||
c.subscriptions = nil
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeToDeviceCommands subscribes to command topics for all devices
|
||||
func (c *Client) SubscribeToDeviceCommands() error {
|
||||
// Unsubscribe from existing subscriptions first
|
||||
c.UnsubscribeFromDeviceCommands()
|
||||
|
||||
numDevices := telldus.GetNumberOfDevices()
|
||||
for i := 0; i < numDevices; i++ {
|
||||
deviceID := telldus.GetDeviceId(i)
|
||||
topic := fmt.Sprintf("telldus/device/%d/set", deviceID)
|
||||
|
||||
// Capture deviceID in closure
|
||||
id := deviceID
|
||||
c.client.Subscribe(topic, 0, func(client mqtt.Client, msg mqtt.Message) {
|
||||
payload := string(msg.Payload())
|
||||
if payload == "ON" {
|
||||
telldus.TurnOn(id)
|
||||
} else if payload == "OFF" {
|
||||
telldus.TurnOff(id)
|
||||
}
|
||||
})
|
||||
|
||||
// Track subscription
|
||||
c.subscriptions = append(c.subscriptions, topic)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
156
pkg/telldus-daemon/daemon.go
Normal file
156
pkg/telldus-daemon/daemon.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"app/pkg/telldus"
|
||||
)
|
||||
|
||||
// Manager handles the telldusd daemon lifecycle
|
||||
type Manager struct {
|
||||
cmd *exec.Cmd
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// New creates a new daemon manager
|
||||
func New() *Manager {
|
||||
return &Manager{}
|
||||
}
|
||||
|
||||
// Start starts the telldusd daemon and captures its output
|
||||
func (m *Manager) Start() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.cmd != nil && m.cmd.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)
|
||||
}
|
||||
|
||||
m.cmd = 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()
|
||||
m.mu.Lock()
|
||||
m.cmd = nil
|
||||
m.mu.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
|
||||
}
|
||||
|
||||
// Stop stops the telldusd daemon
|
||||
func (m *Manager) Stop() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.cmd == nil || m.cmd.Process == nil {
|
||||
log.Println("Telldusd not running")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println("Stopping telldusd...")
|
||||
|
||||
// Send SIGTERM
|
||||
if err := m.cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||
log.Printf("Failed to send SIGTERM to telldusd: %v", err)
|
||||
// Try SIGKILL as fallback
|
||||
if err := m.cmd.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 <- m.cmd.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
log.Println("Telldusd stopped successfully")
|
||||
case <-time.After(5 * time.Second):
|
||||
log.Println("Telldusd did not stop gracefully, killing...")
|
||||
m.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
m.cmd = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restart restarts the telldusd daemon
|
||||
func (m *Manager) Restart() error {
|
||||
log.Println("Restarting telldusd due to configuration change...")
|
||||
|
||||
if err := m.Stop(); 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 := m.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reinitialize telldus library
|
||||
telldus.Init()
|
||||
|
||||
log.Println("Telldusd restarted successfully")
|
||||
return nil
|
||||
}
|
||||
63
pkg/telldus-daemon/watcher.go
Normal file
63
pkg/telldus-daemon/watcher.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
// Watcher watches for configuration file changes
|
||||
type Watcher struct {
|
||||
configPath string
|
||||
onReloadFunc func() error
|
||||
}
|
||||
|
||||
// NewWatcher creates a new config file watcher
|
||||
func NewWatcher(configPath string, onReload func() error) *Watcher {
|
||||
return &Watcher{
|
||||
configPath: configPath,
|
||||
onReloadFunc: onReload,
|
||||
}
|
||||
}
|
||||
|
||||
// Watch starts watching for changes to the configuration file
|
||||
func (w *Watcher) Watch() error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
// Watch the parent directory since file operations might replace the file
|
||||
configDir := filepath.Dir(w.configPath)
|
||||
if err := watcher.Add(configDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Watching for changes to %s", w.configPath)
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// Check if the event is for our config file
|
||||
if event.Name == w.configPath && (event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create) {
|
||||
log.Printf("Configuration file changed: %s", event.Op.String())
|
||||
// Call reload callback if provided
|
||||
if w.onReloadFunc != nil {
|
||||
if err := w.onReloadFunc(); err != nil {
|
||||
log.Printf("Failed to reload devices: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
log.Printf("File watcher error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
226
pkg/telldus/telldus.go
Normal file
226
pkg/telldus/telldus.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package telldus
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/usr/local/include
|
||||
#cgo LDFLAGS: -L/usr/local/lib -ltelldus-core
|
||||
#include <telldus-core.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Extern declarations for exported Go callback functions
|
||||
extern void goDeviceEvent(int deviceId, int method, char *data, int callbackId, void *context);
|
||||
extern void goDeviceChangeEvent(int deviceId, int changeEvent, int changeType, int callbackId, void *context);
|
||||
extern void goRawDeviceEvent(char *data, int controllerId, int callbackId, void *context);
|
||||
extern void goSensorEvent(char *protocol, char *model, int id, int dataType, char *value, int timestamp, int callbackId, void *context);
|
||||
extern void goControllerEvent(int controllerId, int changeEvent, int changeType, char *newValue, int callbackId, void *context);
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Callback function types
|
||||
type DeviceEventFunc func(deviceId, method int, data string, callbackId int)
|
||||
type DeviceChangeEventFunc func(deviceId, changeEvent, changeType, callbackId int)
|
||||
type RawDeviceEventFunc func(data string, controllerId, callbackId int)
|
||||
type SensorEventFunc func(protocol, model string, id, dataType int, value string, timestamp, callbackId int)
|
||||
type ControllerEventFunc func(controllerId, changeEvent, changeType int, newValue string, callbackId int)
|
||||
|
||||
// Global callback variables
|
||||
var deviceEventCallback DeviceEventFunc
|
||||
var deviceChangeEventCallback DeviceChangeEventFunc
|
||||
var rawDeviceEventCallback RawDeviceEventFunc
|
||||
var sensorEventCallback SensorEventFunc
|
||||
var controllerEventCallback ControllerEventFunc
|
||||
|
||||
//export goDeviceEvent
|
||||
func goDeviceEvent(deviceId C.int, method C.int, data *C.char, callbackId C.int, context unsafe.Pointer) {
|
||||
if deviceEventCallback != nil {
|
||||
deviceEventCallback(int(deviceId), int(method), C.GoString(data), int(callbackId))
|
||||
}
|
||||
}
|
||||
|
||||
//export goDeviceChangeEvent
|
||||
func goDeviceChangeEvent(deviceId C.int, changeEvent C.int, changeType C.int, callbackId C.int, context unsafe.Pointer) {
|
||||
if deviceChangeEventCallback != nil {
|
||||
deviceChangeEventCallback(int(deviceId), int(changeEvent), int(changeType), int(callbackId))
|
||||
}
|
||||
}
|
||||
|
||||
//export goRawDeviceEvent
|
||||
func goRawDeviceEvent(data *C.char, controllerId C.int, callbackId C.int, context unsafe.Pointer) {
|
||||
if rawDeviceEventCallback != nil {
|
||||
rawDeviceEventCallback(C.GoString(data), int(controllerId), int(callbackId))
|
||||
}
|
||||
}
|
||||
|
||||
//export goSensorEvent
|
||||
func goSensorEvent(protocol *C.char, model *C.char, id C.int, dataType C.int, value *C.char, timestamp C.int, callbackId C.int, context unsafe.Pointer) {
|
||||
if sensorEventCallback != nil {
|
||||
sensorEventCallback(C.GoString(protocol), C.GoString(model), int(id), int(dataType), C.GoString(value), int(timestamp), int(callbackId))
|
||||
}
|
||||
}
|
||||
|
||||
//export goControllerEvent
|
||||
func goControllerEvent(controllerId C.int, changeEvent C.int, changeType C.int, newValue *C.char, callbackId C.int, context unsafe.Pointer) {
|
||||
if controllerEventCallback != nil {
|
||||
controllerEventCallback(int(controllerId), int(changeEvent), int(changeType), C.GoString(newValue), int(callbackId))
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the Telldus library
|
||||
func Init() {
|
||||
C.tdInit()
|
||||
}
|
||||
|
||||
// Close closes the Telldus library
|
||||
func Close() {
|
||||
C.tdClose()
|
||||
}
|
||||
|
||||
// TurnOn turns on a device
|
||||
func TurnOn(deviceId int) int {
|
||||
return int(C.tdTurnOn(C.int(deviceId)))
|
||||
}
|
||||
|
||||
// TurnOff turns off a device
|
||||
func TurnOff(deviceId int) int {
|
||||
return int(C.tdTurnOff(C.int(deviceId)))
|
||||
}
|
||||
|
||||
// Learns a device
|
||||
func Learn(deviceId int) int {
|
||||
return int(C.tdLearn(C.int(deviceId)))
|
||||
}
|
||||
|
||||
// Dim dims a device to a level (0-255)
|
||||
func Dim(deviceId int, level int) int {
|
||||
return int(C.tdDim(C.int(deviceId), C.uchar(level)))
|
||||
}
|
||||
|
||||
// Bell rings the bell on a device
|
||||
func Bell(deviceId int) int {
|
||||
return int(C.tdBell(C.int(deviceId)))
|
||||
}
|
||||
|
||||
// GetNumberOfDevices returns the number of devices
|
||||
func GetNumberOfDevices() int {
|
||||
return int(C.tdGetNumberOfDevices())
|
||||
}
|
||||
|
||||
// GetDeviceId returns the device ID at the given index
|
||||
func GetDeviceId(index int) int {
|
||||
return int(C.tdGetDeviceId(C.int(index)))
|
||||
}
|
||||
|
||||
// GetName returns the name of a device
|
||||
func GetName(deviceId int) string {
|
||||
cstr := C.tdGetName(C.int(deviceId))
|
||||
defer C.tdReleaseString(cstr)
|
||||
return C.GoString(cstr)
|
||||
}
|
||||
|
||||
// GetProtocol returns the protocol of a device
|
||||
func GetProtocol(deviceId int) string {
|
||||
cstr := C.tdGetProtocol(C.int(deviceId))
|
||||
defer C.tdReleaseString(cstr)
|
||||
return C.GoString(cstr)
|
||||
}
|
||||
|
||||
// GetModel returns the model of a device
|
||||
func GetModel(deviceId int) string {
|
||||
cstr := C.tdGetModel(C.int(deviceId))
|
||||
defer C.tdReleaseString(cstr)
|
||||
return C.GoString(cstr)
|
||||
}
|
||||
|
||||
// Methods returns the supported methods for a device
|
||||
func Methods(deviceId int, methodsSupported int) int {
|
||||
return int(C.tdMethods(C.int(deviceId), C.int(methodsSupported)))
|
||||
}
|
||||
|
||||
// Sensor retrieves sensor data
|
||||
func Sensor(protocol *string, model *string, id *int, dataTypes *int) int {
|
||||
var cProtocol [20]C.char
|
||||
var cModel [20]C.char
|
||||
var cId C.int
|
||||
var cDataTypes C.int
|
||||
ret := int(C.tdSensor(&cProtocol[0], 20, &cModel[0], 20, &cId, &cDataTypes))
|
||||
if ret == 0 {
|
||||
*protocol = C.GoString(&cProtocol[0])
|
||||
*model = C.GoString(&cModel[0])
|
||||
*id = int(cId)
|
||||
*dataTypes = int(cDataTypes)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// SensorValue retrieves a specific sensor value
|
||||
func SensorValue(protocol, model string, id, dataType int) (string, int, int) {
|
||||
var value [20]C.char
|
||||
var timestamp C.int
|
||||
ret := int(C.tdSensorValue(C.CString(protocol), C.CString(model), C.int(id), C.int(dataType), &value[0], 20, ×tamp))
|
||||
if ret == 0 {
|
||||
return C.GoString(&value[0]), int(timestamp), ret
|
||||
}
|
||||
return "", 0, ret
|
||||
}
|
||||
|
||||
// GetErrorString returns the error string for an error code
|
||||
func GetErrorString(errorNo int) string {
|
||||
cstr := C.tdGetErrorString(C.int(errorNo))
|
||||
defer C.tdReleaseString(cstr)
|
||||
return C.GoString(cstr)
|
||||
}
|
||||
|
||||
// RegisterDeviceEvent registers a callback for device events
|
||||
func RegisterDeviceEvent(cb DeviceEventFunc) int {
|
||||
deviceEventCallback = cb
|
||||
return int(C.tdRegisterDeviceEvent(C.TDDeviceEvent(C.goDeviceEvent), nil))
|
||||
}
|
||||
|
||||
// RegisterDeviceChangeEvent registers a callback for device change events
|
||||
func RegisterDeviceChangeEvent(cb DeviceChangeEventFunc) int {
|
||||
deviceChangeEventCallback = cb
|
||||
return int(C.tdRegisterDeviceChangeEvent(C.TDDeviceChangeEvent(C.goDeviceChangeEvent), nil))
|
||||
}
|
||||
|
||||
// RegisterRawDeviceEvent registers a callback for raw device events
|
||||
func RegisterRawDeviceEvent(cb RawDeviceEventFunc) int {
|
||||
rawDeviceEventCallback = cb
|
||||
return int(C.tdRegisterRawDeviceEvent(C.TDRawDeviceEvent(C.goRawDeviceEvent), nil))
|
||||
}
|
||||
|
||||
// RegisterSensorEvent registers a callback for sensor events
|
||||
func RegisterSensorEvent(cb SensorEventFunc) int {
|
||||
sensorEventCallback = cb
|
||||
return int(C.tdRegisterSensorEvent(C.TDSensorEvent(C.goSensorEvent), nil))
|
||||
}
|
||||
|
||||
// RegisterControllerEvent registers a callback for controller events
|
||||
func RegisterControllerEvent(cb ControllerEventFunc) int {
|
||||
controllerEventCallback = cb
|
||||
return int(C.tdRegisterControllerEvent(C.TDControllerEvent(C.goControllerEvent), nil))
|
||||
}
|
||||
|
||||
// UnregisterCallback unregisters a callback
|
||||
func UnregisterCallback(callbackId int) int {
|
||||
return int(C.tdUnregisterCallback(C.int(callbackId)))
|
||||
}
|
||||
|
||||
// Constants for methods
|
||||
const (
|
||||
MethodTurnOn = 1
|
||||
MethodTurnOff = 2
|
||||
MethodBell = 4
|
||||
MethodDim = 16
|
||||
MethodLearn = 32
|
||||
)
|
||||
|
||||
// Constants for sensor data types
|
||||
const (
|
||||
DataTypeTemperature = 1
|
||||
DataTypeHumidity = 2
|
||||
DataTypeRainRate = 4
|
||||
DataTypeRainTotal = 8
|
||||
DataTypeWindDirection = 16
|
||||
DataTypeWindAverage = 32
|
||||
DataTypeWindGust = 64
|
||||
)
|
||||
Reference in New Issue
Block a user