├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── docker ├── ca-certificates.crt ├── linux │ └── Dockerfile └── rpi │ └── Dockerfile ├── etc ├── default │ └── zwaymqtt └── systemd │ └── system │ └── zwaymqtt.service ├── readme.md └── zwaymqtt.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # generated exe 3 | *.exe 4 | # generated linux binary 5 | zwaymqtt 6 | # generated release 7 | releases 8 | # generated zip 9 | *.zip 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - tip 4 | sudo: required 5 | env: 6 | global: 7 | - secure: kdfOrgrJ+3oIloEsqOAuRwKY4m/ql27Qsy3UrKAxs1Kkr8pt6RdLI5Nop0WWDy0gEe5OLLQXVOmEZN/j23d2T7gK3KbYrVzVZHviPZJ0QAdB1jZBAtS5m9Inib7H349ZiZMpOG6nupWODdvzjJq/PYiAlGclNfXPZvokKWImTGTgQwnzEZVJ36rpX4kVd4dFhRssCI29HM+nOpt5ub8U4M4jKQAYSGEB+AC6tQdoe58rD9SuFcvXHENJQ/FSE9YCi94uDChbYeK1nCz55ddnODdl209a+fuzOtlqBh+pZ9l1FAFaO4ug5zA0hbIARES/utBzDTueClwx2uSBMcfrR3JJpVZJ5kLWozqUEC3AjKgyVDe2Y0OiXcQItx2Niaon6QA3igc8UQGYgUA9g2WtAxduhqo9A/fObZ3tvgB8cXjQN/UNtfROgKaunldGlLOiSRUMu8Dq/6ZoHBt9U1y+dQ5BbEheSwcdJTEnwNxyPXn8x0Dk2D+PY8FO//czXWaHmQaXDBLEHRIczmDv3NDqGRVax1j6+uVcXnc7LOCmc68cxMECDZQdNYzAJFdsD0DXpPJkCIwrfxQjHrsW169kBbGT8PI5y/LeEyyhVrrO4WtCz3cSH4xvf+FE7t0JjU+imySu2USpeYmY5MxF3LBAPUPOA2a93VUBpz8vWstpSVQ= 8 | - secure: ngOeyJvRriJhR1XUj5hcS508AeuYN+xaBRvBgL/7hcGWRpdLOi9Uh4i2jsqhGdxca3ErSwLuLxTrJTpjGn4FtzdgAtW4e2wlsgId0Y3RQiX1ZPBV3nbxCKZ0DMczO8GJycp+ZK4TJExhDbsKDBPjD7mh+k4Ah9EcT8J7xstE5CR09oIzhX0VqRAgKvRIXncyhMbC5fKBN9Z2FEhb7HsHv46u7KbXN2fzyl4CvOMX0CsukjtvxQzEQ+WB7l2hGtg9jhyMCNDxWIHc0SQa8NVOPKKVvDfj3cdE4WEZOmzgie8bahO18wTr7I8Hyzf6KzVl2fGzeoa/tG41gbr9CdiN8J5IJKc0W7W5lFGnfnYuXnQNdM3S3lqO9ATV6fbLOlDnXKTwJGycLUJBtaX1uTGfD1ENl2PEL16WUrxaVjBS3vqoeQ6ToQngBXY809j5pyEjX+2am77Zr2bYTGme4VL1O0QVDspA7Nx+J2YNNAVtxfmqEORIjhflO8xu7jXckrG1p18duwjc+975YrCF+dHXKe47sw16auMt7YLu1W1yHKt71+Mpo5/Hrl5+ZC74MOeTpxjLhQy+8jI4s0+SqPtDBMhR1Y8Na+a4fgihI2iYAQw6rLnh9pTFXfoLRYMwfEgz2krvNg+zY7KtMbBDr82o8CMBHp9OQmi12ft/npuF4oQ= 9 | - secure: bgQnUHuTEmm3wL7UImuOOAk+ox5wGu94rJQ8zLEJOxKtLpJWI0p0pHRJqgeh/cPOO/GvtXy++aDwSgXobJO+8WyaM6+oZ394cSOQlC2Jn4ewup4PpOZFqxEFeKU9fWIp6GfyrUpYufh6DIgL4hufmeuUF91tDDrKtfhgLX8M+UbFztdPtzU2qvfmc/3pT8kDpqRzZahtMYjuN9TZ8xAG1rNDWbYokiBhHxK5P1/5qJqWE5tXZ511H4lGQJzFX6aaMqn7fpIj8hDpeaDWNoko2T9FSTrIUKweRtXvJUj2vzJXjxSX0MPoLhLnsE9q8dlOG2k3sd2Yxe2NYy1y6bWGEN66ScA/fzyKYjApr+A1IEUmML7g9i36wzQsdV5IWhgiuPwtRh6+EDGIeTg3jK4R7XrVeN/ppsz0Bdjx0vtCDH7vyMsRkGF1vtj3N4vKlmxcPBRRh63zex/c45d+MLvTO84vIh3YTd2LLY0Jo5K7wO7oczAh06wz3yNtF6m2Tawf3FtYXet6UUoQONxrdRUD1Rm/Woek/SeUb3ip0dSpb2mOa1hw2A/irE6GwSlTgLwYyfZAcCSb+MEaUOPyEpuSeILq1/o9YgtSbgXlh2VD31gptanN6tZ8zZtk0P14USdSEmsLuNB5g0NCI4VZr//xU74Nq2kqdf30Z3hxEGhl7DE= 10 | - COMMIT=${TRAVIS_COMMIT::8} 11 | services: 12 | - docker 13 | install: 14 | - make deps 15 | script: 16 | - make all 17 | deploy: 18 | provider: releases 19 | api_key: 20 | secure: EIFGuItbdzBxaI/Tu05MMVE1/ct5nCossfveSc4qH1I+UqXWzxh6S9w5SsNw1VUlQOByp5qfMf+Ihbvzf2xc0qEhaJZh/YiERHug8E8IWRAC2xdFOcwZyax9TjjUM7xiWJr4CTEGwikmcTtdUMyvA/Ur3m5184uR2Nm+50dgcq+dSprFDbJuFl3Pc/W6gHkSprE09k/8spAkpK2p0CmD5S6Bb4vMrGON9FiD8I4yRqqpt8lpDDnSbjy/y40ie26m471+pmAWHfa9ohGrmHQcaP6yuXC9A6wlmTvyQeO8IYZqVJyyatOHQZMUbBUDCxtTwcaCgmumq7kb4ECbKnrRHyu+J+YpPxot8iNaEmSCg+P5pogSdmQRvsl4XjV5fyzfSbfYOygO7z6JSZgLkIPquptQMdpyQ6ACFUc+CTkH7bf2sBltSJHoPevgSII2CFfeGSjcq8wEXEwKh6uJrF+pyKPsgXWBPqZ+Xy4P8jyYfCW/FWc2Wjg+Ue92ai/YNwvsobNzktqyfkivwFikJHhhnub2gNvm2AhLmKuQh4j9zZEyCUyjceedmj9cCERXEFH50MjAqBO/PstDk1TTOupOepd+dCJ8t3zLnAhg1bxQkn/ISxI553tltKewJfp1jTkdbV7EBeymhqQ4rLYY3/w89amsYo+ACX05IJy0Y1Ehu/w= 21 | file: 22 | - releases/zwaymqtt_darwin_amd64.tgz 23 | - releases/zwaymqtt_linux_amd64.tgz 24 | - releases/zwaymqtt_linux_arm.tgz 25 | - releases/zwaymqtt_windows_amd64.tgz 26 | skip_cleanup: true 27 | on: 28 | tags: true 29 | after_success: 30 | - echo branch $TRAVIS_BRANCH / tag $TRAVIS_TAG / commit $COMMIT 31 | - docker login -e $DOCKER_EMAIL -u $DOCKER_USERNAME -p $DOCKER_PASSWORD 32 | - cp releases/linux/amd64/zwaymqtt docker/linux/ 33 | - cp docker/ca-certificates.crt docker/linux/ 34 | - docker build -f docker/linux/Dockerfile -t $DOCKER_USERNAME/zwaymqtt docker/linux/ 35 | - docker tag -f $DOCKER_USERNAME/zwaymqtt $DOCKER_USERNAME/zwaymqtt:$COMMIT 36 | - docker tag -f $DOCKER_USERNAME/zwaymqtt $DOCKER_USERNAME/zwaymqtt:$TRAVIS_BRANCH 37 | - if [ "$TRAVIS_TAG" != "" ]; then docker tag -f $DOCKER_USERNAME/zwaymqtt $DOCKER_USERNAME/zwaymqtt:$TRAVIS_TAG; fi 38 | - if [ "$TRAVIS_TAG" != "" ]; then docker tag -f $DOCKER_USERNAME/zwaymqtt $DOCKER_USERNAME/zwaymqtt:stable; fi 39 | - docker push $DOCKER_USERNAME/zwaymqtt 40 | - cp releases/linux/arm/zwaymqtt docker/rpi/ 41 | - cp docker/ca-certificates.crt docker/rpi/ 42 | - docker build -f docker/rpi/Dockerfile -t $DOCKER_USERNAME/rpi-zwaymqtt docker/rpi/ 43 | - docker tag -f $DOCKER_USERNAME/rpi-zwaymqtt $DOCKER_USERNAME/rpi-zwaymqtt:$COMMIT 44 | - docker tag -f $DOCKER_USERNAME/rpi-zwaymqtt $DOCKER_USERNAME/rpi-zwaymqtt:$TRAVIS_BRANCH 45 | - if [ "$TRAVIS_TAG" != "" ]; then docker tag -f $DOCKER_USERNAME/rpi-zwaymqtt $DOCKER_USERNAME/rpi-zwaymqtt:$TRAVIS_TAG; fi 46 | - if [ "$TRAVIS_TAG" != "" ]; then docker tag -f $DOCKER_USERNAME/rpi-zwaymqtt $DOCKER_USERNAME/rpi-zwaymqtt:stable; fi 47 | - docker push $DOCKER_USERNAME/rpi-zwaymqtt 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Blomart Cédric 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOVERSION=$(shell go version) 2 | GOOS=$(word 1,$(subst /, ,$(lastword $(GOVERSION)))) 3 | GOARCH=$(word 2,$(subst /, ,$(lastword $(GOVERSION)))) 4 | RELEASE_DIR=releases 5 | SRC_FILES=$(wildcard *.go) 6 | BUILD_FLAGS=-ldflags '-s -w -extldflags "-static"' -a 7 | 8 | deps: 9 | go get git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git 10 | go get github.com/davecheney/profile 11 | 12 | build-windows-amd64: 13 | @$(MAKE) build GOOS=windows GOARCH=amd64 CGO_ENABLED=0 SUFFIX=.exe 14 | 15 | dist-windows-amd64: 16 | @$(MAKE) dist GOOS=windows GOARCH=amd64 SUFFIX=.exe 17 | 18 | build-linux-amd64: 19 | @$(MAKE) build GOOS=linux GOARCH=amd64 CGO_ENABLED=0 20 | 21 | dist-linux-amd64: 22 | @$(MAKE) dist GOOS=linux GOARCH=amd64 23 | 24 | build-darwin-amd64: 25 | @$(MAKE) build GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 26 | 27 | dist-darwin-amd64: 28 | @$(MAKE) dist GOOS=darwin GOARCH=amd64 29 | 30 | build-linux-arm: 31 | @$(MAKE) build GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=0 32 | 33 | dist-linux-arm: 34 | @$(MAKE) dist GOOS=linux GOARCH=arm GOARM=5 35 | 36 | $(RELEASE_DIR)/$(GOOS)/$(GOARCH)/zwaymqtt$(SUFFIX): $(SRC_FILES) 37 | go build $(BUILD_FLAGS) -o $(RELEASE_DIR)/$(GOOS)/$(GOARCH)/zwaymqtt$(SUFFIX) . 38 | 39 | $(RELEASE_DIR)/zwaymqtt_$(GOOS)_$(GOARCH).tgz: $(RELEASE_DIR)/$(GOOS)/$(GOARCH)/zwaymqtt$(SUFFIX) 40 | cd $(RELEASE_DIR)/$(GOOS)/$(GOARCH); tar czf ../../zwaymqtt_$(GOOS)_$(GOARCH).tgz ./zwaymqtt$(SUFFIX) 41 | 42 | dist: $(RELEASE_DIR)/zwaymqtt_$(GOOS)_$(GOARCH).tgz 43 | 44 | build: $(RELEASE_DIR)/$(GOOS)/$(GOARCH)/zwaymqtt$(SUFFIX) 45 | 46 | clean: 47 | rm -rf $(RELEASE_DIR) 48 | 49 | all: 50 | @$(MAKE) dist-windows-amd64 51 | @$(MAKE) dist-linux-amd64 52 | @$(MAKE) dist-darwin-amd64 53 | @$(MAKE) dist-linux-arm 54 | -------------------------------------------------------------------------------- /docker/linux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | MAINTAINER cblomart@gmail.com 3 | COPY ./zwaymqtt /zwaymqtt 4 | COPY ./ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 5 | ENTRYPOINT ["/zwaymqtt"] -------------------------------------------------------------------------------- /docker/rpi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | MAINTAINER cblomart@gmail.com 3 | COPY ./zwaymqtt /zwaymqtt 4 | COPY ./ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 5 | ENTRYPOINT ["/zwaymqtt"] -------------------------------------------------------------------------------- /etc/default/zwaymqtt: -------------------------------------------------------------------------------- 1 | topic_root=nivelles 2 | mqtt_server=localhost 3 | zway_server=localhost 4 | zway_user=admin 5 | zway_pass=echelle 6 | refresh=3 7 | -------------------------------------------------------------------------------- /etc/systemd/system/zwaymqtt.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Z-Way to MQTT 3 | Requires=network.target 4 | 5 | [Service] 6 | EnvironmentFile=-/etc/default/zwaymqtt 7 | ExecStart=/usr/local/bin/zwaymqtt -h $topic_root -m $mqtt_server -p $zway_pass -u $zway_user -s $zway_server -r $refresh 8 | #Restart=always 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Z-Way to MQTT 3 | 4 | A simple Z-Way to MQTT bridge in GO. 5 | 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/cblomart/zwaymqtt)](https://goreportcard.com/report/github.com/cblomart/zwaymqtt) 7 | 8 | [![Build Status](https://drone.io/github.com/cblomart/zwaymqtt/status.png)](https://drone.io/github.com/cblomart/zwaymqtt/latest) 9 | 10 | [![Build Status](https://travis-ci.org/cblomart/zwaymqtt.svg?branch=master)](https://travis-ci.org/cblomart/zwaymqtt) 11 | 12 | [Latest binaries](https://drone.io/github.com/cblomart/zwaymqtt/files) 13 | 14 | The service sends MQTT message for all handled classes to the a MQTT broker. 15 | 16 | Classes can be handled either read only (ro), typicaly for sensors, or read only (ro) ,typicaly for switches. 17 | 18 | There is a base subject for MQTT (root topic; per default "razberry"). 19 | 20 | The nodename will be read form the Z-Way node. 21 | 22 | In principle nodes will be split between actuators and sensors and between binray and analogic ones. 23 | 24 | The instance is always used. (i) 25 | 26 | Currently the bridge is limited to certain ZWave classes (see bellow). 27 | 28 | # Usage 29 | 30 | > Usage of zwaymqtt: 31 | > 32 | > -h string 33 | > 34 | > mqtt topic root or ZWAY_HOME environment variable (default "razberry") 35 | > 36 | > -m string 37 | > 38 | > MQTT server or MQTT_SERVER environment variable (default "localhost:1883") 39 | > 40 | > -mp string 41 | > 42 | > MQTT password or MQTT_PASSWORD environment variable 43 | > 44 | > -mu string 45 | > 46 | > MQTT username or MQTT_USERNAME environment variable 47 | > 48 | > -p string 49 | > 50 | > Z-Way passsword or ZWAY_PASSWORD environment variable 51 | > 52 | > -profile string 53 | > 54 | > Profile execution (cpu/mem/all) 55 | > 56 | > -proto string 57 | > 58 | > MQTT protocol tcp/ws/tls MQTT protocol tcp/ws/tls or MQTT_PROTOCOL environment variable (default "tcp") 59 | > 60 | > -r int 61 | > 62 | > Z-Way refresh rate in seconds or ZWAY_REFRESH environment variable (default 30) 63 | > 64 | > -s string 65 | > 66 | > Z-Way server name or ZWAY_SERVER environment variable (default "localhost:8083") 67 | > 68 | > -u string 69 | > 70 | > Z-Way username or ZWAY_USERNAME environment variable (default "admin") 71 | > 72 | > -v Show debug messages 73 | 74 | notice that main parameters are mapped by an environment variable 75 | 76 | # Docker 77 | 78 | All builds are pushed to docker: 79 | - [cblomart/zwaymqtt](https://hub.docker.com/r/cblomart/zwaymqtt/) 80 | - [cblomart/rpi-zwaymqtt](https://hub.docker.com/r/cblomart/rpi-zwaymqtt/) 81 | 82 | Default tags includes: 83 | - branch (i.e.: master) for latest commit in the branch 84 | - latest for latest release 85 | 86 | Please use environment variables to configure: 87 | - MQTT topic root = ZWAY_HOME 88 | - MQTT server = MQTT_SERVER 89 | - MQTT password = MQTT_PASSWORD 90 | - MQTT username = MQTT_USERNAME 91 | - MQTT protocol = MQTT_PROTOCOL 92 | - Z-Way server name = ZWAY_SERVER 93 | - Z-Way username = ZWAY_USERNAME 94 | - Z-Way password = ZWAY_PASSWORD 95 | - Z-Way refresh rate = ZWAY_REFRESH 96 | - Show debug messagees = ZWAYMQTT_DEBUG (set to 'true') 97 | 98 | ## Example 99 | 100 | Setup an environment file with the required parametes 101 | 102 | **'/etc/default/zwaymqtt'** 103 | 104 | > ZWAY_HOME = zway 105 | > 106 | > ZWAY_SERVER = localhost 107 | > 108 | > ZWAY_USERNAME = admin 109 | > 110 | > ZWAY_PASSWORD = admin 111 | > 112 | > ZWAY_REFRESH = 3 113 | > 114 | > MQTT_PROTOCOL = tls 115 | > 116 | > MQTT_SERVER = localhost 117 | > 118 | > MQTT_USERNAME = mqtt 119 | > 120 | > MQTT_PASSWORD = mqtt 121 | 122 | Run the docker image 123 | 124 | > docker run --env-file:/ext/default/zwaymqtt cblomart/zwaymqtt 125 | 126 | ## Instalation 127 | 128 | ### From release 129 | 130 | On your pi: 131 | 132 | > $ wget https://github.com/cblomart/zwaymqtt/releases/download/0.1/zwaymqtt-linux-arm5.tgz 133 | > 134 | > sudo tar -zxvf ./zwaymqtt.tgz -C / 135 | > 136 | > sudo vi /etc/default/zwaymqtt 137 | > 138 | > sudo systemctl enable zwaymqtt 139 | > 140 | > sudo systemctl start zwaymqtt 141 | 142 | ### from sources 143 | 144 | I install the software at home with the RaZberry. 145 | 146 | To do this: 147 | 148 | - be sure to have GO and ability to cross compile. (If you do not, you can alway run it with go on a pc) 149 | 150 | - compile for an arm running linux (raspberry) on a pc: 151 | 152 | > $ GOOS=linux GOARCH=arm GOARM=5 go get github.com/cblomart/zwaymqtt 153 | 154 | - copy the necessary files to your Pi: 155 | 156 | > $ scp $GOPATH/src/github.com/cblomart/zwaymqtt/etc/systemd/system/zwaymqtt.server pi@raspberry.local:/tmp/ 157 | > 158 | > $ scp $GOPATH/src/github.com/cblomart/zwaymqtt/etc/default/zwaymqtt pi@raspberry.local:/tmp/ 159 | > 160 | > $ scp $GOPATH/bin/linux_arm/zwaymqtt pi@raspberry.local:/tmp/zwaymqtt.bin 161 | 162 | - on your pi, place the files at the right places: 163 | 164 | > $ sudo cp /tmp/zwaymqtt.server /etc/systemd/system/ 165 | > 166 | > $ sudo cp /tmp/zwaymqtt /etc/default/ 167 | > 168 | > $ sudo cp /tmp/zwaymqtt.bin /usr/local/bin/zwaymqtt 169 | > 170 | 171 | - on your pi, edit the /etc/default/zwaymqtt to match your preferences 172 | 173 | - on your pi, enable and start the service: 174 | 175 | > $ sudo systemctl enable zwaymqtt 176 | > 177 | > $ sudo systemctl start zwaymqtt 178 | 179 | 180 | 181 | 182 | 183 | ## Zwave Classes 184 | 185 | ### BATTERY 186 | 187 | Encompassed classes: 188 | 189 | - COMMAND\_CLASS\_BATTERY (ro) 190 | 191 | The events on this class will be mapped to the "\/sensors/analogic/\/\/battery" topic. 192 | 193 | i.e.: razberry/sensors/binary/detector_door_basement/0/general_purpose 194 | 195 | ### SWITCH 196 | 197 | Encompassed classes: 198 | 199 | - COMMAND\_CLASS\_SWITCH\_BINARY (rw) 200 | 201 | The events on this class will be mapped to the "\/actuators/binary/\/\/switch" topic. 202 | 203 | i.e.: razberry/actuators/binary/binary_switch_living/1/switch 204 | 205 | ### MULTILEVEL SWITCH 206 | 207 | Encompassed classes: 208 | 209 | - COMMAND\_CLASS\_SWITCH\_MULTILEVEL (rw) 210 | 211 | The events on this class will be mapped to the "\/actuators/analogic/\/\/dimmer" topic. 212 | 213 | **TODO**: put an example (i have no multilevel switch to test) 214 | 215 | ### BINARY SENSOR 216 | 217 | Encompassed classes: 218 | 219 | - COMMAND\_CLASS\_SENSOR\_BINARY (ro) 220 | 221 | The utility will be determined by the sensor type described on the node. If it is a generic sensor... "generic" will be used. 222 | 223 | The events on this class will be mapped to the "\/sensors/binary/\/\/\" topic. 224 | 225 | i.e.: razberry/sensors/binary/detector_entry/0/motion 226 | 227 | ### MULTILEVEL SENSOR 228 | 229 | Encompassed classes: 230 | 231 | - COMMAND\_CLASS\_SENSOR\_MULTILEVEL (ro) 232 | - COMMAND\_CLASS\_METER (ro) 233 | 234 | The utility is still used but the scale type has been added. 235 | 236 | The events on this class will be mapped to the "\/sensors/binary/\/\/\/\" topic. 237 | 238 | i.e.: razberry/sensors/analogic/detector_entry/0/temperature/c 239 | 240 | ### Thermostat 241 | 242 | Encompassed classes: 243 | 244 | - COMMAND\_CLASS\_THERMOSTAT\_SET\_POINT (rw) 245 | 246 | Todo: more desctiption ;-) 247 | 248 | # License 249 | 250 | The MIT License (MIT) 251 | 252 | Copyright (c) 2016 cblomart 253 | 254 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 255 | 256 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 257 | 258 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 259 | -------------------------------------------------------------------------------- /zwaymqtt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "log" 6 | "flag" 7 | "fmt" 8 | "time" 9 | "strings" 10 | "regexp" 11 | "errors" 12 | "strconv" 13 | "encoding/json" 14 | "net/http" 15 | "io/ioutil" 16 | 17 | MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" 18 | pfl "github.com/davecheney/profile" 19 | ) 20 | 21 | type MqttUpdate struct { 22 | Topic string 23 | Value string 24 | } 25 | 26 | type Gateway struct { 27 | Key string 28 | Topic string 29 | Value string 30 | Write bool 31 | Type string 32 | } 33 | 34 | //command line variable 35 | var zway_server string 36 | var zway_username string 37 | var zway_password string 38 | var zway_home string 39 | var zway_refresh int 40 | var mqtt_server string 41 | var mqtt_username string 42 | var mqtt_password string 43 | var mqtt_protocol string 44 | var debug bool 45 | var profile string 46 | 47 | //used variables 48 | var zway_timestamp int = 0 49 | var zway_dataapi = "/ZWaveAPI/Data/" 50 | var zway_zautoapi = "/ZAutomation/api/v1/" 51 | var zway_runapi = "/ZWaveAPI/Run/" 52 | var zway_cookiename = "ZWAYSession" 53 | var http_client = new(http.Client) 54 | var zway_cookie = new(http.Cookie) 55 | var gateways []Gateway 56 | var zway_retries int = 0 57 | 58 | 59 | //ZWay enumerations 60 | const ( 61 | BASIC_TYPE_CONTROLER = 1 62 | BASIC_TYPE_STATIC_CONTROLER = 2 63 | BASIC_TYPE_SLAVE = 3 64 | BASIC_TYPE_ROUTING_SLAVE = 4 65 | GENERIC_TYPE_THERMOSTAT = 8 66 | GENERIC_TYPE_BINARY_SWITCH = 16 67 | GENERIC_TYPE_MULTILEVEL_SWITCH = 17 68 | GENERIC_TYPE_SWITCH_REMOTE = 18 69 | GENERIC_TYPE_SWITCH_TOGGLE = 19 70 | GENERIC_TYPE_SECURITY_PANEL = 23 71 | GENERIC_TYPE_BINARY_SENSOR = 32 72 | GENERIC_TYPE_MULTILEVEL_SENSOR = 33 73 | GENERIC_TYPE_METER = 49 74 | GENERIC_TYPE_ENTRY_CONTROL = 64 75 | COMMAND_CLASS_NO_OPERATION = 0 76 | COMMAND_CLASS_BASIC = 32 77 | COMMAND_CLASS_CONTROLLER_REPLICATION = 33 78 | COMMAND_CLASS_APPLICATION_STATUS = 34 79 | COMMAND_CLASS_ZIP_SERVICES = 35 80 | COMMAND_CLASS_ZIP_SERVER = 36 81 | COMMAND_CLASS_SWITCH_BINARY = 37 82 | COMMAND_CLASS_SWITCH_MULTILEVEL = 38 83 | COMMAND_CLASS_SWITCH_ALL = 39 84 | COMMAND_CLASS_SWITCH_TOGGLE_BINARY = 40 85 | COMMAND_CLASS_SWITCH_TOGGLE_MULTILEVEL = 41 86 | COMMAND_CLASS_CHIMNEY_FAN = 42 87 | COMMAND_CLASS_SCENE_ACTIVATION = 43 88 | COMMAND_CLASS_SCENE_ACTUATOR_CONF = 44 89 | COMMAND_CLASS_SCENE_CONTROLLER_CONF = 45 90 | COMMAND_CLASS_ZIP_CLIENT = 46 91 | COMMAND_CLASS_ZIP_ADV_SERVICES = 47 92 | COMMAND_CLASS_SENSOR_BINARY = 48 93 | COMMAND_CLASS_SENSOR_MULTILEVEL = 49 94 | COMMAND_CLASS_METER = 50 95 | COMMAND_CLASS_ZIP_ADV_SERVER = 51 96 | COMMAND_CLASS_ZIP_ADV_CLIENT = 52 97 | COMMAND_CLASS_METER_PULSE = 53 98 | COMMAND_CLASS_THERMOSTAT_HEATING = 56 99 | COMMAND_CLASS_METER_TABLE_CONFIG = 60 100 | COMMAND_CLASS_METER_TABLE_MONITOR = 61 101 | COMMAND_CLASS_METER_TABLE_PUSH = 62 102 | COMMAND_CLASS_THERMOSTAT_MODE = 64 103 | COMMAND_CLASS_THERMOSTAT_OPERATING_STATE = 66 104 | COMMAND_CLASS_THERMOSTAT_SET_POINT = 67 105 | COMMAND_CLASS_THERMOSTAT_FAN_MODE = 68 106 | COMMAND_CLASS_THERMOSTAT_FAN_STATE = 69 107 | COMMAND_CLASS_CLIMATE_CONTROL_SCHEDULE = 70 108 | COMMAND_CLASS_THERMOSTAT_SETBACK = 71 109 | COMMAND_CLASS_DOOR_LOCK_LOGGING = 76 110 | COMMAND_CLASS_SCHEDULE_ENTRY_LOCK = 78 111 | COMMAND_CLASS_BASIC_WINDOW_COVERING = 80 112 | COMMAND_CLASS_MTP_WINDOW_COVERING = 81 113 | COMMAND_CLASS_SCHEDULE = 83 114 | COMMAND_CLASS_CRC_16_ENCAP = 86 115 | COMMAND_CLASS_ASSOCIATION_GROUP_INFO = 89 116 | COMMAND_CLASS_DEVICE_RESET_LOCALLY = 90 117 | COMMAND_CLASS_CENTRAL_SCENE = 91 118 | COMMAND_CLASS_IP_ASSOCIATION = 92 119 | COMMAND_CLASS_ANTITHEFT = 93 120 | COMMAND_CLASS_ZWAVEPLUS_INFO = 94 121 | COMMAND_CLASS_MULTI_INSTANCE = 96 122 | COMMAND_CLASS_DOOR_LOCK = 98 123 | COMMAND_CLASS_USER_CODE = 99 124 | COMMAND_CLASS_BARRIER_OPERATOR = 102 125 | COMMAND_CLASS_CONFIGURATION = 112 126 | COMMAND_CLASS_ALARM = 113 127 | COMMAND_CLASS_MANUFACTURER_SPECIFIC = 114 128 | COMMAND_CLASS_POWER_LEVEL = 115 129 | COMMAND_CLASS_PROTECTION = 117 130 | COMMAND_CLASS_LOCK = 118 131 | COMMAND_CLASS_NODE_NAMING = 119 132 | COMMAND_CLASS_FIRMWARE_UPDATE = 122 133 | COMMAND_CLASS_GROUPING_NAME = 123 134 | COMMAND_CLASS_REMOTE_ASSOCIATION_ACTIVATE = 124 135 | COMMAND_CLASS_REMOTE_ASSOCIATION = 125 136 | COMMAND_CLASS_BATTERY = 128 137 | COMMAND_CLASS_CLOCK = 129 138 | COMMAND_CLASS_HAIL = 130 139 | COMMAND_CLASS_WAKEUP = 132 140 | COMMAND_CLASS_ASSOCIATION = 133 141 | COMMAND_CLASS_VERSION = 134 142 | COMMAND_CLASS_INDICATOR = 135 143 | COMMAND_CLASS_PROPRIETRAY = 136 144 | COMMAND_CLASS_LANGUAGE = 137 145 | COMMAND_CLASS_TIME = 138 146 | COMMAND_CLASS_TIME_PARAMETERS = 139 147 | COMMAND_CLASS_GEOGRAPHIC_LOCATION = 140 148 | COMMAND_CLASS_COMPOSITE = 141 149 | COMMAND_CLASS_MULTICHANNEL_ASSOCIATION = 142 150 | COMMAND_CLASS_MULTI_CMD = 143 151 | COMMAND_CLASS_ENERGY_PRODUCTION = 144 152 | COMMAND_CLASS_MANUFACTURER_PROPRIETRATY = 145 153 | COMMAND_CLASS_SCREEN_MD = 146 154 | COMMAND_CLASS_SCREEN_ATTRIBUTES = 147 155 | COMMAND_CLASS_SIMPLE_AV_CONTROL = 148 156 | COMMAND_CLASS_AV_CONTENT_DIRECTORY_MD = 149 157 | COMMAND_CLASS_RENDERER_STATUS = 150 158 | COMMAND_CLASS_AV_CONTENT_SEARCH_MD = 151 159 | COMMAND_CLASS_SECURITY = 152 160 | COMMAND_CLASS_AV_TAGGING_MD = 153 161 | COMMAND_CLASS_IP_CONFIGURATION = 154 162 | COMMAND_CLASS_ASSOCIATION_COMMAND_CONFIGURATION = 155 163 | COMMAND_CLASS_ALARM_SENSOR = 156 164 | COMMAND_CLASS_SILENCE_ALARM = 157 165 | COMMAND_CLASS_SENSOR_CONFIGURATION = 158 166 | COMMAND_CLASS_MARK = 239 167 | COMMAND_CLASS_NON_INEROPERABLE = 240 168 | ) 169 | 170 | var ZWaveClassNames = [...]string{ 171 | COMMAND_CLASS_NO_OPERATION: "command no operation", 172 | COMMAND_CLASS_BASIC: "command basic", 173 | COMMAND_CLASS_CONTROLLER_REPLICATION: "command controler replication", 174 | COMMAND_CLASS_APPLICATION_STATUS: "command application status", 175 | COMMAND_CLASS_ZIP_SERVICES: "command zip services", 176 | COMMAND_CLASS_ZIP_SERVER: "command zip server", 177 | COMMAND_CLASS_SWITCH_BINARY: "command switch binary", 178 | COMMAND_CLASS_SWITCH_MULTILEVEL: "command switch multilevel", 179 | COMMAND_CLASS_SWITCH_ALL: "commad switch all", 180 | COMMAND_CLASS_SWITCH_TOGGLE_BINARY: "command switch toggle binary", 181 | COMMAND_CLASS_SWITCH_TOGGLE_MULTILEVEL: "command switch toggle multilevel", 182 | COMMAND_CLASS_CHIMNEY_FAN: "command chimney fan", 183 | COMMAND_CLASS_SCENE_ACTIVATION: "command scene activation", 184 | COMMAND_CLASS_SCENE_ACTUATOR_CONF: "command scene actuator configuration", 185 | COMMAND_CLASS_SCENE_CONTROLLER_CONF: "command scene controler configuration", 186 | COMMAND_CLASS_ZIP_CLIENT: "command zip client", 187 | COMMAND_CLASS_ZIP_ADV_SERVICES: "command zip adv services", 188 | COMMAND_CLASS_SENSOR_BINARY: "command sensor binary", 189 | COMMAND_CLASS_SENSOR_MULTILEVEL: "command sensor multilevel", 190 | COMMAND_CLASS_METER: "command meter", 191 | COMMAND_CLASS_ZIP_ADV_SERVER: "command zip adv server", 192 | COMMAND_CLASS_ZIP_ADV_CLIENT: "command zip adv client", 193 | COMMAND_CLASS_METER_PULSE: "command meter pulse", 194 | COMMAND_CLASS_THERMOSTAT_HEATING: "command thermostat heating", 195 | COMMAND_CLASS_METER_TABLE_CONFIG: "command meter table config", 196 | COMMAND_CLASS_METER_TABLE_MONITOR: "command meter table monitor", 197 | COMMAND_CLASS_METER_TABLE_PUSH: "command meter table push", 198 | COMMAND_CLASS_THERMOSTAT_MODE: "command thermostat mode", 199 | COMMAND_CLASS_THERMOSTAT_OPERATING_STATE: "command thermostat operationg state", 200 | COMMAND_CLASS_THERMOSTAT_SET_POINT: "command thermostat set point", 201 | COMMAND_CLASS_THERMOSTAT_FAN_MODE: "command thermostat fan mode", 202 | COMMAND_CLASS_THERMOSTAT_FAN_STATE: "command thermostat fan state", 203 | COMMAND_CLASS_CLIMATE_CONTROL_SCHEDULE: "command climate control schedule", 204 | COMMAND_CLASS_THERMOSTAT_SETBACK: "command thermostat setback", 205 | COMMAND_CLASS_DOOR_LOCK_LOGGING: "command door lock logging", 206 | COMMAND_CLASS_SCHEDULE_ENTRY_LOCK: "command schedule entry lock", 207 | COMMAND_CLASS_BASIC_WINDOW_COVERING: "command basic window covering", 208 | COMMAND_CLASS_MTP_WINDOW_COVERING: "command mtp window covering", 209 | COMMAND_CLASS_SCHEDULE: "command shedule", 210 | COMMAND_CLASS_CRC_16_ENCAP: "command crc 16 encap", 211 | COMMAND_CLASS_ASSOCIATION_GROUP_INFO: "command association group info", 212 | COMMAND_CLASS_DEVICE_RESET_LOCALLY: "command device reset locally", 213 | COMMAND_CLASS_CENTRAL_SCENE: "command central scene", 214 | COMMAND_CLASS_IP_ASSOCIATION: "command ip association", 215 | COMMAND_CLASS_ANTITHEFT: "command antitheft", 216 | COMMAND_CLASS_ZWAVEPLUS_INFO: "command zwaveplus info", 217 | COMMAND_CLASS_MULTI_INSTANCE: "command multi instance", 218 | COMMAND_CLASS_DOOR_LOCK: "command door lock", 219 | COMMAND_CLASS_USER_CODE: "command user code", 220 | COMMAND_CLASS_BARRIER_OPERATOR: "command barrier operator", 221 | COMMAND_CLASS_CONFIGURATION: "command configuration", 222 | COMMAND_CLASS_ALARM: "command alarm", 223 | COMMAND_CLASS_MANUFACTURER_SPECIFIC: "commad manufacturer specific", 224 | COMMAND_CLASS_POWER_LEVEL: "command power level", 225 | COMMAND_CLASS_PROTECTION: "command protection", 226 | COMMAND_CLASS_LOCK: "command lock", 227 | COMMAND_CLASS_NODE_NAMING: "command node naming", 228 | COMMAND_CLASS_FIRMWARE_UPDATE: "command firmware update", 229 | COMMAND_CLASS_GROUPING_NAME: "command grouping name", 230 | COMMAND_CLASS_REMOTE_ASSOCIATION_ACTIVATE: "command remote association activte", 231 | COMMAND_CLASS_REMOTE_ASSOCIATION: "command remote association", 232 | COMMAND_CLASS_BATTERY: "command battery", 233 | COMMAND_CLASS_CLOCK: "command clock", 234 | COMMAND_CLASS_HAIL: "command hail", 235 | COMMAND_CLASS_WAKEUP: "command wakeup", 236 | COMMAND_CLASS_ASSOCIATION: "command association", 237 | COMMAND_CLASS_VERSION: "command version", 238 | COMMAND_CLASS_INDICATOR: "command indicator", 239 | COMMAND_CLASS_PROPRIETRAY: "command proprietary", 240 | COMMAND_CLASS_LANGUAGE: "command language", 241 | COMMAND_CLASS_TIME: "command time", 242 | COMMAND_CLASS_TIME_PARAMETERS: "command time parameters", 243 | COMMAND_CLASS_GEOGRAPHIC_LOCATION: "command geographic location", 244 | COMMAND_CLASS_COMPOSITE: "command position", 245 | COMMAND_CLASS_MULTICHANNEL_ASSOCIATION: "command multichannel association", 246 | COMMAND_CLASS_MULTI_CMD: "command multi cmd", 247 | COMMAND_CLASS_ENERGY_PRODUCTION: "command energy production", 248 | COMMAND_CLASS_MANUFACTURER_PROPRIETRATY: "command manufacturer proprietary", 249 | COMMAND_CLASS_SCREEN_MD: "command screen md", 250 | COMMAND_CLASS_SCREEN_ATTRIBUTES: "command screen attributes", 251 | COMMAND_CLASS_SIMPLE_AV_CONTROL: "command simple av control", 252 | COMMAND_CLASS_AV_CONTENT_DIRECTORY_MD: "command av content directory", 253 | COMMAND_CLASS_RENDERER_STATUS: "command renderer status", 254 | COMMAND_CLASS_AV_CONTENT_SEARCH_MD: "command av content search md", 255 | COMMAND_CLASS_SECURITY: "command security", 256 | COMMAND_CLASS_AV_TAGGING_MD: "command av tagging md", 257 | COMMAND_CLASS_IP_CONFIGURATION: "command ip configuration", 258 | COMMAND_CLASS_ASSOCIATION_COMMAND_CONFIGURATION: 259 | "command association command configuration", 260 | COMMAND_CLASS_ALARM_SENSOR: "command alarm sensor", 261 | COMMAND_CLASS_SILENCE_ALARM: "command silence alarm", 262 | COMMAND_CLASS_SENSOR_CONFIGURATION: "command sensor configuration", 263 | COMMAND_CLASS_MARK: "command mark", 264 | COMMAND_CLASS_NON_INEROPERABLE: "command non interoperable", 265 | } 266 | 267 | var ZWaveTypeNames = [...]string{ 268 | BASIC_TYPE_CONTROLER: "basic controler", 269 | BASIC_TYPE_STATIC_CONTROLER: "basic static controler", 270 | BASIC_TYPE_SLAVE: "basic slave", 271 | BASIC_TYPE_ROUTING_SLAVE: "basic routing slave", 272 | GENERIC_TYPE_THERMOSTAT: "generic thermostat", 273 | GENERIC_TYPE_BINARY_SWITCH: "generic binary switch", 274 | GENERIC_TYPE_MULTILEVEL_SWITCH: "generic multilevel switch", 275 | GENERIC_TYPE_SWITCH_REMOTE: "generic switch remote", 276 | GENERIC_TYPE_SWITCH_TOGGLE: "generic switch toggle", 277 | GENERIC_TYPE_SECURITY_PANEL: "generic security panel", 278 | GENERIC_TYPE_BINARY_SENSOR: "generic binary sensor", 279 | GENERIC_TYPE_MULTILEVEL_SENSOR: "generic multilevel sensor", 280 | GENERIC_TYPE_METER: "generic meter", 281 | GENERIC_TYPE_ENTRY_CONTROL: "generic entry control", 282 | } 283 | 284 | func (g *Gateway) ToString() string { 285 | w := "->" 286 | if g.Write { w = "<>" } 287 | return fmt.Sprintf("%s %s %s (%s)", g.Key, w, g.Topic, g.Type) 288 | } 289 | 290 | func (g *Gateway) GetValue(update map[string]interface{}) string { 291 | switch g.Type { 292 | case "string": 293 | value, err := jsonStringValue(g.Key + "." + g.Value,update) 294 | if err == nil { 295 | return value 296 | } 297 | case "int": 298 | value, err := jsonFloatValue(g.Key + "." + g.Value,update) 299 | if err == nil { 300 | return fmt.Sprintf("%d", int(value)) 301 | } 302 | case "float": 303 | value, err := jsonFloatValue(g.Key + "." + g.Value,update) 304 | if err == nil { 305 | v := fmt.Sprintf("%.3f", value) 306 | if strings.Contains(v,".") { 307 | v = strings.TrimRight(v,"0.") 308 | } 309 | return v 310 | } 311 | case "bool": 312 | value, err := jsonBoolValue(g.Key + "." + g.Value,update) 313 | if err == nil { 314 | return fmt.Sprintf("%t", value) 315 | } 316 | } 317 | return "" 318 | } 319 | 320 | func init() { 321 | //initialize command line parameters 322 | flag.StringVar(&zway_server,"s","localhost:8083","Z-Way server name or ZWAY_SERVER environment variable") 323 | flag.StringVar(&zway_username,"u","admin","Z-Way username or ZWAY_USERNAME environment variable") 324 | flag.StringVar(&zway_password,"p","","Z-Way passsword or ZWAY_PASSWORD environment variable") 325 | flag.StringVar(&zway_home,"h","razberry","mqtt topic root or ZWAY_HOME environment variable") 326 | flag.StringVar(&mqtt_server,"m","localhost:1883","MQTT server or MQTT_SERVER environment variable") 327 | flag.StringVar(&mqtt_username,"mu","","MQTT username or MQTT_USERNAME environment variable") 328 | flag.StringVar(&mqtt_password,"mp","","MQTT password or MQTT_PASSWORD environment variable") 329 | flag.StringVar(&mqtt_protocol,"proto","tcp","MQTT protocol tcp/ws/tls or MQTT_PROTOCOL environment variable") 330 | flag.IntVar(&zway_refresh,"r",30,"Z-Way refresh rate in seconds or ZWAY_REFRESH environment variable") 331 | flag.BoolVar(&debug,"v",false,"Show debug messages") 332 | flag.StringVar(&profile,"profile","","Profile execution (cpu/mem/all)") 333 | flag.Parse() 334 | 335 | // check defaults against environment variables 336 | if zway_server == "localhost:8083" && len(os.Getenv("ZWAY_SERVER")) > 0 { 337 | zway_server = os.Getenv("ZWAY_SERVER") 338 | } 339 | 340 | if zway_username == "admin" && len(os.Getenv("ZWAY_USERNAME")) > 0 { 341 | zway_username = os.Getenv("ZWAY_USERNAME") 342 | } 343 | 344 | if len(zway_password) == 0 && len(os.Getenv("ZWAY_PASSWORD")) > 0 { 345 | zway_password = os.Getenv("ZWAY_PASSWORD") 346 | } 347 | 348 | if zway_home == "razberry" && len(os.Getenv("ZWAY_HOME")) > 0 { 349 | zway_home = os.Getenv("ZWAY_HOME") 350 | } 351 | 352 | if zway_refresh == 30 && len(os.Getenv("ZWAY_REFRESH")) > 0 { 353 | zway_refresh, _ = strconv.Atoi(os.Getenv("ZWAY_REFRESH")) 354 | } 355 | 356 | if mqtt_server == "localhost:1883" && len(os.Getenv("MQTT_SERVER")) > 0 { 357 | mqtt_server = os.Getenv("MQTT_SERVER") 358 | } 359 | 360 | if len(mqtt_username) == 0 && len(os.Getenv("MQTT_USERNAME")) > 0 { 361 | mqtt_username = os.Getenv("MQTT_USERNAME") 362 | } 363 | 364 | if len(mqtt_password) == 0 && len(os.Getenv("MQTT_PASSWORD")) > 0 { 365 | mqtt_password = os.Getenv("MQTT_PASSWORD") 366 | } 367 | 368 | if mqtt_protocol == "tcp" && len(os.Getenv("MQTT_PROTOCOL")) > 0 { 369 | mqtt_protocol = os.Getenv("MQTT_PROTOCOL") 370 | } 371 | 372 | if !debug && len(os.Getenv("ZWAYMQTT_DEBUG")) > 0 { 373 | if os.Getenv("ZWAYMQTT_DEBUG") == "true" { 374 | debug = true 375 | } 376 | } 377 | 378 | //standardise hostname values to : 379 | zway_match, err := regexp.MatchString(":[0-9]+$",zway_server) 380 | if err != nil { 381 | log.Fatal(fmt.Sprintf("Could not use regexp: %s", err)) 382 | } 383 | if zway_match == false { 384 | log.Print("Setting port 8083 on given Z-Way server") 385 | zway_server = zway_server + ":8083" 386 | } 387 | mqtt_match, err := regexp.MatchString(":[0-9]+$",mqtt_server) 388 | if err != nil { 389 | log.Fatal(fmt.Sprintf("Could not use regexp: %s", err)) 390 | } 391 | if mqtt_match == false { 392 | log.Print("Setting port 1883 on given MQTT server") 393 | mqtt_server = mqtt_server + ":1883" 394 | } 395 | } 396 | 397 | func getzway() string { 398 | if (debug) { log.Print("Getting Z-Way update.") } 399 | url := fmt.Sprintf("http://%s%s%d", zway_server, zway_dataapi, zway_timestamp) 400 | req, err := http.NewRequest("GET",url,nil) 401 | if err != nil { 402 | log.Printf("Error initializing request: %s", err) 403 | } 404 | if zway_cookie != nil { 405 | req.AddCookie(zway_cookie) 406 | } 407 | rsp, err := http_client.Do(req) 408 | if err != nil { 409 | log.Printf("Could not make zway update: %s", err) 410 | return "" 411 | } 412 | defer rsp.Body.Close() 413 | bdy, err := ioutil.ReadAll(rsp.Body) 414 | if err != nil { 415 | log.Printf("could not read body: %s", err) 416 | } 417 | return string(bdy) 418 | } 419 | 420 | func authzway() { 421 | //getting Zway authentication cookie 422 | url := fmt.Sprintf("http://%s%slogin", zway_server, zway_zautoapi) 423 | login := fmt.Sprintf("{\"login\": \"%s\", \"password\": \"%s\"}", 424 | zway_username, zway_password) 425 | req, err := http.NewRequest("POST",url,strings.NewReader(login)) 426 | if err != nil { 427 | log.Printf("Error initializing request: %s", err) 428 | } 429 | req.Header.Set("Content-Type", "application/json") 430 | rsp, err := http_client.Do(req) 431 | if err != nil { 432 | log.Fatalf("Could not login to Z-Way: %s", err) 433 | } 434 | cookies := rsp.Cookies() 435 | for i := range cookies { 436 | if cookies[i].Name == zway_cookiename && cookies[i].Path == "/" { 437 | zway_cookie = cookies[i] 438 | break 439 | } 440 | } 441 | if zway_cookie == nil { 442 | log.Fatal("Z-Way cookie not found.") 443 | } 444 | } 445 | 446 | func jsonValue(key string, target map[string]interface{}) (interface{}, error) { 447 | //if the value is directly found... return it 448 | if target[key] != nil { 449 | return target[key], nil 450 | } 451 | current := target 452 | keys := strings.Split(key,".") 453 | for i := range keys[:len(keys)-1] { 454 | value := current[keys[i]] 455 | if value == nil { 456 | return nil, errors.New(fmt.Sprintf("Json Key not existent (%s)", keys[i])) 457 | } 458 | current = value.(map[string]interface{}) 459 | } 460 | key = keys[len(keys)-1] 461 | value := current[key] 462 | if value != nil { 463 | return value, nil 464 | } 465 | return nil, errors.New("Json Value non existent.") 466 | } 467 | 468 | func jsonStringValue(key string, target map[string]interface{}) (string, error) { 469 | iface, err := jsonValue(key,target) 470 | if err != nil { 471 | return "", err 472 | } 473 | return iface.(string), nil 474 | } 475 | 476 | func jsonIntValue(key string, target map[string]interface{}) (int, error) { 477 | iface, err := jsonValue(key,target) 478 | if err != nil { 479 | return 0, err 480 | } 481 | return iface.(int), nil 482 | } 483 | 484 | func jsonFloatValue(key string, target map[string]interface{}) (float64, error) { 485 | iface, err := jsonValue(key,target) 486 | if err != nil { 487 | return 0.0, err 488 | } 489 | return iface.(float64), nil 490 | } 491 | 492 | func jsonMapValue(key string, target map[string]interface{}) (map[string]interface{}, error) { 493 | iface, err := jsonValue(key,target) 494 | if err != nil { 495 | return nil, err 496 | } 497 | return iface.(map[string]interface{}), nil 498 | } 499 | 500 | func jsonBoolValue(key string, target map[string]interface{}) (bool, error) { 501 | iface, err := jsonValue(key,target) 502 | if err != nil { 503 | return false, err 504 | } 505 | return iface.(bool), nil 506 | } 507 | 508 | func zwaygetcmdclassdata(cmdClasses map[string]interface{}, cmdClass int) (map[string]interface{}, error) { 509 | iface := cmdClasses[strconv.Itoa(cmdClass)] 510 | if iface == nil { 511 | return nil, errors.New("Command class not implemented by instance") 512 | } 513 | class := iface.(map[string]interface{}) 514 | data, err := jsonMapValue("data",class) 515 | if err != nil { 516 | return nil, err 517 | } 518 | return data, nil 519 | } 520 | 521 | func normName(name string) string { 522 | //trim 523 | res := strings.Trim(name," /") 524 | //lower 525 | res = strings.ToLower(res) 526 | //spaces 527 | res = strings.Replace(res," ","_",-1) 528 | //percents 529 | res = strings.Replace(res,"%","pc",-1) 530 | //deg 531 | res = strings.Replace(res,"°","",-1) 532 | return res 533 | } 534 | 535 | func zwayparsedevices(update map[string]interface{}) { 536 | log.Print("Parse Z-Way devices") 537 | for node, info := range update { 538 | m := info.(map[string]interface{}) 539 | basicType, err := jsonFloatValue("data.basicType.value",m) 540 | if err != nil { 541 | log.Printf("basic type not found: %s", err) 542 | continue 543 | } 544 | genericType, err:= jsonFloatValue("data.genericType.value",m) 545 | if err != nil { 546 | log.Printf("generic type not found: %s", err) 547 | continue 548 | } 549 | givenName, err := jsonStringValue("data.givenName.value",m) 550 | if err != nil { 551 | log.Printf("given name not found: %s", err) 552 | continue 553 | } 554 | //specificType := int(jsonFloatValue("data.specificType.value",m)) 555 | isControler := false 556 | switch int(basicType) { 557 | case BASIC_TYPE_CONTROLER: 558 | isControler = true 559 | case BASIC_TYPE_STATIC_CONTROLER: 560 | isControler = true 561 | } 562 | //skip if controller 563 | if isControler { 564 | log.Printf("Skipping node %s: %s", node, ZWaveTypeNames[int(basicType)]) 565 | continue 566 | } 567 | //skip if no name 568 | if len(givenName) == 0 { 569 | log.Printf("given name empty") 570 | continue 571 | } 572 | //parsing instances 573 | instances, err := jsonMapValue("instances",m) 574 | if err != nil { 575 | continue 576 | } 577 | for i := range instances { 578 | instance := instances[i].(map[string]interface{}) 579 | commandClasses, err := jsonMapValue("commandClasses",instance) 580 | if err != nil { 581 | log.Printf("command classes not found: %s", err) 582 | continue 583 | } 584 | nkey := fmt.Sprintf("devices.%s.instances.%s.commandClasses.%d.data", 585 | node, i, COMMAND_CLASS_BATTERY) 586 | topic := fmt.Sprintf("%s/sensors/analogic/%s/%s/battery", 587 | zway_home, normName(givenName),i) 588 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 589 | Value: "last.value", Write:false, Type: "int"}) 590 | switch int(genericType) { 591 | case GENERIC_TYPE_BINARY_SWITCH: 592 | nkey := fmt.Sprintf("devices.%s.instances.%s.commandClasses.%d.data", 593 | node, i, COMMAND_CLASS_SWITCH_BINARY) 594 | topic := fmt.Sprintf("%s/actuators/binary/%s/%s/switch", 595 | zway_home, normName(givenName), i) 596 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 597 | Value: "level.value", Write:true, Type: "bool"}) 598 | case GENERIC_TYPE_MULTILEVEL_SWITCH: 599 | nkey := fmt.Sprintf("devices.%s.instances.%s.commandClasses.%d.data", 600 | node, i, COMMAND_CLASS_SWITCH_MULTILEVEL) 601 | topic := fmt.Sprintf("%s/actuators/analogic/%s/%s/switch", 602 | zway_home, normName(givenName),i) 603 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 604 | Value: "level.value", Write:true, Type: "float"}) 605 | case GENERIC_TYPE_BINARY_SENSOR: 606 | data, err := zwaygetcmdclassdata(commandClasses, 607 | COMMAND_CLASS_SENSOR_BINARY) 608 | if err != nil { 609 | break 610 | } 611 | sensorType := "generic" 612 | nkey := fmt.Sprintf("devices.%s.instances.%s.commandClasses.%d.data", 613 | node, i, COMMAND_CLASS_SENSOR_BINARY) 614 | topic := fmt.Sprintf("%s/sensors/binary/%s/%s/%s", 615 | zway_home, normName(givenName), i, sensorType) 616 | _, err = jsonBoolValue("level.value",update) 617 | if err == nil { 618 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 619 | Value: "level.value", Write:false, Type: "bool"}) 620 | } else { 621 | for k, v := range data { 622 | if _, err := strconv.Atoi(k); err == nil { 623 | sensor := v.(map[string]interface{}) 624 | sensorType, err := jsonStringValue("sensorTypeString.value",sensor) 625 | if err != nil { 626 | log.Printf("Could not get sensor type: %s", err) 627 | continue 628 | } 629 | nkey := fmt.Sprintf( 630 | "devices.%s.instances.%s.commandClasses.%d.data.%s", 631 | node, i, COMMAND_CLASS_SENSOR_BINARY,k) 632 | topic := fmt.Sprintf("%s/sensors/binary/%s/%s/%s", 633 | zway_home,normName(givenName), i, normName(sensorType)) 634 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 635 | Value: "level.value", Write:false, Type: "bool"}) 636 | } 637 | } 638 | } 639 | fallthrough 640 | case GENERIC_TYPE_MULTILEVEL_SENSOR: 641 | data, err := zwaygetcmdclassdata(commandClasses, 642 | COMMAND_CLASS_SENSOR_MULTILEVEL) 643 | if err == nil { 644 | for k, v := range data { 645 | if _, err := strconv.Atoi(k); err == nil { 646 | sensor := v.(map[string]interface{}) 647 | sensorType, err := jsonStringValue("sensorTypeString.value", 648 | sensor) 649 | if err != nil { 650 | log.Printf("Could not get sensor type: %s", err) 651 | continue 652 | } 653 | sensorScale, err := jsonStringValue("scaleString.value", 654 | sensor) 655 | if err != nil { 656 | log.Printf("Could not get sensor scale: %s", err) 657 | continue 658 | } 659 | nkey := fmt.Sprintf( 660 | "devices.%s.instances.%s.commandClasses.%d.data.%s", 661 | node, i, COMMAND_CLASS_SENSOR_MULTILEVEL,k) 662 | topic := fmt.Sprintf("%s/sensors/analogic/%s/%s/%s/%s", 663 | zway_home, normName(givenName), i, normName(sensorType), 664 | normName(sensorScale)) 665 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 666 | Value: "val.value", Write:false, Type: "float"}) 667 | } 668 | } 669 | } 670 | case GENERIC_TYPE_METER: 671 | data, err := zwaygetcmdclassdata(commandClasses,COMMAND_CLASS_METER) 672 | if err == nil { 673 | for k, v := range data { 674 | if _, err := strconv.Atoi(k); err == nil { 675 | sensor := v.(map[string]interface{}) 676 | sensorType, err := jsonStringValue("sensorTypeString.value", 677 | sensor) 678 | if err != nil { 679 | log.Printf("Could not get sensor type: %s", err) 680 | continue 681 | } 682 | sensorScale, err := jsonStringValue("scaleString.value", 683 | sensor) 684 | if err != nil { 685 | log.Printf("Could not get sensor scale: %s", err) 686 | continue 687 | } 688 | nkey := fmt.Sprintf( 689 | "devices.%s.instances.%s.commandClasses.%d.data.%s", 690 | node, i, COMMAND_CLASS_METER,k) 691 | topic := fmt.Sprintf("%s/sensors/analogic/%s/%s/%s/%s", 692 | zway_home, normName(givenName), i, normName(sensorType), 693 | normName(sensorScale)) 694 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 695 | Value: "val.value", Write:false, Type: "float"}) 696 | } 697 | } 698 | } 699 | case GENERIC_TYPE_THERMOSTAT: 700 | //get the binary switch to enable/disable thermostat 701 | nkey := fmt.Sprintf("devices.%s.instances.%s.commandClasses.%d.data", 702 | node, i, COMMAND_CLASS_SWITCH_BINARY) 703 | topic := fmt.Sprintf("%s/actuators/binary/%s/%s/switch", 704 | zway_home, normName(givenName), i) 705 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 706 | Value: "level.value", Write:true, Type: "bool"}) 707 | //TODO: informations about set point 708 | data, err := zwaygetcmdclassdata(commandClasses, 709 | COMMAND_CLASS_THERMOSTAT_SET_POINT) 710 | if err == nil { 711 | for k, v := range data { 712 | if _, err := strconv.Atoi(k); err == nil { 713 | setpoint := v.(map[string]interface{}) 714 | setpointType, err := jsonStringValue("modeName.value", 715 | setpoint) 716 | if err != nil { 717 | log.Printf("Could not get set point mode: %s", err) 718 | continue 719 | } 720 | setpointScale, err := jsonStringValue("scaleString.value", 721 | setpoint) 722 | if err != nil { 723 | log.Printf("Could not get setpoint scale: %s", err) 724 | continue 725 | } 726 | nkey := fmt.Sprintf( 727 | "devices.%s.instances.%s.commandClasses.%d.data.%s", 728 | node, i, COMMAND_CLASS_THERMOSTAT_SET_POINT,k) 729 | topic := fmt.Sprintf("%s/actuators/analogic/%s/%s/%s/%s", 730 | zway_home, normName(givenName), i, normName(setpointType), 731 | normName(setpointScale)) 732 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 733 | Value: "val.value", Write:true, Type: "int"}) 734 | } 735 | } 736 | } 737 | data, err = zwaygetcmdclassdata(commandClasses, 738 | COMMAND_CLASS_SENSOR_MULTILEVEL) 739 | if err == nil { 740 | for k, v := range data { 741 | if _, err := strconv.Atoi(k); err == nil { 742 | sensor := v.(map[string]interface{}) 743 | sensorType, err := jsonStringValue("sensorTypeString.value", 744 | sensor) 745 | if err != nil { 746 | log.Printf("Could not get sensor type: %s", err) 747 | continue 748 | } 749 | sensorScale, err := jsonStringValue("scaleString.value", 750 | sensor) 751 | if err != nil { 752 | log.Printf("Could not get sensor scale: %s", err) 753 | continue 754 | } 755 | nkey := fmt.Sprintf( 756 | "devices.%s.instances.%s.commandClasses.%d.data.%s", 757 | node, i, COMMAND_CLASS_SENSOR_MULTILEVEL,k) 758 | topic := fmt.Sprintf("%s/sensors/analogic/%s/%s/%s/%s", 759 | zway_home, normName(givenName), i, normName(sensorType), 760 | normName(sensorScale)) 761 | gateways = append(gateways, Gateway{Key: nkey, Topic: topic, 762 | Value: "val.value", Write:false, Type: "float"}) 763 | } 764 | } 765 | } 766 | default: 767 | log.Printf("device not implemented: type: %f / name: %s", genericType, givenName) 768 | } 769 | } 770 | } 771 | } 772 | 773 | func zwayupdategateways(update map[string]interface{}, mqtt_updates chan<- MqttUpdate) { 774 | if (debug) { log.Print("Update Z-Way devices") } 775 | for _, g := range gateways { 776 | //Z-Way is always true 777 | value := g.GetValue(update) 778 | if len(value) > 0 { 779 | if (debug) { log.Printf("ZWAY: %s / Value: %s", g.ToString(), value ) } 780 | mqtt_updates <- MqttUpdate{Topic: g.Topic, Value: value} 781 | } 782 | } 783 | } 784 | 785 | func normalizeJson(json map[string]interface{}) map[string]interface{} { 786 | for k, v := range json { 787 | if strings.IndexRune(k,'.') > -1 { 788 | keys := strings.Split(k,".") 789 | nkey := keys[0] 790 | rest := strings.Join(keys[1:len(keys)],".") 791 | tmp := make(map[string]interface{}) 792 | tmp[rest] = v.(map[string]interface{}) 793 | if json[nkey] != nil { 794 | for k2, v2 := range json[nkey].(map[string]interface{}) { 795 | tmp[k2] = v2 796 | } 797 | } 798 | json[nkey] = normalizeJson(tmp) 799 | delete(json, k) 800 | } 801 | } 802 | return json 803 | } 804 | 805 | func checkzwayupdate(update string,mqtt_updates chan<- MqttUpdate) { 806 | var f interface{} 807 | err := json.Unmarshal([]byte(update), &f) 808 | if err != nil { 809 | log.Printf("Error decoding json: %s", err) 810 | } 811 | m := f.(map[string]interface{}) 812 | m = normalizeJson(m) 813 | if zway_timestamp == 0 { 814 | devices, err := jsonMapValue("devices",m) 815 | if err != nil { 816 | log.Printf("devices not found: %s", err) 817 | return 818 | } 819 | zwayparsedevices(devices) 820 | } 821 | zwayupdategateways(m,mqtt_updates) 822 | zway_timestampf, err := jsonFloatValue("updateTime",m) 823 | if err != nil { 824 | log.Printf("timestamp not found: %s", err) 825 | return 826 | } 827 | zway_timestamp = int(zway_timestampf) 828 | } 829 | 830 | //define a function for the default message handler 831 | var f MQTT.MessageHandler = func(client *MQTT.Client, msg MQTT.Message) { 832 | topic := msg.Topic() 833 | value := string(msg.Payload()) 834 | for _, g := range gateways { 835 | if g.Topic == topic { g.Set(value) } 836 | } 837 | } 838 | 839 | func (g *Gateway) Set(value string) { 840 | if !g.Write { 841 | if (debug) { log.Printf("MQTT: %s / Readonly", g.ToString()) } 842 | return 843 | } 844 | if g.Get() == value { 845 | if (debug) { log.Printf("MQTT: %s / Value not changed", g.ToString()) } 846 | return 847 | } 848 | //check value 849 | switch g.Type { 850 | case "int": 851 | if strings.Contains(value,".") { 852 | value = strings.TrimRight(value,"0.") 853 | } 854 | i, err := strconv.Atoi(value) 855 | if err != nil { 856 | log.Printf("MQTT: %s / value not int: %s", g.ToString(), value) 857 | return 858 | } 859 | value = fmt.Sprintf("%d",i) 860 | case "float": 861 | if strings.Contains(value,".") { 862 | value = strings.TrimRight(value,"0.") 863 | } 864 | f, err := strconv.ParseFloat(value,64) 865 | if err != nil { 866 | log.Printf("MQTT: %s / value not float: %s", g.ToString(), value) 867 | return 868 | } 869 | value = fmt.Sprintf("%.3f", f) 870 | } 871 | log.Printf("MQTT: %s / Value: %s ", g.ToString(), value) 872 | key := g.Key 873 | r := regexp.MustCompile("\\.([0-9]+)(\\.|$)") 874 | key = r.ReplaceAllString(key, "[$1].") 875 | r = regexp.MustCompile("\\.data$") 876 | key = r.ReplaceAllString(key,"") 877 | result, _ := zwayget(zway_runapi,fmt.Sprintf("%s.Set(%s)", key, value)) 878 | if result != "null" { 879 | log.Printf("Error updating value: %s", result) 880 | } 881 | } 882 | 883 | func (g *Gateway) Get() string { 884 | if (debug) { log.Print("Setting Z-Way value.") } 885 | key := g.Key 886 | r := regexp.MustCompile("\\.([0-9]+)\\.") 887 | key = r.ReplaceAllString(key, "[$1].") 888 | result, _ := zwayget(zway_runapi, fmt.Sprintf("%s.%s", key, g.Value)) 889 | return result 890 | } 891 | 892 | func zwayget(api string, path string) (string, error) { 893 | url := fmt.Sprintf("http://%s%s%s", zway_server, api, path) 894 | if (debug) { log.Printf("Http Get on Z-Way: %s", url) } 895 | req, err := http.NewRequest("GET",url,nil) 896 | if err != nil { 897 | return "", err 898 | } 899 | if zway_cookie != nil { 900 | req.AddCookie(zway_cookie) 901 | } 902 | rsp, err := http_client.Do(req) 903 | if err != nil { 904 | return "", err 905 | } 906 | defer rsp.Body.Close() 907 | bdy, err := ioutil.ReadAll(rsp.Body) 908 | if err != nil { 909 | return "", err 910 | } 911 | result := string(bdy) 912 | return result, nil 913 | } 914 | 915 | 916 | func main() { 917 | //start profiling 918 | if len(profile) > 0 { 919 | log.Print("Profiling enabled") 920 | cfg := pfl.Config{} 921 | if profile=="mem" || profile=="all" { 922 | cfg.MemProfile = true 923 | } 924 | if profile=="cpu" || profile=="all" { 925 | cfg.CPUProfile = true 926 | } 927 | defer pfl.Start(&cfg).Stop() 928 | } 929 | //print informations given 930 | log.Print("Starting Z-Way to mqtt gateway...") 931 | log.Printf("Z-Way server: %s", zway_server) 932 | if len(zway_password) > 0 { 933 | log.Printf("Z-Way user: %s", zway_username) 934 | } else { 935 | log.Print("Not using authentication as no password given.") 936 | } 937 | log.Printf("Z-Way refresh rate: %d", zway_refresh) 938 | log.Printf("MQTT server: %s", mqtt_server) 939 | 940 | //authtenticate to zway 941 | if len(zway_password) > 0 { 942 | authzway() 943 | } 944 | 945 | //connect and subscribe to mqtt 946 | //prepare 947 | opts := MQTT.NewClientOptions() 948 | opts.AddBroker(mqtt_protocol+"://"+mqtt_server) 949 | opts.SetClientID("ZWayMQTT") 950 | opts.SetDefaultPublishHandler(f) 951 | opts.SetAutoReconnect(true) 952 | if len(mqtt_username) > 0 && len(mqtt_password) > 0 { 953 | opts.SetUsername(mqtt_username) 954 | opts.SetPassword(mqtt_password) 955 | } 956 | 957 | //Connect 958 | mqtt := MQTT.NewClient(opts) 959 | if token := mqtt.Connect(); token.Wait() && token.Error() != nil { 960 | panic(token.Error()) 961 | } 962 | 963 | //create the control channel 964 | quit := make(chan struct{}) 965 | defer close(quit) 966 | 967 | //create zway update channel 968 | zway_updates := make(chan string,3) 969 | defer close(zway_updates) 970 | 971 | //create mqtt update channel 972 | mqtt_updates := make(chan MqttUpdate,20) 973 | defer close(mqtt_updates) 974 | 975 | //create the zway refresh timer 976 | refreshes := time.NewTicker(time.Second * time.Duration(zway_refresh)).C 977 | 978 | //make initial refreshe 979 | zway_updates <- getzway() 980 | 981 | //subscribe only when zway started 982 | subject := zway_home + "/actuators/#" 983 | if token := mqtt.Subscribe(subject, 1, nil); token.Wait() && token.Error() != nil { 984 | fmt.Println(token.Error()) 985 | os.Exit(1) 986 | } 987 | 988 | //start refreshes 989 | go func() { 990 | for _ = range refreshes { 991 | update := getzway() 992 | if len(update) > 0 { 993 | zway_updates <- getzway() 994 | } else { 995 | log.Print("Got empty zwave response...") 996 | if zway_retries < 3 { 997 | log.Printf("Reinitializing Z-Way for the %d time.", zway_retries) 998 | authzway() 999 | zway_retries += 1 1000 | } else { 1001 | log.Print("Already tested 3 times: stop") 1002 | <-quit 1003 | return 1004 | } 1005 | } 1006 | } 1007 | }() 1008 | 1009 | //start update parsing 1010 | go func() { 1011 | for zway_update := range zway_updates { 1012 | checkzwayupdate(zway_update,mqtt_updates) 1013 | } 1014 | }() 1015 | 1016 | //star mqtt updating 1017 | go func() { 1018 | for mqtt_update := range mqtt_updates { 1019 | token := mqtt.Publish(mqtt_update.Topic, 1, true, mqtt_update.Value) 1020 | token.Wait() 1021 | } 1022 | }() 1023 | 1024 | //start the main loop 1025 | for { 1026 | select { 1027 | case <- quit: 1028 | return 1029 | } 1030 | } 1031 | } 1032 | --------------------------------------------------------------------------------