more refinements

This commit is contained in:
Mats Tornberg
2025-11-22 15:57:36 +00:00
parent 00f236cac7
commit 790b174a9e
8 changed files with 270 additions and 22 deletions

28
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,28 @@
FROM golang:1.25.4-bookworm
# Install dependencies for telldus-core
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
libssl-dev \
libavahi-client-dev \
libglib2.0-dev \
libftdi-dev \
libconfuse-dev \
nodejs \
npm \
&& rm -rf /var/lib/apt/lists/*
# Build telldus-core
COPY ./telldus-core /usr/src/telldus-core
WORKDIR /usr/src/telldus-core
RUN cmake . \
&& make \
&& make install \
&& ldconfig
# Set workdir for the project
WORKDIR /workspaces/go-telldus-matter
ENV GO111MODULE=on
ENV CGO_ENABLED=1

View File

@@ -0,0 +1,33 @@
{
"name": "Go Telldus Matter Dev",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"workspaceFolder": "/workspaces/go-telldus-matter",
"runArgs": [
"--privileged"
],
"mounts": [
"source=/dev/bus/usb,target=/dev/bus/usb,type=bind",
"source=${localWorkspaceFolder}/tellstick.conf,target=/etc/tellstick.conf,type=bind"
],
"containerEnv": {
"MQTT_URL": "10.10.3.12:1883",
"MQTT_USER": "telldus-dev",
"MQTT_PASSWORD": ""
},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.Go",
"ms-vscode.vscode-typescript-next"
],
"settings": {
"go.toolsManagement.checkForUpdates": "local",
"go.useLanguageServer": true
}
}
},
"postCreateCommand": "go mod download && cd frontend && npm install"
}

View File

@@ -50,17 +50,7 @@ RUN cp -r dist /go/src/app/
# Set workdir back
WORKDIR /go/src/app
# Create startup script
RUN echo '#!/bin/bash\n\
/usr/local/sbin/telldusd --nodaemon &\n\
TELLDUSD_PID=$!\n\
./main &\n\
MAIN_PID=$!\n\
trap "kill $MAIN_PID $TELLDUSD_PID; exit" INT TERM\n\
wait $MAIN_PID\n\
kill $TELLDUSD_PID' > start.sh && chmod +x start.sh
EXPOSE 8080
# Run the application
CMD ["./start.sh"]
# Run the application (it will manage telldusd internally)
CMD ["./main"]

1
chip

Submodule chip deleted from 9548aca151

Binary file not shown.

15
go.mod
View File

@@ -3,9 +3,14 @@ module app
go 1.25.4
require (
github.com/eclipse/paho.mqtt.golang v1.5.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sync v0.17.0 // indirect
github.com/eclipse/paho.mqtt.golang v1.5.1
github.com/fsnotify/fsnotify v1.7.0
github.com/mattn/go-sqlite3 v1.14.32
)
require (
github.com/gorilla/websocket v1.5.3 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
)

12
go.sum
View File

@@ -1,10 +1,14 @@
github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=

189
main.go
View File

@@ -2,13 +2,16 @@ package main
import (
"app/telldus"
"bufio"
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"strings"
"sync"
@@ -17,6 +20,7 @@ import (
mqtt "github.com/eclipse/paho.mqtt.golang"
_ "github.com/mattn/go-sqlite3"
"github.com/fsnotify/fsnotify"
)
const (
@@ -25,6 +29,8 @@ const (
var mqttDev *mqttDevice
var db *sql.DB
var telldusCmd *exec.Cmd
var telldusMu sync.Mutex
type mqttDevice struct {
client mqtt.Client
@@ -216,7 +222,189 @@ var mu sync.Mutex
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 {
log.Fatalf("Failed to start telldusd: %v", err)
}
defer stopTelldusd()
// Start watching config file
configPath := "/etc/tellstick.conf"
go watchConfigFile(configPath)
// Initialize Telldus
telldus.Init()
defer telldus.Close()
@@ -328,6 +516,7 @@ func main() {
log.Println("Shutting down gracefully...")
mqttDev.client.Disconnect(250)
telldus.Close()
stopTelldusd()
os.Exit(0)
}()