summaryrefslogtreecommitdiff
path: root/vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2025-12-10 21:08:39 +0100
committerxengineering <me@xengineering.eu>2025-12-10 21:08:39 +0100
commit1f4c3308be296d95163e3e0ea54761410f4da140 (patch)
tree54640f0f423c02aaae07d99a6754beb47a83f79c /vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go
parent83277c420525e06f9d0c234e018f481b4579d6cd (diff)
downloadsia-server-1f4c3308be296d95163e3e0ea54761410f4da140.tar
sia-server-1f4c3308be296d95163e3e0ea54761410f4da140.tar.zst
sia-server-1f4c3308be296d95163e3e0ea54761410f4da140.zip
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.
Diffstat (limited to 'vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go')
-rw-r--r--vendor/xengineering.eu/homematic-go/homematic/xmlrpc.go147
1 files changed, 147 insertions, 0 deletions
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
+}