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"