- Add internal/clients/moonraker.go: HTTP client for Moonraker REST API - Add internal/clients/mqtt.go: MQTT client via paho.mqtt.golang with TLS support - Add internal/workers/moonraker_poller.go: background polling, usage logging, SSE broadcasts - Add internal/workers/mqtt_subscriber.go: per-printer MQTT subscriber, Bambu telemetry parsing, SSE broadcasts - Wire workers into cmd/server/main.go (Start/Stop with graceful shutdown) Blocked at step 4: go toolchain not installed on build host
120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
// Package clients provides third-party printer integrations.
|
|
package clients
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
|
)
|
|
|
|
// MQTTClient wraps the Eclipse Paho MQTT client for printer telemetry.
|
|
type MQTTClient struct {
|
|
client mqtt.Client
|
|
}
|
|
|
|
// MQTTConfig holds per-printer MQTT connection settings.
|
|
type MQTTConfig struct {
|
|
BrokerHost string
|
|
BrokerPort int
|
|
TopicPrefix string
|
|
TLSEnabled bool
|
|
ClientID string
|
|
}
|
|
|
|
// BambuPrintStatus is the known Bambu Lab print-status payload shape.
|
|
type BambuPrintStatus struct {
|
|
Print struct {
|
|
GcodeFile string `json:"gcode_file,omitempty"`
|
|
Stage int `json:"stage,omitempty"`
|
|
SubTaskName string `json:"subtask_name,omitempty"`
|
|
PrintType string `json:"print_type,omitempty"`
|
|
FilamentUsedMm float64 `json:"mc_percent,omitempty"` // placeholder; real telemetry varies
|
|
} `json:"print,omitempty"`
|
|
}
|
|
|
|
// NewMQTTClient creates an MQTT client connected to the given broker.
|
|
func NewMQTTClient(cfg MQTTConfig) (*MQTTClient, error) {
|
|
if cfg.BrokerPort == 0 {
|
|
if cfg.TLSEnabled {
|
|
cfg.BrokerPort = 8883
|
|
} else {
|
|
cfg.BrokerPort = 1883
|
|
}
|
|
}
|
|
if cfg.ClientID == "" {
|
|
cfg.ClientID = fmt.Sprintf("extrudex-%d", time.Now().Unix())
|
|
}
|
|
|
|
opts := mqtt.NewClientOptions().
|
|
AddBroker(fmt.Sprintf("tcp://%s:%d", cfg.BrokerHost, cfg.BrokerPort)).
|
|
SetClientID(cfg.ClientID).
|
|
SetAutoReconnect(true).
|
|
SetConnectTimeout(10 * time.Second).
|
|
SetOrderMatters(false)
|
|
|
|
if cfg.TLSEnabled {
|
|
opts = opts.SetTLSConfig(&tls.Config{InsecureSkipVerify: false})
|
|
}
|
|
|
|
client := mqtt.NewClient(opts)
|
|
token := client.Connect()
|
|
if token.Wait() && token.Error() != nil {
|
|
return nil, fmt.Errorf("mqtt connect failed: %w", token.Error())
|
|
}
|
|
|
|
slog.Info("mqtt client connected", "broker", cfg.BrokerHost, "port", cfg.BrokerPort, "tls", cfg.TLSEnabled)
|
|
return &MQTTClient{client: client}, nil
|
|
}
|
|
|
|
// Subscribe registers a callback for messages matching topic.
|
|
func (c *MQTTClient) Subscribe(topic string, qos byte, callback func([]byte)) error {
|
|
token := c.client.Subscribe(topic, qos, func(_ mqtt.Client, msg mqtt.Message) {
|
|
callback(msg.Payload())
|
|
})
|
|
if token.Wait() && token.Error() != nil {
|
|
return fmt.Errorf("mqtt subscribe failed: %w", token.Error())
|
|
}
|
|
slog.Info("mqtt subscribed", "topic", topic, "qos", qos)
|
|
return nil
|
|
}
|
|
|
|
// Unsubscribe removes a subscription.
|
|
func (c *MQTTClient) Unsubscribe(topics ...string) error {
|
|
token := c.client.Unsubscribe(topics...)
|
|
if token.Wait() && token.Error() != nil {
|
|
return fmt.Errorf("mqtt unsubscribe failed: %w", token.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Disconnect cleanly disconnects the MQTT client.
|
|
func (c *MQTTClient) Disconnect(quiesceMs uint) {
|
|
c.client.Disconnect(quiesceMs)
|
|
}
|
|
|
|
// IsConnected returns whether the underlying client is connected.
|
|
func (c *MQTTClient) IsConnected() bool {
|
|
return c.client.IsConnected()
|
|
}
|
|
|
|
// ParseBambuTelemetry attempts to parse a Bambu Lab telemetry JSON payload.
|
|
func ParseBambuTelemetry(payload []byte) (*BambuPrintStatus, error) {
|
|
var msg BambuPrintStatus
|
|
if err := json.Unmarshal(payload, &msg); err != nil {
|
|
return nil, fmt.Errorf("parse bambu telemetry failed: %w", err)
|
|
}
|
|
return &msg, nil
|
|
}
|
|
|
|
// DefaultBambuTopics returns the default topic patterns for Bambu Lab printers.
|
|
func DefaultBambuTopics(topicPrefix string) []string {
|
|
return []string{
|
|
topicPrefix + "/report",
|
|
}
|
|
}
|