From 1f4c3308be296d95163e3e0ea54761410f4da140 Mon Sep 17 00:00:00 2001 From: xengineering Date: Wed, 10 Dec 2025 21:08:39 +0100 Subject: Add homematic-go v0.1.0 This is the minimal viable product (MVP) of this library suitable to build the MVP of the sia-server. --- .../homematic-go/homematic/xmlrpc.go | 147 +++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go (limited to 'vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go') diff --git a/vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go b/vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go new file mode 100644 index 0000000..e6d44af --- /dev/null +++ b/vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go @@ -0,0 +1,147 @@ +package homematic + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "strconv" + + "github.com/beevik/etree" +) + +type Requester struct { + addr string +} + +func NewRequester(addr string) Requester { + return Requester{ + addr: addr, + } +} + +func (req Requester) Request(request etree.Document) (*etree.Document, error) { + r, w := io.Pipe() + + go func() { + request.Indent(2) + _, err := request.WriteTo(w) + _ = w.CloseWithError(err) + }() + + response, err := http.Post(req.addr, "application/xml", r) + if err != nil { + return nil, fmt.Errorf("HTTP POST failed: %w", err) + } + + doc := etree.NewDocument() + _, err = doc.ReadFrom(response.Body) + if err != nil { + return nil, fmt.Errorf("Failed to decode response: %w", err) + } + + return doc, nil +} + +func (req Requester) ListDevices() (Devices, error) { + var devices Devices + + request := etree.NewDocument() + request.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`) + call := request.CreateElement("methodCall") + call.CreateElement("methodName").CreateText("listDevices") + call.CreateElement("params") + + response, err := req.Request(*request) + if err != nil { + return devices, fmt.Errorf("Could not request listDevices: %w", err) + } + + for _, dev := range response.FindElements("/methodResponse/params/param/value/array/data/value/struct") { + doc := etree.NewDocumentWithRoot(dev.Copy()) + + device := Device{} + device.LoadXML(doc) + + devices = append(devices, device) + } + + return devices, nil +} + +func (req Requester) GetValue(address string) (bool, error) { + request := etree.NewDocument() + request.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`) + methodCall := request.CreateElement("methodCall") + methodName := methodCall.CreateElement("methodName") + methodName.CreateText("getValue") + params := methodCall.CreateElement("params") + params.CreateElement("param").CreateElement("value").CreateElement("string").CreateText(address) + params.CreateElement("param").CreateElement("value").CreateElement("string").CreateText("STATE") + + response, err := req.Request(*request) + if err != nil { + return false, fmt.Errorf("Could not request getValue: %w", err) + } + + value := response.FindElement("/methodResponse/params/param/value/i4") + if value == nil { + return false, fmt.Errorf("Could not get value from response") + } + + integer, err := strconv.Atoi(value.Text()) + if err != nil { + return false, fmt.Errorf("Could not convert state value to int: %w", err) + } + + switch integer { + case 0: + return false, nil + case 1: + return true, nil + default: + return false, fmt.Errorf("Cannot cast integer %d to bool", integer) + } +} + +type Responder struct { + ip net.IP + port int + handler func(http.ResponseWriter, *http.Request) +} + +func NewResponder(ip net.IP, port int, handler func(http.ResponseWriter, *http.Request)) Responder { + return Responder{ + ip: ip, + port: port, + handler: handler, + } +} + +func (resp Responder) Start() (context.CancelFunc, int, error) { + listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", resp.ip, resp.port)) + if err != nil { + return nil, 0, fmt.Errorf("Could not listen: %w", err) + } + + port := listener.Addr().(*net.TCPAddr).Port + + m := http.NewServeMux() + m.HandleFunc("/", resp.handler) + + s := http.Server{ + Handler: m, + } + go s.Serve(listener) + + ctx, cancel := context.WithCancel(context.Background()) + + go func() { + <-ctx.Done() + s.Close() + listener.Close() + }() + + return cancel, port, nil +} -- cgit v1.2.3-70-g09d2