diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bd5e9c..ce5c1a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,15 +1,23 @@
# Go Library
-## [v1.10.5 - 2024-01-12]
+## [v1.11.0 - 2024-01-20]
### New
- dblib: add wait till the init_done table indicates db is ready
- dblib: add DBLogout function
+- add hmlib module for using Homematic Devices using [XMLAPI-Addon](https://github.com/homematic-community/XML-API)
+### changed
+- move docker resources to separate folder
+- update dependencies
+- maillib: use mailserver 13.2.0 as test container
### Changed
- update dependencies
- dblib: rename variables
- dblib: change oracle port
- ldaplib: increase time for provisioning to 15s
- remove tools.go
+### fixed
+- NPE on dblib container purge
+
## [v1.10.4 - 2023-11-13]
### New
diff --git a/Readme.md b/Readme.md
index 5ee655a..6866cfa 100644
--- a/Readme.md
+++ b/Readme.md
@@ -13,9 +13,11 @@ this is a collection of my often used functions
- dblib: db related functions, esp. for oracle and tns handling
- maillib: function to send Mails
- ldaplib: base ldap functions
+- hmlib: handle access to homematic devices using [XMLAPI-Addon](https://github.com/homematic-community/XML-API)
### usage
for usage see the provided test cases and the implemenations as is:
- [tnscli](https://github.com/tommi2day/tnscli)
- [pwcli](https://github.com/tommi2day/pwcli)
+- [check_hm](https://github.com/Tommi2Day/check_hm
diff --git a/go.mod b/go.mod
index 83d773b..60aee02 100644
--- a/go.mod
+++ b/go.mod
@@ -4,18 +4,21 @@ go 1.21
require (
github.com/Luzifer/go-openssl/v4 v4.2.2
- github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c
+ github.com/ProtonMail/go-crypto v1.0.0
github.com/emersion/go-imap v1.2.1
github.com/emersion/go-message v0.18.0
github.com/glebarez/go-sqlite v1.22.0
github.com/go-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.6
+ github.com/go-resty/resty/v2 v2.11.0
github.com/hashicorp/vault/api v1.10.0
+ github.com/jarcoal/httpmock v1.3.1
github.com/jmoiron/sqlx v1.3.5
github.com/ory/dockertest/v3 v3.10.0
- github.com/sijms/go-ora/v2 v2.8.5
+ github.com/sijms/go-ora/v2 v2.8.6
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
+ github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.8.4
github.com/wneessen/go-mail v0.4.0
github.com/xlzd/gotp v0.1.0
@@ -34,9 +37,9 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
- github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/docker/cli v24.0.7+incompatible // indirect
- github.com/docker/docker v24.0.7+incompatible // indirect
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+ github.com/docker/cli v25.0.0+incompatible // indirect
+ github.com/docker/docker v25.0.0+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -44,6 +47,7 @@ require (
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.16.0 // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
@@ -63,33 +67,41 @@ require (
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
- github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
- github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/term v0.5.0 // indirect
- github.com/onsi/gomega v1.30.0 // indirect
+ github.com/onsi/gomega v1.31.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.11 // indirect
+ github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
+ github.com/sagikazarmark/locafero v0.4.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
+ github.com/spf13/afero v1.11.0 // indirect
+ github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
+ golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
@@ -98,7 +110,7 @@ require (
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
- modernc.org/libc v1.40.1 // indirect
+ modernc.org/libc v1.40.5 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.28.0 // indirect
diff --git a/go.sum b/go.sum
index fac7ec0..a019622 100644
--- a/go.sum
+++ b/go.sum
@@ -11,8 +11,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
-github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
-github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
+github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
+github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@@ -35,12 +35,13 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
-github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
-github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
-github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docker/cli v25.0.0+incompatible h1:zaimaQdnX7fYWFqzN88exE9LDEvRslexpFowZBX6GoQ=
+github.com/docker/cli v25.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/docker v25.0.0+incompatible h1:g9b6wZTblhMgzOT2tspESstfw6ySZ9kdm94BLDKaZac=
+github.com/docker/docker v25.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -64,6 +65,10 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+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/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
@@ -82,6 +87,8 @@ github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkc
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc=
+github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
+github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
@@ -125,10 +132,10 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ=
github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8=
-github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
-github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
+github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
@@ -147,26 +154,29 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
+github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
-github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
-github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
+github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
+github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
@@ -175,12 +185,15 @@ github.com/opencontainers/runc v1.1.11 h1:9LjxyVlE0BPMRP2wuQDRlHV4941Jp9rc3F0+YK
github.com/opencontainers/runc v1.1.11/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
+github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
+github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
@@ -188,21 +201,34 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
-github.com/sijms/go-ora/v2 v2.8.5 h1:8y20J75NWuZ3+pWfm04j+QZIra2olIFwagTa0gHbm5Q=
-github.com/sijms/go-ora/v2 v2.8.5/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
+github.com/sijms/go-ora/v2 v2.8.6 h1:sq907Z5cno0YIXidOYM85tiQFFVhhgssw09IJPG0WG4=
+github.com/sijms/go-ora/v2 v2.8.6/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -213,6 +239,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/wneessen/go-mail v0.4.0 h1:Oo4HLIV8My7G9JuZkoOX6eipXQD+ACvIqURYeIzUc88=
github.com/wneessen/go-mail v0.4.0/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
@@ -229,6 +257,8 @@ github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8q
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -238,8 +268,11 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
+golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -257,6 +290,7 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -283,13 +317,13 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -299,6 +333,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -312,6 +347,7 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -335,7 +371,6 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -343,8 +378,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
-modernc.org/libc v1.40.1 h1:ZhRylEBcj3GyQbPVC8JxIg7SdrT4JOxIDJoUon0NfF8=
-modernc.org/libc v1.40.1/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
+modernc.org/libc v1.40.5 h1:B9KljZSWzWCV2WtgQ54xu0Ig4imof21SLnKFx7qZ3os=
+modernc.org/libc v1.40.5/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
diff --git a/hmlib/common.go b/hmlib/common.go
new file mode 100644
index 0000000..e250b83
--- /dev/null
+++ b/hmlib/common.go
@@ -0,0 +1,30 @@
+// Package hmlib is a library for interfacing with the HomeMatic XML API
+package hmlib
+
+import (
+ "strconv"
+ "time"
+)
+
+// IDMapEntry contains the objects for any ID
+type IDMapEntry struct {
+ IseID string
+ Name string
+ EntryType string
+ Entry any
+}
+
+// AllIds is a map of all iseIDs to IDMapEntry
+var AllIds = map[string]IDMapEntry{}
+
+// NameIDMap is a map of all names to iseIDs
+var NameIDMap = map[string]string{}
+
+// FormatUnixtsString converts a unix timestamp string to a human readable
+func FormatUnixtsString(ts string) string {
+ timestamp, err := strconv.ParseInt(ts, 10, 64)
+ if err != nil {
+ return ts
+ }
+ return time.Unix(timestamp, 0).Format(time.RFC3339)
+}
diff --git a/hmlib/device.go b/hmlib/device.go
new file mode 100644
index 0000000..86ee4f3
--- /dev/null
+++ b/hmlib/device.go
@@ -0,0 +1,171 @@
+package hmlib
+
+import (
+ "encoding/xml"
+ "fmt"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// DeviceListEndpoint is the endpoint for the device list
+const DeviceListEndpoint = "/addons/xmlapi/devicelist.cgi"
+
+// DeviceTypeListEndpoint is the endpoint for the device type list
+const DeviceTypeListEndpoint = "/addons/xmlapi/devicetypelist.cgi"
+
+// DeviceAddressMap is a list of devices by name
+var DeviceAddressMap = map[string]DeviceListEntry{}
+
+// DeviceIDMap is a list of devices by id
+var DeviceIDMap = map[string]DeviceListEntry{}
+
+// DeviceTypes is a list of device types per name
+var DeviceTypes = map[string]DeviceTypeEntry{}
+
+// DeviceListResponse is a list of devices returned by API
+type DeviceListResponse struct {
+ XMLName xml.Name `xml:"deviceList"`
+ DeviceListEntries []DeviceListEntry `xml:"device"`
+}
+
+// DeviceListEntry is a single device
+type DeviceListEntry struct {
+ XMLName xml.Name `xml:"device"`
+ Name string `xml:"name,attr"`
+ Type string `xml:"device_type,attr"`
+ Address string `xml:"address,attr"`
+ Interface string `xml:"interface,attr"`
+ IseID string `xml:"ise_id,attr"`
+ ReadyConfig string `xml:"ready_config,attr"`
+ Channels []DeviceChannel `xml:"channel"`
+}
+
+// DeviceChannel is a single channel of a device
+type DeviceChannel struct {
+ XMLName xml.Name `xml:"channel"`
+ Name string `xml:"name,attr"`
+ Type string `xml:"type,attr"`
+ Address string `xml:"address,attr"`
+ IseID string `xml:"ise_id,attr"`
+ ParentDevice string `xml:"parent_device,attr"`
+ Index string `xml:"index,attr"`
+ Direction string `xml:"direction,attr"`
+ GroupPartner string `xml:"group_partner,attr"`
+ AesAvailable string `xml:"aes_available,attr"`
+ TransmissionMode string `xml:"transmission_mode,attr"`
+ Visible string `xml:"visible,attr"`
+ ReadyConfig string `xml:"ready_config,attr"`
+ Operate string `xml:"operate,attr"`
+}
+
+// DeviceTypeListResponse is a list of device types returned by API
+type DeviceTypeListResponse struct {
+ XMLName xml.Name `xml:"deviceTypeList"`
+ DeviceTypeListEntries []DeviceTypeEntry `xml:"deviceType"`
+}
+
+// DeviceTypeEntry is a single device type
+type DeviceTypeEntry struct {
+ XMLName xml.Name `xml:"deviceType"`
+ Name string `xml:"name,attr"`
+ Description string `xml:"description,attr"`
+ ThumbnailPath string `xml:"thumbnailPath,attr"`
+ ImagePath string `xml:"imagePath,attr"`
+ Forms []DeviceTypeForm `xml:"form"`
+}
+
+// DeviceTypeForm is a single form of a device type
+type DeviceTypeForm struct {
+ XMLName xml.Name `xml:"form"`
+ Name string `xml:"name,attr"`
+ Type string `xml:"type,attr"`
+}
+
+// GetDeviceList returns the list of devices
+func GetDeviceList(deviceIds []string, internal bool) (result DeviceListResponse, err error) {
+ result = DeviceListResponse{}
+ log.Debug("devicelist called")
+ var parameter = map[string]string{}
+ if len(deviceIds) > 0 {
+ parameter["device_id"] = strings.Join(deviceIds, ",")
+ }
+ if internal {
+ parameter["show_internal"] = "1"
+ }
+ // reset maps
+ AllIds = map[string]IDMapEntry{}
+ DeviceAddressMap = map[string]DeviceListEntry{}
+ DeviceIDMap = map[string]DeviceListEntry{}
+ // query
+ err = QueryAPI(DeviceListEndpoint, &result, parameter)
+ if err != nil {
+ log.Errorf("devicelist returned error: %s", err)
+ return
+ }
+ for _, e := range result.DeviceListEntries {
+ DeviceIDMap[e.IseID] = e
+ DeviceAddressMap[e.Address] = e
+ AllIds[e.IseID] = IDMapEntry{e.IseID, e.Name, "Device", e}
+ for _, c := range e.Channels {
+ AllIds[c.IseID] = IDMapEntry{c.IseID, c.Name, "Channel", c}
+ }
+ }
+ log.Debugf("devicelist returned %d devices", len(result.DeviceListEntries))
+ return
+}
+
+// GetDeviceTypeList returns the list of device types
+func GetDeviceTypeList() (result DeviceTypeListResponse, err error) {
+ result = DeviceTypeListResponse{}
+ DeviceTypes = map[string]DeviceTypeEntry{}
+ log.Debug("devicetypelist called")
+ err = QueryAPI(DeviceTypeListEndpoint, &result, nil)
+ if err != nil {
+ log.Errorf("devicetypelist returned error: %s", err)
+ return
+ }
+ log.Debugf("devicetypelist returned %d devices", len(result.DeviceTypeListEntries))
+ for _, e := range result.DeviceTypeListEntries {
+ DeviceTypes[e.Name] = e
+ }
+ return
+}
+
+// String returns a string representation of a device list
+func (e DeviceListResponse) String() string {
+ var s string
+ for _, e := range e.DeviceListEntries {
+ s += fmt.Sprintf("%s\n", e)
+ }
+ return s
+}
+
+// String returns a string representation of a device
+func (e DeviceListEntry) String() string {
+ return fmt.Sprintf("ID:%s, Name: %s, Address: %s, Type: %s", e.IseID, e.Name, e.Address, e.Type)
+}
+
+// String returns a string representation of a channel
+func (e DeviceChannel) String() string {
+ return fmt.Sprintf("ID:%s, Name: %s, Address: %s, Type: %s, Parent: %s ", e.IseID, e.Name, e.Address, e.Type, e.ParentDevice)
+}
+
+// String returns a string representation of a device type list
+func (e DeviceTypeListResponse) String() string {
+ var s string
+ for _, e := range e.DeviceTypeListEntries {
+ s += fmt.Sprintf("%s\n", e)
+ }
+ return s
+}
+
+// String returns a string representation of a device type
+func (e DeviceTypeEntry) String() string {
+ return fmt.Sprintf("Name: %s, Description: %s, %d forms", e.Name, e.Description, len(e.Forms))
+}
+
+// String returns a string representation of a device type form
+func (e DeviceTypeForm) String() string {
+ return fmt.Sprintf("Name: %s, Type: %s", e.Name, e.Type)
+}
diff --git a/hmlib/device_test.go b/hmlib/device_test.go
new file mode 100644
index 0000000..b36ca74
--- /dev/null
+++ b/hmlib/device_test.go
@@ -0,0 +1,88 @@
+package hmlib
+
+import (
+ "net/url"
+ "testing"
+
+ "github.com/jarcoal/httpmock"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/tommi2day/gomodules/test"
+)
+
+func TestDevice(t *testing.T) {
+ var err error
+ test.Testinit(t)
+ httpmock.ActivateNonDefault(httpClient.GetClient())
+ defer httpmock.DeactivateAndReset()
+ hmURL = MockURL
+ hmToken = MockToken
+ var deviceList DeviceListResponse
+ t.Run("device list", func(t *testing.T) {
+ response := DeviceListTest
+ responder := httpmock.NewStringResponder(200, response)
+ fakeURL := hmURL + DeviceListEndpoint
+ httpmock.RegisterResponder("GET", fakeURL, responder)
+
+ deviceList, err = GetDeviceList(nil, false)
+ assert.NoErrorf(t, err, "GetDeviceList should not return an error:%s", err)
+ require.Greater(t, len(deviceList.DeviceListEntries), 0, "GetDeviceList should return entries")
+ assert.Equal(t, 1, len(DeviceAddressMap), "GetDeviceList should return 1 entry")
+ assert.Equal(t, 1, len(DeviceIDMap), "GetDeviceList should return 1 entry")
+ d, ok := DeviceAddressMap["000955699D3D84"]
+ assert.True(t, ok, "GetDeviceList should contain 000955699D3D84")
+ assert.Equal(t, "Bewegungsmelder Garage", d.Name, "GetDeviceList should contain Bewegungsmelder Garage")
+ d, ok = DeviceIDMap["4740"]
+ assert.True(t, ok, "GetDeviceList should contain 4740")
+ assert.Equal(t, "Bewegungsmelder Garage", d.Name, "GetDeviceList should contain Bewegungsmelder Garage")
+ assert.Equal(t, "HmIP-SMO", d.Type, "GetDeviceList should contain HmIP-SMO")
+ assert.Equal(t, len(d.Channels), 4, "GetDeviceList should contain 4 channels")
+ t.Log(deviceList.String())
+ })
+ t.Run("device list empty", func(t *testing.T) {
+ response := DeviceListEmptyTest
+ responder := httpmock.NewStringResponder(200, response)
+ fakeURL := hmURL + DeviceListEndpoint
+ httpmock.RegisterResponder("GET", fakeURL, responder)
+ deviceList, err = GetDeviceList(nil, true)
+ assert.NoErrorf(t, err, "GetDeviceList should not return an error:%s", err)
+ require.Equal(t, len(deviceList.DeviceListEntries), 0, "GetDeviceList should return entries")
+ assert.Equal(t, 0, len(DeviceAddressMap), "GetDeviceList should return 0 entry")
+ assert.Equal(t, 0, len(DeviceIDMap), "GetDeviceList should return 0 entry")
+ })
+ t.Run("device types list", func(t *testing.T) {
+ response := DeviceTypeListTest
+ responder := httpmock.NewStringResponder(200, response)
+ fakeURL := hmURL + DeviceTypeListEndpoint
+ httpmock.RegisterResponder("GET", fakeURL, responder)
+ deviceTypes, err := GetDeviceTypeList()
+ assert.NoErrorf(t, err, "GetDeviceTypes should not return an error:%s", err)
+ assert.Equal(t, 5, len(deviceTypes.DeviceTypeListEntries), "GetDeviceTypes should return 5 entries")
+ if len(deviceTypes.DeviceTypeListEntries) > 0 {
+ e := deviceTypes.DeviceTypeListEntries[0]
+ assert.Equal(t, "HM-RC-Sec4-3", e.Name, "GetDeviceType[0] should return HM-RC-Sec4-3")
+ assert.Equal(t, "HM-RC-4", e.Description, "GetDeviceType[0] should return HM-RC-4")
+ l := len(e.Forms)
+ assert.Greater(t, l, 1, "GetDeviceType[0] should return more 1 form")
+ if l > 0 {
+ t.Logf("Form[0]:%s", e.Forms[0].String())
+ }
+ }
+ t.Log(deviceTypes.String())
+ })
+ t.Run("device types list not autenticated", func(t *testing.T) {
+ fakeURL := "http://localhost:80" + DeviceTypeListEndpoint
+ sid := "xxx"
+ SetHmToken(sid)
+ SetHmURL("http://localhost:80")
+ q := url.Values{
+ "sid": []string{sid},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", fakeURL, q,
+ httpmock.NewStringResponder(200, DeviceTypeListNotAuthTest))
+ deviceTypes, err := GetDeviceTypeList()
+ assert.Errorf(t, err, "GetDeviceTypes should return an error:%s", err)
+ assert.Equal(t, 0, len(deviceTypes.DeviceTypeListEntries), "GetDeviceTypes should return 0 entries")
+ })
+}
diff --git a/hmlib/hmdata_test.go b/hmlib/hmdata_test.go
new file mode 100644
index 0000000..e807681
--- /dev/null
+++ b/hmlib/hmdata_test.go
@@ -0,0 +1,381 @@
+package hmlib
+
+// MockURL is a sample URL to be used for testing
+const MockURL = "http://localhost:8080"
+
+// MockToken is a sample token to be used for testing
+const MockToken = "1234567890"
+
+// DeviceListTest is a sample response from a devicelist.cgi request
+// devicelist.cgi?device_id=4740
+const DeviceListTest = `
+
+
+
+
+
+
+
+
+`
+
+// DeviceListEmptyTest is a sample response from a devicelist.cgi request with empty result
+const DeviceListEmptyTest = `
+
+`
+
+const DeviceTypeListNotAuthTest = `
+
+
+
+`
+
+// DeviceTypeListTest is a sample response from a devicetypelist.cgi request
+const DeviceTypeListTest = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+// NotificationsTest is a sample response from a notification.cgi request
+const NotificationsTest = `
+
+
+
+
+`
+
+// MasterValueTest is a sample response from a mastervalue.cgi request
+// mastervalue.cgi?device_id=4740,4763
+const MasterValueTest = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+// MasterValueErrorTest is a sample response from a mastervalue.cgi request with error
+const MasterValueErrorTest = `
+
+DEVICE NOT FOUND
+
+`
+
+// MasterValueChangeTest is a sample response from a mastervaluechange.cgi request
+// mastervaluechange.cgi?device_id=4740&name=ARR_TIMEOUT&value=11
+const MasterValueChangeTest = `
+
+
+
+
+
+`
+
+// StateTest is a sample response from a state.cgi request
+// state.cgi?device_id=4740
+const StateTest = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+// StateListTest is a sample response from a statelist.cgi request
+const StateListTest = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+// StateEmptyTest is a sample response from a statelist.cgi request with empty result
+const StateEmptyTest = `
+
+`
+
+// StateDP4748 is a sample response from a state.cgi request for a single datapoint
+const StateDP4748 = `
+
+
+
+`
+
+// StateChangeTest is a sample response from a state.cgi request for a single datapoint
+const StateChangeTest = `
+
+
+
+`
+
+// StateChangeEmptyTest is a sample response from a failed state.cgi request
+const StateChangeEmptyTest = `
+
+`
+
+// StateChangeNotFoundTest is a sample response from a incomplete state.cgi request
+const StateChangeNotFoundTest = `
+
+
+
+
+`
+
+// RssiTest is a sample response from a rssi.cgi request
+const RssiTest = `
+
+
+
+
+
+
+`
+
+// SysVarListTest is a sample response from a sysvar.cgi request
+const SysVarListTest = `
+
+
+
+
+
+
+
+
+
+`
+
+// SysVarTest is a sample response from a sysvar.cgi request
+// sysvar.cgi?ise_id=8254
+const SysVarTest = `
+
+
+
+`
+
+// SysVarTextTest is a sample response from a sysvar.cgi request with text option
+// sysvar.cgi?&ise_id=8254&text=true
+const SysVarTextTest = `
+
+
+
+`
+
+// SysVarEmptyTest is a sample response from a sysvar.cgi request with empty result
+const SysVarEmptyTest = `
+
+`
+
+// RoomListTest is a sample response from a roomlist.cgi request
+const RoomListTest = `
+
+
+
+
+
+
+
+
+
+
+
+
+`
diff --git a/hmlib/mastervalue.go b/hmlib/mastervalue.go
new file mode 100644
index 0000000..f09de00
--- /dev/null
+++ b/hmlib/mastervalue.go
@@ -0,0 +1,125 @@
+package hmlib
+
+import (
+ "encoding/xml"
+ "fmt"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// MasterValueEndpoint is the endpoint for retrieving master value for a device
+const MasterValueEndpoint = "/addons/xmlapi/mastervalue.cgi"
+
+// MasterValueChangeEndpoint is the endpoint for changing master value for a device
+const MasterValueChangeEndpoint = "/addons/xmlapi/mastervaluechange.cgi"
+
+// MasterValues is a list of devices with their master values
+type MasterValues struct {
+ XMLName xml.Name `xml:"mastervalue"`
+ MasterValueDevices []MasterValueDevice `xml:"device"`
+}
+
+// MasterValueDevice is a single device with its master values
+type MasterValueDevice struct {
+ XMLName xml.Name `xml:"device"`
+ Name string `xml:"name,attr"`
+ IseID string `xml:"ise_id,attr"`
+ DeviceType string `xml:"device_type,attr"`
+ MasterValue []MasterValueEntry `xml:"mastervalue"`
+ Error string `xml:"error"`
+ Content string `xml:",chardata"`
+}
+
+// MasterValueEntry is a single entry of a master value
+type MasterValueEntry struct {
+ XMLName xml.Name `xml:"mastervalue"`
+ Name string `xml:"name,attr"`
+ Value string `xml:"value,attr"`
+}
+
+// GetMasterValues returns the master values of the given devices
+func GetMasterValues(deviceIds []string, requestedNames []string) (result MasterValues, err error) {
+ var parameter = map[string]string{}
+ log.Debug("getmastervalue called")
+ if len(deviceIds) == 0 {
+ err = fmt.Errorf("no device id given")
+ return
+ }
+
+ parameter["device_id"] = strings.Join(deviceIds, ",")
+ if len(requestedNames) > 0 {
+ parameter["requested_names"] = strings.Join(requestedNames, ",")
+ }
+
+ err = QueryAPI(MasterValueEndpoint, &result, parameter)
+ if err != nil {
+ err = fmt.Errorf("value query Error id %v", err)
+ return
+ }
+ for _, e := range result.MasterValueDevices {
+ if len(e.Content) > 0 {
+ c := strings.TrimSpace(e.Content)
+ if len(c) > 0 {
+ err = fmt.Errorf("device error for id %s: %s", e.IseID, c)
+ return
+ }
+ }
+ }
+ log.Debugf("getmastervalues returned: %v", result)
+ return
+}
+
+// ChangeMasterValues changes the master values of the given devices
+func ChangeMasterValues(deviceIds []string, names []string, values []string) (result MasterValues, err error) {
+ var parameter = map[string]string{}
+ log.Debug("getmastervalue called")
+ if len(deviceIds) == 0 {
+ err = fmt.Errorf("no device id given")
+ return
+ }
+
+ parameter["device_id"] = strings.Join(deviceIds, ",")
+ if len(names) > 0 {
+ parameter["name"] = strings.Join(names, ",")
+ }
+ if len(values) > 0 {
+ parameter["value"] = strings.Join(values, ",")
+ }
+
+ err = QueryAPI(MasterValueChangeEndpoint, &result, parameter)
+ if err != nil {
+ err = fmt.Errorf("value query Error id %v", err)
+ return
+ }
+ for _, e := range result.MasterValueDevices {
+ if len(e.Content) > 0 {
+ c := strings.TrimSpace(e.Content)
+ if len(c) > 0 {
+ err = fmt.Errorf("device error for id %s: %s", e.IseID, c)
+ return
+ }
+ }
+ }
+ log.Debugf("getmastervalues returned: %v", result)
+ return
+}
+
+// String returns a string representation of a MasterValueDevice
+func (e MasterValueDevice) String() string {
+ return fmt.Sprintf("Name: %s Error: %s Content:%s\n", e.Name, e.Error, e.MasterValue)
+}
+
+// String returns a string representation of a MasterValueEntry
+func (e MasterValueEntry) String() string {
+ return fmt.Sprintf("Name: %s, Value: %s", e.Name, e.Value)
+}
+
+// String returns a string representation of a MasterValues
+func (e MasterValues) String() string {
+ var s string
+ for _, v := range e.MasterValueDevices {
+ s += fmt.Sprintf("%s\n", v)
+ }
+ return s
+}
diff --git a/hmlib/mastervalue_test.go b/hmlib/mastervalue_test.go
new file mode 100644
index 0000000..a7f08ea
--- /dev/null
+++ b/hmlib/mastervalue_test.go
@@ -0,0 +1,90 @@
+package hmlib
+
+import (
+ "net/url"
+ "strings"
+ "testing"
+
+ "github.com/jarcoal/httpmock"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/tommi2day/gomodules/test"
+)
+
+func TestValue(t *testing.T) {
+ test.Testinit(t)
+ httpmock.ActivateNonDefault(httpClient.GetClient())
+ hmURL = MockURL
+ hmToken = MockToken
+ defer httpmock.DeactivateAndReset()
+ valueURL := hmURL + MasterValueEndpoint
+ changeURL := hmURL + MasterValueChangeEndpoint
+ t.Run("mastervalue", func(t *testing.T) {
+ var v MasterValues
+ var err error
+ rn := []string{"ARR_TIMEOUT", "CYCLIC_BIDI_INFO_MSG_DISCARD_FACTOR"}
+ devices := []string{"4740", "4741"}
+ queryValue := url.Values{
+ "device_id": []string{strings.Join(devices, ",")},
+ "requested_names": []string{strings.Join(rn, ",")},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", valueURL, queryValue,
+ httpmock.NewStringResponder(200, MasterValueTest))
+ v, err = GetMasterValues(devices, rn)
+ require.NoErrorf(t, err, "GetValueList should not return an error:%s", err)
+ l := len(v.MasterValueDevices)
+ assert.Equal(t, 2, l, "GetMasterValuies should return 2 devices")
+ if l > 0 {
+ assert.Equal(t, 12, len(v.MasterValueDevices[0].MasterValue), "GetMasterValues should return 2 values")
+ }
+ t.Logf(v.String())
+ })
+ t.Run("mastervalue error", func(t *testing.T) {
+ var v MasterValues
+ var err error
+ rn := []string{"ARR_TIMEOUT", "CYCLIC_BIDI_INFO_MSG_DISCARD_FACTOR"}
+ queryValueError := url.Values{
+ "device_id": []string{"2850"},
+ "requested_names": []string{strings.Join(rn, ",")},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", valueURL, queryValueError,
+ httpmock.NewStringResponder(200, MasterValueErrorTest))
+ v, err = GetMasterValues([]string{"2850"}, rn)
+ require.Errorf(t, err, "GetValueList should return an error")
+ l := len(v.MasterValueDevices)
+ assert.Equal(t, 1, l, "GetMasterValues should return 1 devices")
+ if l == 1 {
+ c := strings.TrimSpace(v.MasterValueDevices[0].Content)
+ assert.Equal(t, "DEVICE NOT FOUND", c, "GetMasterValues should return Error Message")
+ }
+ t.Log(v.String())
+ })
+ t.Run("mastervalue change", func(t *testing.T) {
+ var v MasterValues
+ var err error
+ queryChange := url.Values{
+ "device_id": []string{"4740"},
+ "name": []string{"ARR_TIMEOUT"},
+ "value": []string{"11"},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", changeURL, queryChange,
+ httpmock.NewStringResponder(200, MasterValueChangeTest))
+ v, err = ChangeMasterValues([]string{"4740"}, []string{"ARR_TIMEOUT"}, []string{"11"})
+ require.NoErrorf(t, err, "ChangeMasterValues should not return an error")
+ l := len(v.MasterValueDevices)
+ assert.Equal(t, 1, l, "GetMasterValues should return 1 devices")
+ if l == 1 && len(v.MasterValueDevices[0].MasterValue) > 0 {
+ c := strings.TrimSpace(v.MasterValueDevices[0].MasterValue[0].Value)
+ assert.Equal(t, "11", c, "ChangeMasterValues should return the new value")
+ } else {
+ t.Errorf("ChangeMasterValues should return the changed entry")
+ }
+ t.Log(v.String())
+ })
+}
diff --git a/hmlib/notification.go b/hmlib/notification.go
new file mode 100644
index 0000000..3ab5ffe
--- /dev/null
+++ b/hmlib/notification.go
@@ -0,0 +1,49 @@
+package hmlib
+
+import (
+ "encoding/xml"
+ "fmt"
+ "time"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// NotificationsEndpoint is the endpoint for the notification list
+const NotificationsEndpoint = "/config/xmlapi/systemNotification.cgi"
+
+// SystemNotificationResponse is a list of notifications returned by API
+type SystemNotificationResponse struct {
+ XMLName xml.Name `xml:"systemNotification"`
+ Notifications []Notification `xml:"notification"`
+}
+
+// NotificationDetail is a aggregated single notification
+type NotificationDetail struct {
+ System string
+ Address string
+ Type string
+ Since time.Time
+ Name string
+}
+
+// Notification is a single notification
+type Notification struct {
+ XMLName xml.Name `xml:"notification"`
+ Name string `xml:"name,attr"`
+ Type string `xml:"type,attr"`
+ Timestamp string `xml:"timestamp,attr"`
+ IseID string `xml:"ise_id,attr"`
+}
+
+// String returns a string representation of the notification
+func (e Notification) String() string {
+ return fmt.Sprintf("ID:%s, %s", e.IseID, e.Name)
+}
+
+// GetNotifications returns the list of notifications
+func GetNotifications() (result SystemNotificationResponse, err error) {
+ log.Debug("getnotifications called")
+ err = QueryAPI(NotificationsEndpoint, &result, nil)
+ log.Debugf("notifications returned: %v", result)
+ return
+}
diff --git a/hmlib/notification_test.go b/hmlib/notification_test.go
new file mode 100644
index 0000000..589d753
--- /dev/null
+++ b/hmlib/notification_test.go
@@ -0,0 +1,35 @@
+package hmlib
+
+import (
+ "testing"
+
+ "github.com/jarcoal/httpmock"
+ "github.com/stretchr/testify/assert"
+ "github.com/tommi2day/gomodules/test"
+)
+
+func TestNotification(t *testing.T) {
+ test.Testinit(t)
+ httpmock.ActivateNonDefault(httpClient.GetClient())
+ defer httpmock.DeactivateAndReset()
+
+ hmURL = MockURL
+ hmToken = MockToken
+ // mock the response for notifications
+ responderURL := hmURL + NotificationsEndpoint
+ httpmock.RegisterResponder("GET", responderURL, httpmock.NewStringResponder(200, NotificationsTest))
+
+ stateListURL := hmURL + StateListEndpoint
+ httpmock.RegisterResponder("GET", stateListURL, httpmock.NewStringResponder(200, StateListTest))
+
+ t.Run("get_notifications", func(t *testing.T) {
+ n, err := GetNotifications()
+ assert.NoErrorf(t, err, "GetNotifications should not return an error")
+ assert.Equal(t, 2, len(n.Notifications), "GetNotifications should return 1 entry")
+ assert.Equal(t, "BidCos-RF.NEQ0117117:0.STICKY_UNREACH", n.Notifications[0].Name, "GetNotifications should return BidCos-RF.NEQ0117117:0.STICKY_UNREACH")
+ if len(n.Notifications) > 1 {
+ assert.Equal(t, "HmIP-RF.000955699D3D84:0.LOW_BAT", n.Notifications[1].Name, "GetNotifications should return HmIP-RF.000955699D3D84:0.LOW_BAT")
+ assert.Equal(t, "LOWBAT", n.Notifications[1].Type, "GetNotifications should return LOWBAT type")
+ }
+ })
+}
diff --git a/hmlib/roomlist.go b/hmlib/roomlist.go
new file mode 100644
index 0000000..b94987c
--- /dev/null
+++ b/hmlib/roomlist.go
@@ -0,0 +1,59 @@
+package hmlib
+
+import (
+ "encoding/xml"
+ "fmt"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// RoomListEndpoint is the endpoint for the device list
+const RoomListEndpoint = "/addons/xmlapi/roomlist.cgi"
+
+// RoomListResponse is a list of devices returned by API
+type RoomListResponse struct {
+ XMLName xml.Name `xml:"roomList"`
+ Rooms []Room `xml:"room"`
+}
+
+// Room is a single room in RoomListResponse
+type Room struct {
+ XMLName xml.Name `xml:"room"`
+ Name string `xml:"name,attr"`
+ IseID string `xml:"ise_id,attr"`
+ Channels []RoomChannel `xml:"channel"`
+}
+
+// RoomChannel is a single channel in Room
+type RoomChannel struct {
+ XMLName xml.Name `xml:"channel"`
+ IseID string `xml:"ise_id,attr"`
+}
+
+// RoomMap is a list of rooms by name
+var RoomMap = make(map[string]Room)
+
+// GetRoomList returns the list of rooms
+func GetRoomList() (result RoomListResponse, err error) {
+ log.Debug("getrssi called")
+ err = QueryAPI(RoomListEndpoint, &result, nil)
+ for _, v := range result.Rooms {
+ RoomMap[v.Name] = v
+ }
+ log.Debugf("getRoomList returned %d rooms", len(result.Rooms))
+ return
+}
+
+// String returns a string representation of the device list
+func (e RoomListResponse) String() string {
+ var s string
+ for _, v := range e.Rooms {
+ s += fmt.Sprintf("%s\n", v)
+ }
+ return s
+}
+
+// String returns a string representation of the device list
+func (e Room) String() string {
+ return fmt.Sprintf("Name:%s Channels:%d ", e.Name, len(e.Channels))
+}
diff --git a/hmlib/roomlist_test.go b/hmlib/roomlist_test.go
new file mode 100644
index 0000000..b9e690d
--- /dev/null
+++ b/hmlib/roomlist_test.go
@@ -0,0 +1,37 @@
+package hmlib
+
+import (
+ "testing"
+
+ "github.com/jarcoal/httpmock"
+ "github.com/stretchr/testify/assert"
+ "github.com/tommi2day/gomodules/test"
+)
+
+func TestRoomlist(t *testing.T) {
+ test.Testinit(t)
+ httpmock.ActivateNonDefault(httpClient.GetClient())
+ response := RoomListTest
+ responder := httpmock.NewStringResponder(200, response)
+ fakeURL := hmURL + RoomListEndpoint
+ httpmock.RegisterResponder("GET", fakeURL, responder)
+ defer httpmock.DeactivateAndReset()
+
+ t.Run("RoomListResponse", func(t *testing.T) {
+ actual, err := GetRoomList()
+ t.Logf(actual.String())
+ assert.NoErrorf(t, err, "GetRoomList should not return an error:%s", err)
+ assert.Equal(t, 2, len(actual.Rooms), "GetRoomList should return 2 entries")
+ idl := map[string]string{}
+ for _, r := range actual.Rooms {
+ rn := r.Name
+ cl := r.Channels
+ for _, c := range cl {
+ idl[c.IseID] = rn
+ }
+ }
+ n, ok := idl["3076"]
+ assert.True(t, ok, "GetRoomList should contain channel 3076")
+ assert.Equal(t, "Bad", n, "GetRoomList should contain channel 3076 in Bad")
+ })
+}
diff --git a/hmlib/rssi.go b/hmlib/rssi.go
new file mode 100644
index 0000000..8c41c75
--- /dev/null
+++ b/hmlib/rssi.go
@@ -0,0 +1,53 @@
+package hmlib
+
+import (
+ "encoding/xml"
+ "fmt"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// RssiEndpoint is the endpoint for the device list
+const RssiEndpoint = "/addons/xmlapi/rssilist.cgi"
+
+// RssiListResponse is a list of devices returned by API
+type RssiListResponse struct {
+ XMLName xml.Name `xml:"rssiList"`
+ RssiDevices []RssiDevice `xml:"rssi"`
+}
+
+// RssiDevice returns Rssi values for a single device
+type RssiDevice struct {
+ XMLName xml.Name `xml:"rssi"`
+ Device string `xml:"device,attr"`
+ Rx string `xml:"rx,attr"`
+ Tx string `xml:"tx,attr"`
+}
+
+// RssiDeviceMap is a list of rssi devices by name
+var RssiDeviceMap = make(map[string]RssiDevice)
+
+// GetRssiList returns the rssi list of hm devices
+func GetRssiList() (result RssiListResponse, err error) {
+ log.Debug("getrssi called")
+ err = QueryAPI(RssiEndpoint, &result, nil)
+ for _, v := range result.RssiDevices {
+ RssiDeviceMap[v.Device] = v
+ }
+ log.Debugf("getRSSI returned %d entries", len(result.RssiDevices))
+ return
+}
+
+// String returns a string representation of the device list
+func (e RssiListResponse) String() string {
+ var s string
+ for _, v := range e.RssiDevices {
+ s += fmt.Sprintf("%s\n", v)
+ }
+ return s
+}
+
+// String returns a string representation of the device list
+func (e RssiDevice) String() string {
+ return fmt.Sprintf("Address:%s rx:%s tx: %s", e.Device, e.Rx, e.Tx)
+}
diff --git a/hmlib/rssi_test.go b/hmlib/rssi_test.go
new file mode 100644
index 0000000..a62855c
--- /dev/null
+++ b/hmlib/rssi_test.go
@@ -0,0 +1,28 @@
+package hmlib
+
+import (
+ "testing"
+
+ "github.com/jarcoal/httpmock"
+ "github.com/stretchr/testify/assert"
+ "github.com/tommi2day/gomodules/test"
+)
+
+func TestRssi(t *testing.T) {
+ test.Testinit(t)
+ httpmock.ActivateNonDefault(httpClient.GetClient())
+ response := RssiTest
+ responder := httpmock.NewStringResponder(200, response)
+ fakeURL := hmURL + RssiEndpoint
+ httpmock.RegisterResponder("GET", fakeURL, responder)
+ defer httpmock.DeactivateAndReset()
+
+ t.Run("Rssi func", func(t *testing.T) {
+ actual, err := GetRssiList()
+ t.Logf(actual.String())
+ assert.NoErrorf(t, err, "GetRssiList should not return an error:%s", err)
+ assert.Equal(t, 4, len(actual.RssiDevices), "GetRssiList should return 4 entries")
+ _, ok := RssiDeviceMap["MEQ0481419"]
+ assert.True(t, ok, "GetRssiList should contain MEQ0481419")
+ })
+}
diff --git a/hmlib/state.go b/hmlib/state.go
new file mode 100644
index 0000000..0102fa9
--- /dev/null
+++ b/hmlib/state.go
@@ -0,0 +1,230 @@
+package hmlib
+
+import (
+ "encoding/xml"
+ "fmt"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// StateEndpoint is the endpoint to retrieve the state for a given list of devices
+const StateEndpoint = "/addons/xmlapi/state.cgi"
+
+// StateChangeEndpoint is the endpoint to change a state value
+const StateChangeEndpoint = "/addons/xmlapi/statechange.cgi"
+
+// StateListEndpoint is the endpoint to retrieve the state for all devices
+const StateListEndpoint = "/addons/xmlapi/statelist.cgi"
+
+// StateListResponse is a list of devices returned by API
+type StateListResponse struct {
+ XMLName xml.Name `xml:"stateList"`
+ StateDevices []StateDevice `xml:"device"`
+}
+
+// StateDeviceResponse is a list of devices returned by API
+type StateDeviceResponse struct {
+ XMLName xml.Name `xml:"state"`
+ StateDevices []StateDevice `xml:"device"`
+}
+
+// StateDatapointResponse is a list of datapoints returned by API
+type StateDatapointResponse struct {
+ XMLName xml.Name `xml:"state"`
+ StateDatapoints []Datapoint `xml:"datapoint"`
+}
+
+// StateDevice returns the state of a single device
+type StateDevice struct {
+ XMLName xml.Name `xml:"device"`
+ Name string `xml:"name,attr"`
+ IseID string `xml:"ise_id,attr"`
+ Unreach string `xml:"unreach,attr"`
+ ConfigPending string `xml:"config_pending,attr"`
+ Channels []StateChannel `xml:"channel"`
+}
+
+// StateChannel returns the state of a single channel
+type StateChannel struct {
+ XMLName xml.Name `xml:"channel"`
+ Name string `xml:"name,attr"`
+ IseID string `xml:"ise_id,attr"`
+ LastDPActionTime string `xml:"lastdpactiontime,attr"`
+ Datapoints []Datapoint `xml:"datapoint"`
+}
+
+// Datapoint returns the state of a single datapoint
+type Datapoint struct {
+ XMLName xml.Name `xml:"datapoint"`
+ Name string `xml:"name,attr"`
+ IseID string `xml:"ise_id,attr"`
+ Value string `xml:"value,attr"`
+ ValueType string `xml:"valuetype,attr"`
+ ValueUnit string `xml:"valueunit,attr"`
+ Timestamp string `xml:"timestamp,attr"`
+ LastTimestamp string `xml:"lasttimestamp,attr"`
+}
+
+// StateChangeResponse is the result of a statechange.cgi call
+type StateChangeResponse struct {
+ XMLName xml.Name `xml:"result"`
+ NotFound []bool `xml:"not_found"`
+ Changes []ChangeResult `xml:"changed"`
+}
+
+// ChangeResult is the result of a single change
+type ChangeResult struct {
+ XMLName xml.Name `xml:"changed"`
+ IseID string `xml:"id,attr"`
+ NewValue string `xml:"new_value,attr"`
+ Success bool `xml:"success,attr"`
+}
+
+// StateList is the result of a statelist.cgi call
+var StateList = StateListResponse{}
+
+// GetStateList returns the state of all devices
+func GetStateList() (stateList StateListResponse, err error) {
+ stateList = StateListResponse{}
+ log.Debug("getstatelist called")
+ err = QueryAPI(StateListEndpoint, &stateList, nil)
+ if err != nil {
+ log.Errorf("getstatelist returned error: %s", err)
+ return
+ }
+ for _, e := range stateList.StateDevices {
+ AllIds[e.IseID] = IDMapEntry{e.IseID, e.Name, "Device", e}
+ NameIDMap[e.Name] = e.IseID
+ for _, c := range e.Channels {
+ AllIds[c.IseID] = IDMapEntry{c.IseID, c.Name, "Channel", c}
+ NameIDMap[c.Name] = c.IseID
+ for _, d := range c.Datapoints {
+ AllIds[d.IseID] = IDMapEntry{d.IseID, d.Name, "Datapoint", d}
+ NameIDMap[d.Name] = d.IseID
+ }
+ }
+ }
+ log.Debugf("getstateList returned %d IDs", len(AllIds))
+ StateList = stateList
+ return
+}
+
+// GetStateByDeviceID returns the state of the given devices
+func GetStateByDeviceID(ids []string) (result StateDeviceResponse, err error) {
+ log.Debug("getstatebyid called")
+ if len(ids) == 0 {
+ err = fmt.Errorf("no ids given")
+ return
+ }
+ parameter := map[string]string{"device_id": strings.Join(ids, ",")}
+ err = QueryAPI(StateEndpoint, &result, parameter)
+ log.Debugf("getstate returned: %v", result)
+ return
+}
+
+// GetStateByChannelID returns the state of the given channels
+func GetStateByChannelID(ids []string) (result StateDeviceResponse, err error) {
+ log.Debug("getstatebychannelid called")
+ if len(ids) == 0 {
+ err = fmt.Errorf("no ids given")
+ return
+ }
+ parameter := map[string]string{"channel_id": strings.Join(ids, ",")}
+ err = QueryAPI(StateEndpoint, &result, parameter)
+ log.Debugf("getstate returned: %v", result)
+ return
+}
+
+// GetStateByDataPointID returns the state of the given datapoints
+func GetStateByDataPointID(ids []string) (result StateDatapointResponse, err error) {
+ log.Debug("getstatebydpid called")
+ if len(ids) == 0 {
+ err = fmt.Errorf("no ids given")
+ return
+ }
+ parameter := map[string]string{"datapoint_id": strings.Join(ids, ",")}
+ err = QueryAPI(StateEndpoint, &result, parameter)
+ log.Debugf("getstate returned: %v", result)
+ return
+}
+
+// ChangeState changes the state of the given id
+func ChangeState(ids []string, values []string) (result StateChangeResponse, err error) {
+ result = StateChangeResponse{}
+ log.Debug("changestatebyid called")
+ if len(ids) == 0 {
+ err = fmt.Errorf("no ids given")
+ return
+ }
+ if len(values) == 0 {
+ err = fmt.Errorf("no values given")
+ return
+ }
+ parameter := map[string]string{"ise_id": strings.Join(ids, ",")}
+ parameter["new_value"] = strings.Join(values, ",")
+ err = QueryAPI(StateChangeEndpoint, &result, parameter)
+ if err != nil {
+ err = fmt.Errorf("value query Error id %v", err)
+ return
+ }
+
+ if len(result.Changes) == 0 && len(result.NotFound) == 0 {
+ err = fmt.Errorf("no changes, wrong parameter?")
+ return
+ }
+ l := len(result.NotFound)
+ if l > 0 {
+ err = fmt.Errorf("%d ids not found", l)
+ return
+ }
+
+ log.Debugf("changestate returned: %v", result)
+ return
+}
+
+// String returns a string representation of a StateListResponse
+func (e StateDevice) String() string {
+ return fmt.Sprintf("ID:%s, Name: %s", e.IseID, e.Name)
+}
+
+// String returns a string representation of a StateChannel
+func (e StateChannel) String() string {
+ return fmt.Sprintf("ID:%s, Name: %s", e.IseID, e.Name)
+}
+
+// String returns a string representation of a Datapoint
+func (e Datapoint) String() string {
+ return fmt.Sprintf("ID:%s, Name: %s, Value: %s%s, Type: %s Last: %s", e.IseID, e.Name, e.Value, e.ValueUnit, e.ValueType, e.LastTimestamp)
+}
+
+// String returns a string representation of a StateDeviceResponse
+func (e StateDeviceResponse) String() string {
+ var s string
+ for _, v := range e.StateDevices {
+ s += fmt.Sprintf("%s\n", v)
+ }
+ return s
+}
+
+// String returns a string representation of a StateDatapointResponse
+func (e StateDatapointResponse) String() string {
+ var s string
+ for _, v := range e.StateDatapoints {
+ s += fmt.Sprintf("%s\n", v)
+ }
+ return s
+}
+
+func (e StateChangeResponse) String() string {
+ var s string
+ for _, v := range e.Changes {
+ s += fmt.Sprintf("%s\n", v)
+ }
+ return s
+}
+
+// String returns a string representation of a ChangeResult
+func (e ChangeResult) String() string {
+ return fmt.Sprintf("ID:%s, NewValue: %s, Success: %v", e.IseID, e.NewValue, e.Success)
+}
diff --git a/hmlib/state_test.go b/hmlib/state_test.go
new file mode 100644
index 0000000..26faf41
--- /dev/null
+++ b/hmlib/state_test.go
@@ -0,0 +1,168 @@
+package hmlib
+
+import (
+ "net/url"
+ "testing"
+
+ "github.com/jarcoal/httpmock"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/tommi2day/gomodules/test"
+)
+
+func TestState(t *testing.T) {
+ test.Testinit(t)
+ httpmock.ActivateNonDefault(httpClient.GetClient())
+ hmURL = MockURL
+ hmToken = MockToken
+ defer httpmock.DeactivateAndReset()
+ stateListURL := hmURL + StateListEndpoint
+ httpmock.RegisterResponder("GET", stateListURL, httpmock.NewStringResponder(200, StateListTest))
+
+ stateURL := hmURL + StateEndpoint
+ changeURL := hmURL + StateChangeEndpoint
+ httpmock.RegisterResponder("GET", stateURL, httpmock.NewStringResponder(200, StateTest))
+ // mock the response for state
+ var stateList StateListResponse
+ var err error
+ t.Run("statelist", func(t *testing.T) {
+ stateList, err = GetStateList()
+ require.NoErrorf(t, err, "GetStateList should not return an error:%s", err)
+ sl := len(stateList.StateDevices)
+ assert.Equal(t, 2, sl, "GetStateList should return 2 devices")
+ l := len(AllIds)
+ assert.Equal(t, 44, l, "AllIds should return 44 entries")
+ e, ok := AllIds["4740"]
+ assert.True(t, ok, "AllIds should contain 4740")
+ if ok {
+ assert.Equal(t, "Bewegungsmelder Garage", e.Name, "AllIds should contain Bewegungsmelder Garage")
+ assert.Equal(t, "Device", e.EntryType, "ID 4740 should be a device")
+ assert.IsType(t, StateDevice{}, e.Entry, "ID 4740 should be a device")
+ c := e.Entry.(StateDevice).Channels
+ assert.Equal(t, 4, len(c), "ID 4740 should contain 4 channels")
+ if len(c) > 0 {
+ assert.Contains(t, c[0].Name, "Bewegungsmelder Garage:0", "ID 4740 should contain channel Bewegungsmelder Garage:0")
+ }
+ }
+ })
+ t.Run("single device state", func(t *testing.T) {
+ var s StateDeviceResponse
+ s, err = GetStateByDeviceID([]string{"4740"})
+ require.NoErrorf(t, err, "GetStateBy should not return an error:%s", err)
+ assert.Equal(t, 1, len(s.StateDevices), "GetStateByDeviceID should return 1 device")
+ if len(s.StateDevices) > 0 {
+ assert.Containsf(t, s.StateDevices[0].Name, "Bewegungsmelder Garage", "GetStateByDeviceID should return Bewegungsmelder Garage")
+ assert.Equal(t, 4, len(s.StateDevices[0].Channels), "GetStateByDeviceID should return 1 channel")
+ }
+ t.Logf(s.String())
+ })
+ t.Run("single channel state", func(t *testing.T) {
+ var s StateDeviceResponse
+ s, err = GetStateByChannelID([]string{"4741"})
+ require.NoErrorf(t, err, "GetStateBy should not return an error:%s", err)
+ assert.Equal(t, 1, len(s.StateDevices), "GetStateByDeviceID should return 1 device")
+ if len(s.StateDevices) > 0 {
+ c := s.StateDevices[0].Channels
+ assert.Equal(t, 4, len(c), "GetStateByChannelID should return 1 channel")
+ assert.Containsf(t, c[0].Name, "Bewegungsmelder Garage:0", "GetStateByChannelID should return Bewegungsmelder Garage:0")
+ assert.Equal(t, 10, len(c[0].Datapoints), "GetStateByChannelID should return 1 Datapoints")
+ }
+ t.Logf(s.String())
+ })
+ t.Run("single datapoint state", func(t *testing.T) {
+ var s StateDatapointResponse
+ queryDP := url.Values{
+ "datapoint_id": []string{"4748"},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", stateURL, queryDP,
+ httpmock.NewStringResponder(200, StateDP4748))
+ s, err := GetStateByDataPointID([]string{"4748"})
+ require.NoErrorf(t, err, "GetStateBy should not return an error:%s", err)
+ assert.Equal(t, 1, len(s.StateDatapoints), "GetStateByDatapointID should return 1 datapoint")
+ if len(s.StateDatapoints) > 0 {
+ assert.Equal(t, s.StateDatapoints[0].IseID, "4748", "GetStateByDatapointID should return ID 4748")
+ assert.Equal(t, s.StateDatapoints[0].Value, "false", "GetStateByDatapointID should return value false")
+ }
+ t.Logf(s.String())
+ })
+ t.Run("State Empty", func(t *testing.T) {
+ var s StateDatapointResponse
+ queryStateEmpty := url.Values{
+ "datapoint_id": []string{"9999"},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", stateURL, queryStateEmpty,
+ httpmock.NewStringResponder(200, StateEmptyTest))
+
+ s, err = GetStateByDataPointID([]string{"9999"})
+ require.NoErrorf(t, err, "GetStateBy should not return an error:%s", err)
+ assert.Equal(t, 0, len(s.StateDatapoints), "GetStateByDatapointID should return 0 datapoint")
+ t.Logf(s.String())
+ })
+ t.Run("state change", func(t *testing.T) {
+ var r StateChangeResponse
+
+ queryChange := url.Values{
+ "ise_id": []string{"4740"},
+ "new_value": []string{"11"},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", changeURL, queryChange,
+ httpmock.NewStringResponder(200, StateChangeTest))
+ r, err = ChangeState([]string{"4740"}, []string{"11"})
+ require.NoErrorf(t, err, "ChangeState should not return an error")
+ l := len(r.Changes)
+ assert.Equal(t, 1, l, "GetMasterValues should return 1 devices")
+ if l == 1 {
+ assert.True(t, r.Changes[0].Success, "ChangeState should return the new value")
+ } else {
+ t.Errorf("ChangeState should return the changed entry")
+ }
+ t.Log(r.String())
+ })
+ t.Run("state change not found", func(t *testing.T) {
+ var r StateChangeResponse
+ queryChange := url.Values{
+ "ise_id": []string{"474,4740"},
+ "new_value": []string{"11"},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", changeURL, queryChange,
+ httpmock.NewStringResponder(200, StateChangeNotFoundTest))
+ r, err = ChangeState([]string{"474", "4740"}, []string{"11"})
+ require.Errorf(t, err, "ChangeState should return an error")
+ l := len(r.NotFound)
+ assert.Equal(t, 1, l, "ChangeState should return 1 id not found")
+ l = len(r.Changes)
+ assert.Equal(t, 1, l, "ChangeState should return 1 id success ")
+ if l == 1 {
+ assert.Equal(t, "4740", r.Changes[0].IseID, "ChangeState should return ID 4740")
+ assert.True(t, r.Changes[0].Success, "ChangeState should return true for ID 4740")
+ }
+ t.Log(r.String())
+ })
+ t.Run("state change empty", func(t *testing.T) {
+ var r StateChangeResponse
+ queryChange := url.Values{
+ "device_id": []string{"4740"},
+ "new_value": []string{"11"},
+ "sid": []string{hmToken},
+ }
+ p := map[string]string{
+ "device_id": "4740",
+ "new_value": "11",
+ "sid": hmToken,
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", changeURL, queryChange,
+ httpmock.NewStringResponder(200, StateChangeEmptyTest))
+ err = QueryAPI(changeURL, &r, p)
+ require.Errorf(t, err, "ChangeState should return an error")
+ t.Log(r.String())
+ })
+}
diff --git a/hmlib/sysvar.go b/hmlib/sysvar.go
new file mode 100644
index 0000000..b597bd5
--- /dev/null
+++ b/hmlib/sysvar.go
@@ -0,0 +1,84 @@
+package hmlib
+
+import (
+ "encoding/xml"
+ "fmt"
+ "strings"
+
+ log "github.com/sirupsen/logrus"
+)
+
+// SysVarEndpoint is the endpoint for a given system variable
+const SysVarEndpoint = "/addons/xmlapi/sysvar.cgi"
+
+// SysVarListEndpoint is the endpoint for the system variables list
+const SysVarListEndpoint = "/addons/xmlapi/sysvarlist.cgi"
+
+// SysvarListResponse is a list of system variables returned by API
+type SysvarListResponse struct {
+ XMLName xml.Name `xml:"systemVariables"`
+ SysvarEntry []SysVarEntry `xml:"systemVariable"`
+}
+
+// SysVarEntry is a single system variable in SysvarListResponse
+type SysVarEntry struct {
+ XMLName xml.Name `xml:"systemVariable"`
+ Name string `xml:"Name,attr"`
+ Variable string `xml:"variable,attr"`
+ Value string `xml:"value,attr"`
+ ValueList string `xml:"value_list,attr"`
+ ValueText string `xml:"value_text,attr"`
+ IseID string `xml:"ise_id,attr"`
+ Min string `xml:"min,attr"`
+ Max string `xml:"max,attr"`
+ Unit string `xml:"unit,attr"`
+ Type string `xml:"type,attr"`
+ Subtype string `xml:"subtype,attr"`
+ Timestamp string `xml:"timestamp,attr"`
+ ValueName0 string `xml:"value_name_0,attr"`
+ ValueName1 string `xml:"value_name_1,attr"`
+}
+
+const pmTrue = "true"
+
+// SysVarIDMap is a map of system variables by ID
+var SysVarIDMap = map[string]SysVarEntry{}
+
+// String returns a string representation of the system variable list
+func (e SysVarEntry) String() string {
+ return fmt.Sprintf("%s= %s (%s), ID:%s, ts %s\n", e.Name, e.Value, e.Unit, e.IseID, FormatUnixtsString(e.Timestamp))
+}
+
+// GetSysvar returns a single system variable
+func GetSysvar(sysvarIDs []string, text bool) (result SysvarListResponse, err error) {
+ log.Debug("sysvars called")
+ var parameter = make(map[string]string)
+ if len(sysvarIDs) > 0 {
+ parameter["ise_id"] = strings.Join(sysvarIDs, ",")
+ }
+ if text {
+ parameter["text"] = pmTrue
+ }
+ err = QueryAPI(SysVarEndpoint, &result, parameter)
+ log.Debugf("getSysvars returned %d entries", len(result.SysvarEntry))
+ return
+}
+
+// GetSysvarList returns the list of system variables
+func GetSysvarList(text bool) (err error) {
+ var result SysvarListResponse
+ var parameter = make(map[string]string)
+ log.Debug("sysvarlist called")
+ if text {
+ parameter["text"] = pmTrue
+ }
+ err = QueryAPI(SysVarListEndpoint, &result, parameter)
+
+ for _, e := range result.SysvarEntry {
+ SysVarIDMap[e.IseID] = e
+ AllIds[e.IseID] = IDMapEntry{e.IseID, e.Name, "Sysvar", e}
+ }
+
+ log.Debugf("getSysvarList returned %d entries", len(result.SysvarEntry))
+ return
+}
diff --git a/hmlib/sysvar_test.go b/hmlib/sysvar_test.go
new file mode 100644
index 0000000..b2c5035
--- /dev/null
+++ b/hmlib/sysvar_test.go
@@ -0,0 +1,69 @@
+package hmlib
+
+import (
+ "net/url"
+ "testing"
+
+ "github.com/jarcoal/httpmock"
+ "github.com/stretchr/testify/assert"
+ "github.com/tommi2day/gomodules/test"
+)
+
+func TestSysvar(t *testing.T) {
+ var err error
+ test.Testinit(t)
+ httpmock.ActivateNonDefault(httpClient.GetClient())
+ defer httpmock.DeactivateAndReset()
+ hmURL = MockURL
+ hmToken = MockToken
+
+ t.Run("sysvar list", func(t *testing.T) {
+ response := SysVarListTest
+ responder := httpmock.NewStringResponder(200, response)
+ fakeURL := hmURL + SysVarListEndpoint
+ httpmock.RegisterResponder("GET", fakeURL, responder)
+
+ err = GetSysvarList(true)
+ assert.NoErrorf(t, err, "GetSysvarList should not return an error:%s", err)
+ assert.Equal(t, 7, len(SysVarIDMap), "GetSysvarList should return 7 entries")
+ s, ok := AllIds["8254"]
+ assert.True(t, ok, "GetDeviceList should contain 4740")
+ if ok && s.EntryType == "systemVariable" {
+ assert.Equal(t, "Sysvar", s.EntryType, "GetDeviceList should contain 6.000000")
+ assert.Equal(t, "DutyCycle-LGW", s.Name, "GetSysvarList 8264 Name should contain DutyCycle-LGW")
+ }
+ t.Log(s)
+ })
+
+ t.Run("sysvar list empty", func(t *testing.T) {
+ SysVarIDMap = map[string]SysVarEntry{}
+ fakeURL := hmURL + SysVarListEndpoint
+ queryVar := url.Values{
+ "text": []string{"true"},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", fakeURL, queryVar,
+ httpmock.NewStringResponder(200, SysVarEmptyTest))
+ err = GetSysvarList(true)
+ assert.NoErrorf(t, err, "GetSysvarList should not return an error:%s", err)
+ assert.Equal(t, 0, len(SysVarIDMap), "GetSysvarList should return 0 entry")
+ })
+
+ t.Run("sysvar empty", func(t *testing.T) {
+ SysVarIDMap = map[string]SysVarEntry{}
+ fakeURL := hmURL + SysVarEndpoint
+ // mock the response for state
+ queryVar := url.Values{
+ "ise_id": []string{"4711"},
+ "sid": []string{hmToken},
+ }
+ httpmock.RegisterResponderWithQuery(
+ "GET", fakeURL, queryVar,
+ httpmock.NewStringResponder(200, SysVarEmptyTest))
+ l, err := GetSysvar([]string{"4711"}, false)
+ assert.NoErrorf(t, err, "GetSysvar should not return an error:%s", err)
+ assert.NotNil(t, l, "GetSysvar should return a list")
+ assert.Equal(t, 0, len(l.SysvarEntry), "GetSysvar should return 0 entry")
+ })
+}
diff --git a/hmlib/xmlapi.go b/hmlib/xmlapi.go
new file mode 100644
index 0000000..6f2d048
--- /dev/null
+++ b/hmlib/xmlapi.go
@@ -0,0 +1,108 @@
+package hmlib
+
+import (
+ "bytes"
+ "encoding/xml"
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/go-resty/resty/v2"
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/viper"
+ "golang.org/x/net/html/charset"
+)
+
+var (
+ hmToken string
+ hmURL string
+)
+
+var httpClient *resty.Client = resty.New()
+
+// QueryAPI function for retrieving via http hmurl/endpoint and return result as xml
+func QueryAPI(endpoint string, result interface{}, parameter map[string]string) (err error) {
+ // Create a Resty Client
+ if hmToken == "" {
+ err = fmt.Errorf("no token set")
+ return
+ }
+ if hmURL == "" {
+ err = fmt.Errorf("no hmWarnThreshold server url set")
+ return
+ }
+
+ if endpoint == "" {
+ err = fmt.Errorf("no endpoint set")
+ return
+ }
+
+ callingURL := fmt.Sprintf("%s%s", hmURL, endpoint)
+ httpClient.QueryParam = url.Values{}
+ httpClient.SetHeader("Content-Type", "text/xml")
+ httpClient.SetQueryParam("sid", hmToken)
+ httpClient.SetDebug(viper.GetBool("debug"))
+ // reset query params
+
+ if len(parameter) > 0 {
+ httpClient.SetQueryParams(parameter)
+ }
+ qp := httpClient.QueryParam
+ log.Debugf("query called to %s(%v)", callingURL, qp)
+ resp, err := httpClient.R().
+ EnableTrace().
+ Get(callingURL)
+ if err != nil {
+ err = fmt.Errorf("cannot do request: %s", err)
+ return
+ }
+ if resp.StatusCode() != 200 {
+ err = fmt.Errorf("invalid status code: %d", resp.StatusCode())
+ return
+ }
+ data := resp.Body()
+ body := string(data)
+ log.Debugf("response status: %s", resp.Status())
+ log.Debugln("response body:", body)
+
+ if strings.Contains(body, "not_authenticated") {
+ err = fmt.Errorf("unauthorized, wrong token?")
+ return
+ }
+ // for iso-8859-1 use decoder.CharsetReader = charset.NewReaderLabel
+ // https://github.com/go-resty/resty/issues/481
+ decoder := xml.NewDecoder(bytes.NewReader(data))
+ decoder.CharsetReader = charset.NewReaderLabel
+ err = decoder.Decode(&result)
+ return
+}
+
+// SetHmToken sets the token for the next QueryAPI call
+func SetHmToken(token string) {
+ hmToken = token
+}
+
+// SetHmURL sets the url for the next QueryAPI call
+func SetHmURL(url string) {
+ hmURL = url
+}
+
+// GetHmToken returns the token for the next QueryAPI call
+func GetHmToken() string {
+ return hmToken
+}
+
+// GetHmURL returns the url for the next QueryAPI call
+func GetHmURL() string {
+ return hmURL
+}
+
+// GetHTTPClient returns the http client for the next QueryAPI call
+func GetHTTPClient() *resty.Client {
+ return httpClient
+}
+
+// SetHTTPClient sets the http client for the next QueryAPI call
+func SetHTTPClient(c *resty.Client) {
+ httpClient = c
+}
diff --git a/hmlib/xmlapi_test.go b/hmlib/xmlapi_test.go
new file mode 100644
index 0000000..68ccc53
--- /dev/null
+++ b/hmlib/xmlapi_test.go
@@ -0,0 +1,74 @@
+package hmlib
+
+import (
+ "encoding/xml"
+ "testing"
+
+ "github.com/jarcoal/httpmock"
+ "github.com/stretchr/testify/assert"
+ "github.com/tommi2day/gomodules/test"
+)
+
+const mockTestEndpoint = "/addons/xmlapi/test.cgi"
+const mockTestResponse = `Working200
`
+const mockInvalidEndpoint = "/addons/xmlapi/invalid.cgi"
+
+type mockTest struct {
+ XMLName xml.Name `xml:"test"`
+ Status mockStatus `xml:"status"`
+}
+type mockStatus struct {
+ XMLName xml.Name `xml:"status"`
+ Message string `xml:"message"`
+ Code string `xml:"code"`
+}
+
+func TestQueryAPI(t *testing.T) {
+ var err error
+ var result mockTest
+ test.Testinit(t)
+
+ httpmock.ActivateNonDefault(httpClient.GetClient())
+ defer httpmock.DeactivateAndReset()
+ t.Run("QueryAPI with empty token", func(t *testing.T) {
+ hmToken = ""
+ err = QueryAPI(mockTestEndpoint, &result, nil)
+ t.Log(err)
+ assert.Error(t, err, "QueryAPI should return an error")
+ })
+
+ hmToken = MockToken
+ t.Run("QueryAPI with empty endpoint", func(t *testing.T) {
+ err = QueryAPI("", &result, nil)
+ t.Log(err)
+ assert.Error(t, err, "QueryAPI should return an error")
+ })
+ t.Run("QueryAPI with empty url", func(t *testing.T) {
+ hmURL = ""
+ err = QueryAPI(mockTestEndpoint, &result, nil)
+ t.Log(err)
+ assert.Error(t, err, "QueryAPI should return an error")
+ })
+ hmURL = MockURL
+ t.Run("QueryAPI with valid endpoint", func(t *testing.T) {
+ response := mockTestResponse
+ responder := httpmock.NewStringResponder(200, response)
+ fakeURL := MockURL + mockTestEndpoint
+ httpmock.RegisterResponder("GET", fakeURL, responder)
+
+ err = QueryAPI(mockTestEndpoint, &result, nil)
+ t.Log(result)
+ assert.NoErrorf(t, err, "QueryAPI should not return an error:%s", err)
+ assert.Equal(t, "Working", result.Status.Message, "QueryAPI should return a valid result")
+ })
+ t.Run("QueryAPI with invalid endpoint", func(t *testing.T) {
+ response := ""
+ responder := httpmock.NewStringResponder(404, response)
+ fakeURL := MockURL + mockInvalidEndpoint
+ httpmock.RegisterResponder("GET", fakeURL, responder)
+ err = QueryAPI(mockInvalidEndpoint, &result, nil)
+ t.Log(err)
+ assert.Error(t, err, "QueryAPI should return an error")
+ assert.Contains(t, err.Error(), "invalid status code: 404", "QueryAPI should return an error")
+ })
+}
diff --git a/pwlib/vault_docker_test.go b/pwlib/vault_docker_test.go
index 14d5662..14321ff 100644
--- a/pwlib/vault_docker_test.go
+++ b/pwlib/vault_docker_test.go
@@ -2,11 +2,12 @@ package pwlib
import (
"fmt"
- "github.com/tommi2day/gomodules/test"
"net/http"
"os"
"time"
+ "github.com/tommi2day/gomodules/test"
+
"github.com/tommi2day/gomodules/common"
"github.com/ory/dockertest/v3"