From 611cc7be4ad3698b872b755b992621903c79ff8e Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 26 Jan 2017 16:46:17 -0600 Subject: [PATCH] Updated REAMDE to new constructs. Added Time repalcement Added ability inactivate a device. --- README.md | 159 +++++++++++------- pom.xml | 2 +- .../HABridge/dao/DeviceDescriptor.java | 11 ++ .../HABridge/dao/DeviceRepository.java | 9 + .../bwssystems/HABridge/hue/HueMulator.java | 23 ++- .../bwssystems/HABridge/hue/TimeDecode.java | 38 +++++ .../HABridge/plugins/exec/CommandHome.java | 10 +- .../HABridge/plugins/hal/HalHome.java | 6 +- .../HABridge/plugins/http/HTTPHandler.java | 48 +++--- .../HABridge/plugins/http/HTTPHome.java | 6 +- .../HABridge/plugins/mqtt/MQTTHome.java | 11 +- .../HABridge/plugins/tcp/TCPHome.java | 5 +- .../HABridge/plugins/udp/UDPHome.java | 12 +- .../resources/public/views/configuration.html | 2 + .../resources/public/views/editdevice.html | 6 + 15 files changed, 241 insertions(+), 107 deletions(-) create mode 100644 src/main/java/com/bwssystems/HABridge/hue/TimeDecode.java diff --git a/README.md b/README.md index e16bcf9..ea4e32b 100644 --- a/README.md +++ b/README.md @@ -33,16 +33,16 @@ ATTENTION: This requires JDK 1.8 to run ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below. ``` -java -jar ha-bridge-4.0.3.jar +java -jar ha-bridge-4.1.0.jar ``` ### Automation on Linux systems To have this configured and running automatically there are a few resources to use. One is using Docker and a docker container has been built for this and can be gotten here: https://github.com/aptalca/docker-ha-bridge -Create the directory and make sure that ha-bridge-4.0.3.jar is in your /home/pi/habridge directory. +Create the directory and make sure that ha-bridge-4.1.0.jar is in your /home/pi/habridge directory. ``` pi@raspberrypi:~ $ mkdir habridge pi@raspberrypi:~ $ cd habridge -pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.0.3/ha-bridge-4.0.3.jar +pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.1.0/ha-bridge-4.1.0.jar ``` #### System Control Setup on a pi (preferred) For next gen Linux systems (this includes the Raspberry Pi), here is a systemctl unit file that you can install. Here is a link on how to do this: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units @@ -61,7 +61,7 @@ After=network.target [Service] Type=simple -ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.0.3.jar +ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.1.0.jar [Install] WantedBy=multi-user.target @@ -96,7 +96,7 @@ Then cut and past this, modify any locations that are not correct ``` cd /home/pi/habridge rm /home/pi/habridge/habridge-log.txt -nohup java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.0.3.jar > /home/pi/habridge/habridge-log.txt 2>&1 & +nohup java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.1.0.jar > /home/pi/habridge/habridge-log.txt 2>&1 & chmod 777 /home/pi/habridge/habridge-log.txt ``` Exit and save the file with ctrl-X and follow the prompts and then execute on the command line: @@ -252,7 +252,40 @@ The helper tabs will also show you what you have already configured for that tar #### The Add/Edit Tab Another way to add a device is through the Manual Add Tab. This allows you to manually enter the name, the on and off URLs and select if there are custom handling with the type of call that can be made. This allows for control of anything that has a distinct request that can be executed so you are not limited to the Vera, Harmony, Nest or other Hue. -The format of these can be the default HTTP request which executes the URLs formatted as `http://` as a GET. Other options to this are to select the HTTP Verb and add the data type and add a body that is passed with the request. Secure https is supported as well, just use `https://`. When using POST and PUT, you have the ability to specify the body that will be sent with the request as well as the application type for the http call. +There is a new format for the on/dim/off URL areas. The new editor handles the intricasies of the components, but is broken down here for explanation. + +Here are the fields that can be put into the call item: +Json Type | field name | What | Use +----------|------------|------|----- +String or JsonElement | item| This is the payload that will be called for devices | Required +Integer | count | This is how many times this items will be executed | Optional +Integer | delay | This is how long we will wait until the next call after | Optional +String | type | This is the type of device we are executing | Required +String | filterIPs | This is used filter on the IPs given in the list | Optional +String | httpVerb | This is the http command if given, default is GET | Optional +String | httpBody | Send this Body with a PUT or POST | Optional +String | httpHeaders | Send these headers with the http call | Optional +String | contentType | Define the type of content in the body | Optional + +Example: +``` +[{"item":,"type":""."count":X."delay":X."filterIPs":""."httpVerb":"","httpBody":"","httpHeaders":[{"name":"header name","value":"header value"},{"name":"another header","value":"another value"}],"contentType":""},{"item":,"type":""}] +``` + +The format of the item can be the default HTTP request which executes the URLs formatted as `http://` as a GET. Other options to this are to select the HTTP Verb and add the data type and add a body that is passed with the request. Secure https is supported as well, just use `https://`. When using POST and PUT, you have the ability to specify the body that will be sent with the request as well as the application type for the http call. + +The valid device types are: "custom", "veraDevice", "veraScene", "harmonyActivity", "harmonyButton", "nestHomeAway", "nestThermoSet", "hueDevice", "halDevice", + "halButton", "halHome", "halThermoSet", "mqttMessage", "cmdDevice", "hassDevice", "tcpDevice", "udpDevice", "httpDevice", "domoticzDevice" + +Filter Ip example: +``` +Turn on Lights in Bedroom 1 (http://api.call.here/1) - Restricted to Echo 1 (10.1.1.1) +Turn on Lights in Bedroom 2 (http://api.call.here/2) - Restricted to Echo 2 (10.2.2.2) +Turn on Lights in Bedroom 3 (http://api.call.here/3) - Restricted to Echo 3 (10.3.3.3) + +Device: "Lights" +On URL: [{"item":"http://api.call.here/1", "httpVerb":"POST", "httpBody":"value1=1&value2=2","type":"httpDevice","filterIPs":"10.1.1.1"},{"item":"http://api.call.here/2", "httpVerb":"POST", "httpBody":"value1=1&value2=2","type":"httpDevice","filterIPs":"10.2.2.2"},{"item":"http://api.call.here/3", "httpVerb":"POST", "httpBody":"value1=1&value2=2","type":"httpDevice","filterIPs":"10.3.3.3"}] +``` Headers can be added as well using a Json construct [{"name":"header type name","value":"the header value"}] with the format example: ``` @@ -262,44 +295,43 @@ Headers can be added as well using a Json construct [{"name":"header type name", Another option that is detected by the bridge is to use UDP or TCP direct calls such as `udp://:/` to send a UDP request. TCP calls are handled the same way as `tcp://:/`. If your data for the UDP or TCP request is formatted as "0x00F009B9" lexical hex format, the bridge will convert the data into a binary stream to send. -You can also use the value replacement constructs within these statements. Such as using the expressions ${intensity.percent} for 0-100 or ${intensity.byte} for 0-255 for straight pass through of the value or items that require special calculated values using ${intensity.math()} i.e. "${intensity.math(X/4)}". +You can also use the value replacement constructs within these statements. Such as using the expressions "${time.format(Java time format string)}" for inserting a date/time stamp, ${intensity.percent} for 0-100 or ${intensity.byte} for 0-255 for straight pass through of the value or items that require special calculated values using ${intensity.math()} i.e. "${intensity.math(X/4)}". See Value Passing Controls Below. Examples: ``` -GET -http://192.168.1.1:8180/set/this/value/${intensity.percent} -PUT -http://192.168.1.1:8280/set/this -ContentBody: {"someValue":"${intensity.byte}"} +[{"item":"http://192.168.1.1:8180/set/this/value/${intensity.percent}","type":"httpDevice","httpVerb":"GET"}] -udp://192.168.1.1:5000/0x45${intensity.percent}55 -udp://192.168.2.2:6000/fireoffthismessage\n +[{"item":"http://192.168.1.1:8280/set/this","type":"httpDevice","httpVerb":"PUT","httpBody":{"someValue":"${intensity.byte}"}}] -tcp://192.168.3.3:9000/sendthismessage +[{"item":"udp://192.168.1.1:5000/0x45${intensity.percent}55","type":"udpDevice"}] -tcp://192.168.4.4:10000/0x435f12dd${intensity.math((X -4)*50)}438c +[{"item":"udp://192.168.2.2:6000/fireoffthismessage\n","type":"udpDevice"}] -tcp://192.168.5.5:110000/0x +[{"item":"tcp://192.168.3.3:9000/sendthismessage","type":"tcpDevice"}] + +[{"item":"tcp://192.168.4.4:10000/0x435f12dd${intensity.math((X -4)*50)}438c","type":"tcpDevice"}] + +[{"item":"tcp://192.168.5.5:110000/0x","type":"tcpDevice"}] ``` #### Multiple Call Construct Also available is the ability to specify multiple commands in the On URL, Dim URL and Off URL areas by adding Json constructs listed here. This is only for the types of tcp, udp, http, https or a new exec type. Also within the item format you can specify delay in milliseconds and count per item. These new paramters work on device buttons for the Harmony as well. Format Example in the URL areas: ``` -[{"item":"http://192.168.1.1:8180/do/this/thing"}, -{"item":"http://192.168.1.1:8180/do/the/next/thing","delay":1000,"count":2}, -{"item":"http://192.168.1.1:8180/do/another/thing"}] +[{"item":"http://192.168.1.1:8180/do/this/thing","type":"httpDevice"}, +{"item":"http://192.168.1.1:8180/do/the/next/thing","delay":1000,"count":2,"type":"httpDevice"}, +{"item":"http://192.168.1.1:8180/do/another/thing","type":"httpDevice"}] -[{"item":"udp://192.168.1.1:5000/0x450555"}, -{"item":"udp://192.168.1.1:5000/0x45${intensity.percent}55"}] +[{"item":"udp://192.168.1.1:5000/0x450555","type":"udpDevice"}, +{"item":"udp://192.168.1.1:5000/0x45${intensity.percent}55","type":"udpDevice"}] -[{"item":"udp://192.168.1.1:5000/0x450555"}, -{"item":"http://192.168.1.1:8180/do/this/thing"}, -{"item":"tcp://192.168.2.1/sendthisdata"}, -{"item":"https://192.168.12.1/do/this/secure/thing"}, -{"item":"exec://notepad.exe"}] +[{"item":"udp://192.168.1.1:5000/0x450555","type":"udpDevice"}, +{"item":"http://192.168.1.1:8180/do/this/thing","type":"httpDevice"}, +{"item":"tcp://192.168.2.1/sendthisdata","type":"tcpDevice"}, +{"item":"https://192.168.12.1/do/this/secure/thing","type":"httpDevice"}, +{"item":"exec://notepad.exe","type":"cmdDevice"}] ``` #### Script or Command Execution The release as of v2.0.0 will now support the execution of a local script or program. This will blindly fire off a process to run and is bound by the privileges of the java process. @@ -308,20 +340,27 @@ To configure this type of manual add, you will need to select the Device type of In the URL areas, the format of the execution is just providing what command line you would like to run, or using the multiple call item construct described above. ``` -notepad.exe +[{"item":"C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1","type":"cmdDevice"}, +{"item":"notepad.exe","type":"cmdDevice"}] OR -[{"item":"C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1"}, -{"item":"notepad.exe"}] +[{"item":"exec://notepad.exe","type":"cmdDevice"}] -OR +``` +#### Value Passing Controls +There are multiple replacement constructs available to be put into any of the calls except Harmony items, Net Items and HAL items. These constructs are: "${time.format(Java time format string)}", "${intensity.percent}", "${intensity.byte}" and "${intensity.math(using X in your calc)}". +You can control items that require special calculated values using ${intensity.math()} i.e. "${intensity.math(X/4)}". +For the items that want to have a date time put into the message, utilize ${time.format(yyyy-MM-ddTHH:mm:ssXXX)} where "yyyy-MM-ddTHH:mm:ssXXX" can be any format from the Java SimpleDateFormat documented here: https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html +e.g. +``` +[{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=10&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.math(X/4)}","type":"httpDevice"}] -/home/me/startsomething.sh +[{"item":"udp://192.168.1.1:5000/0x45${intensity.percent}55","type":"udpDevice"}] -OR +[{"item":"tcp://192.168.1.1:5000/This is the intensity real value ${intensity.byte}","type":"tcpDevice"}] -[{"item":"exec://notepad.exe"}] +[{"item":{"clientId":"TestClient","topic":"Yep","message":"This is the time ${time.format(yyyy-MM-ddTHH:mm:ssXXX)}"},"type":"mqttDevice"}] ``` @@ -415,8 +454,8 @@ contentBodyOff | string | This is the content body that you would like to send w { "name" : "bedroom light", "deviceType" : "switch", - "onUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41", - "offUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41" + "onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}], + "offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}] } ``` #### Dimming Control Example @@ -426,8 +465,8 @@ e.g. { "name": "entry light", "deviceType": "switch", - "offUrl": "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31", - "onUrl": "http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=31&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.percent}" + "offUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31","type":"veraDevice"}], + "onUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=31&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.percent}","type":"veraDevice"}] } ``` See the echo's documentation for the dimming phrase. @@ -439,8 +478,8 @@ e.g. { "name": "Thermostat, "deviceType": "custom", - "offUrl": "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=10", - "onUrl": "http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=10&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.math(X/4)}" + "offUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=10","type":"veraDevice"}], + "onUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=10&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.math(X/4)}","type":"veraDevice"}] } ``` See the echo's documentation for the dimming phrase. @@ -452,12 +491,8 @@ e.g: { "name": "test device", "deviceType": "custom", - "offUrl": "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31", - "onUrl": "http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=31&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.percent}", - "httpVerb":"POST", - "contentType" : "application/json", - "contentBody" : "{\"fooBar\":\"baz_on\"}" - "contentBodyOff" : "{\"fooBar\":\"baz_off\"}" + "offUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31","httpVerb":"POST","contentType" : "application/json","httpBody" : "{\"fooBar\":\"baz_off\"}], + "onUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=31&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.percent}","type":"httpDevice","httpVerb":"POST","contentType" : "application/json","httpBody" : "{\"fooBar\":\"baz_on\"}] } ``` #### Custom Usage URLs Example @@ -466,8 +501,8 @@ Anything that takes an action as a result of an HTTP request will probably work { "name": "night mode", "deviceType": ""custom", - "offUrl": "http://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=1", - "onUrl": "http://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=3" + "offUrl": [{"item":"http://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=1","type":"httpDevice"}], + "onUrl": [{"item":"http://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=3","type":"httpDevice"}] } ``` Here is a UDP example that can send binary data. @@ -475,8 +510,8 @@ Here is a UDP example that can send binary data. { "name": "UDPPacket", "deviceType": "custom", - "offUrl": "udp://192.168.1.1:8899/0x460055", - "onUrl": "udp://192.168.1.1:8899/0x450055" + "offUrl": [{"item":"udp://192.168.1.1:8899/0x460055","type":"udpDevice"}], + "onUrl": [{"item":"udp://192.168.1.1:8899/0x450055","type":"udpDevice"}] } ``` #### Response @@ -501,8 +536,8 @@ contentBodyOff | string | This is the content body that you would like to send w "id" : "12345", "name" : "bedroom light", "deviceType" : "switch", - "onUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41", - "offUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41" + "onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}], + "offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}] } ``` ### Update a Device @@ -535,8 +570,8 @@ contentBodyOff | string | This is the content body that you would like to send w "id" : "6789", "name" : "table light", "deviceType" : "switch", - "onUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41", - "offUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41" + "onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}], + "offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}] } ``` #### Response @@ -545,8 +580,8 @@ contentBodyOff | string | This is the content body that you would like to send w "id" : "6789", "name" : "table light", "deviceType" : "switch", - "onUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41", - "offUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41" + "onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}], + "offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}] } ``` ### Get All Devices @@ -561,15 +596,15 @@ Individual entries are the same as a single device but in json list format. "id" : "12345", "name" : "bedroom light", "deviceType" : "switch", - "onUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41", - "offUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41" + "onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}], + "offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}] } { "id" : "6789", "name" : "table light", "deviceType" : "switch", - "onUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41", - "offUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41" + "onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}], + "offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}] }] ``` ### Get a Specific Device @@ -584,8 +619,8 @@ The response is the same layout as defined in the add device response. "id" : "6789", "name" : "table light", "deviceType" : "switch", - "onUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41", - "offUrl" : "http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41" + "onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}], + "offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}] } ``` ### Delete a Specific Device diff --git a/pom.xml b/pom.xml index 455aa0b..addc4fd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 4.1.0beta4 + 4.1.0beta5 jar diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java index 480ea57..6e1a5de 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java @@ -56,6 +56,9 @@ public class DeviceDescriptor{ @SerializedName("contentBodyDim") @Expose private String contentBodyDim; + @SerializedName("inactive") + @Expose + private Boolean inactive; private DeviceState deviceState; @@ -197,4 +200,12 @@ public class DeviceDescriptor{ this.deviceState = deviceState; } + public Boolean getInactive() { + return inactive; + } + + public void setInactive(Boolean inactive) { + this.inactive = inactive; + } + } diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java index f6460aa..67a67de 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java @@ -74,6 +74,15 @@ public class DeviceRepository extends BackupHandler { return list; } + public List findActive() { + List list = new ArrayList(); + for(DeviceDescriptor aDevice : new ArrayList(devices.values())) { + if(aDevice.getInactive() == null || !aDevice.getInactive()) + list.add(aDevice); + } + return list; + } + public DeviceDescriptor findOne(String id) { return devices.get(id); } diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index ae483aa..3631a1c 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -520,7 +520,7 @@ public class HueMulator { log.debug("hue lights list requested: " + userId + " from " + requestIp); theErrors = validateWhitelistUser(userId, false); if (theErrors == null) { - List deviceList = repository.findAll(); + List deviceList = repository.findActive(); deviceResponseMap = new HashMap(); for (DeviceDescriptor device : deviceList) { DeviceResponse deviceResponse = null; @@ -552,7 +552,13 @@ public class HueMulator { log.debug("hue api user create requested: " + body + " from " + ipAddress); if (body != null && !body.isEmpty()) { - aNewUser = aGsonHandler.fromJson(body, UserCreateRequest.class); + try { + aNewUser = aGsonHandler.fromJson(body, UserCreateRequest.class); + } catch (Exception e) { + log.warn("Could not add user. Request garbled: " + body); + return aGsonHandler.toJson(HueErrorResponse.createResponse("2", "/", + "Could not add user.", null, null, null).getTheErrors(), HueError[].class); + } newUser = aNewUser.getUsername(); aDeviceType = aNewUser.getDevicetype(); } @@ -644,7 +650,11 @@ public class HueMulator { HueError[] theErrors = validateWhitelistUser(userId, false); if (theErrors != null) return aGsonHandler.toJson(theErrors); - theStateChanges = aGsonHandler.fromJson(body, StateChangeBody.class); + try { + theStateChanges = aGsonHandler.fromJson(body, StateChangeBody.class); + } catch (Exception e) { + theStateChanges = null; + } if (theStateChanges == null) { log.warn("Could not parse state change body. Light state not changed."); return aGsonHandler.toJson(HueErrorResponse.createResponse("2", "/lights/" + lightId, @@ -690,8 +700,11 @@ public class HueMulator { HueError[] theErrors = validateWhitelistUser(userId, false); if (theErrors != null) return aGsonHandler.toJson(theErrors); - - theStateChanges = aGsonHandler.fromJson(body, StateChangeBody.class); + try { + theStateChanges = aGsonHandler.fromJson(body, StateChangeBody.class); + } catch (Exception e) { + theStateChanges = null; + } if (theStateChanges == null) { log.warn("Could not parse state change body. Light state not changed."); return aGsonHandler.toJson(HueErrorResponse.createResponse("2", "/lights/" + lightId, diff --git a/src/main/java/com/bwssystems/HABridge/hue/TimeDecode.java b/src/main/java/com/bwssystems/HABridge/hue/TimeDecode.java new file mode 100644 index 0000000..6d1c835 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/TimeDecode.java @@ -0,0 +1,38 @@ +package com.bwssystems.HABridge.hue; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TimeDecode { + private static final Logger log = LoggerFactory.getLogger(TimeDecode.class); + private static final String TIME_FORMAT = "${time.format("; + private static final String TIME_FORMAT_CLOSE = ")}"; + + /* + * light weight templating here, was going to use free marker but it was a + * bit too heavy for what we were trying to do. + * + * currently provides: time format using Java DateTimeFormatter options + */ + public static String replaceTimeValue(String request) { + if (request == null) { + return null; + } + if (request.contains(TIME_FORMAT)) { + String timeFormatDescriptor = request.substring(request.indexOf(TIME_FORMAT) + TIME_FORMAT.length(), + request.indexOf(TIME_FORMAT_CLOSE)); + + try { + log.debug("Time eval is: " + timeFormatDescriptor); + SimpleDateFormat dateFormat = new SimpleDateFormat(timeFormatDescriptor); + request = request.replace(TIME_FORMAT + timeFormatDescriptor + TIME_FORMAT_CLOSE, dateFormat.format(new Date())); + } catch (Exception e) { + log.warn("Could not format current time: " + timeFormatDescriptor, e); + } + } + return request; + } +} diff --git a/src/main/java/com/bwssystems/HABridge/plugins/exec/CommandHome.java b/src/main/java/com/bwssystems/HABridge/plugins/exec/CommandHome.java index 2e60e9e..0879188 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/exec/CommandHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/exec/CommandHome.java @@ -11,6 +11,7 @@ import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.MultiCommandUtil; +import com.bwssystems.HABridge.hue.TimeDecode; public class CommandHome implements Home { private static final Logger log = LoggerFactory.getLogger(CommandHome.class); @@ -29,20 +30,21 @@ public class CommandHome implements Home { intermediate = anItem.getItem().getAsString().substring(anItem.getItem().getAsString().indexOf("://") + 3); else intermediate = anItem.getItem().getAsString(); - String anError = doExecRequest(intermediate, - BrightnessDecode.calculateIntensity(itensity, targetBri, targetBriInc), lightId); + intermediate = BrightnessDecode.calculateReplaceIntensityValue(intermediate, itensity, targetBri, targetBriInc, false); + intermediate = TimeDecode.replaceTimeValue(intermediate); + String anError = doExecRequest(intermediate, lightId); if (anError != null) { responseString = anError; } return responseString; } - private String doExecRequest(String anItem, int intensity, String lightId) { + private String doExecRequest(String anItem, String lightId) { log.debug("Executing request: " + anItem); String responseString = null; if (anItem != null && !anItem.equalsIgnoreCase("")) { try { - Process p = Runtime.getRuntime().exec(BrightnessDecode.replaceIntensityValue(anItem, intensity, false)); + Process p = Runtime.getRuntime().exec(anItem); log.debug("Process running: " + p.isAlive()); } catch (IOException e) { log.warn("Could not execute request: " + anItem, e); diff --git a/src/main/java/com/bwssystems/HABridge/plugins/hal/HalHome.java b/src/main/java/com/bwssystems/HABridge/plugins/hal/HalHome.java index f57e44f..78a3045 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/hal/HalHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/hal/HalHome.java @@ -19,6 +19,7 @@ import com.bwssystems.HABridge.api.hue.HueErrorResponse; import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.MultiCommandUtil; +import com.bwssystems.HABridge.hue.TimeDecode; import com.bwssystems.HABridge.plugins.http.HTTPHandler; import com.google.gson.Gson; @@ -125,10 +126,13 @@ public class HalHome implements Home { String anUrl = BrightnessDecode.calculateReplaceIntensityValue(anItem.getItem().getAsString(), intensity, targetBri, targetBriInc, false); + anUrl = TimeDecode.replaceTimeValue(anUrl); String aBody = null; - if(anItem.getHttpBody()!= null && !anItem.getHttpBody().isEmpty()) + if(anItem.getHttpBody()!= null && !anItem.getHttpBody().isEmpty()) { aBody = BrightnessDecode.calculateReplaceIntensityValue(anItem.getHttpBody(), intensity, targetBri, targetBriInc, false); + aBody = TimeDecode.replaceTimeValue(aBody); + } // make call if (anHttpHandler.doHttpRequest(anUrl, anItem.getHttpVerb(), anItem.getContentType(), aBody, new Gson().fromJson(anItem.getHttpHeaders(), NameValue[].class)) == null) { diff --git a/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHandler.java b/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHandler.java index ff27556..621585c 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHandler.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHandler.java @@ -4,8 +4,12 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; @@ -30,21 +34,22 @@ import com.bwssystems.HABridge.api.NameValue; public class HTTPHandler { private static final Logger log = LoggerFactory.getLogger(HTTPHandler.class); private HttpClient httpClient; - private CloseableHttpClient httpclientSSL; - private SSLContext sslcontext; - private SSLConnectionSocketFactory sslsf; - private RequestConfig globalConfig; +// private CloseableHttpClient httpclientSSL; +// private SSLContext sslcontext; +// private SSLConnectionSocketFactory sslsf; +// private RequestConfig globalConfig; public HTTPHandler() { httpClient = HttpClients.createDefault(); + // Removed Specific SSL as Apache HttpClient automatically uses SSL if the URI starts with https:// // Trust own CA and all self-signed certs - sslcontext = SSLContexts.createDefault(); +// sslcontext = SSLContexts.createDefault(); // Allow TLSv1 protocol only - sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1,TLSv1.1,TLSv1.2" }, null, - SSLConnectionSocketFactory.getDefaultHostnameVerifier()); - globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build(); - httpclientSSL = HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultRequestConfig(globalConfig).build(); +// sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLS" }, null, +// SSLConnectionSocketFactory.getDefaultHostnameVerifier()); +// globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build(); +// httpclientSSL = HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultRequestConfig(globalConfig).build(); } @@ -96,9 +101,10 @@ public class HTTPHandler { } try { HttpResponse response; - if (url.startsWith("https")) - response = httpclientSSL.execute(request); - else + // Removed Specific SSL as Apache HttpClient automatically uses SSL if the URI starts with https:// +// if (url.startsWith("xyzhttps")) +// response = httpclientSSL.execute(request); +// else response = httpClient.execute(request); log.debug((httpVerb == null ? "GET" : httpVerb) + " execute on URL responded: " + response.getStatusLine().getStatusCode()); @@ -132,18 +138,18 @@ public class HTTPHandler { } - public CloseableHttpClient getHttpclientSSL() { - return httpclientSSL; - } +// public CloseableHttpClient getHttpclientSSL() { +// return httpclientSSL; +// } public void closeHandler() { httpClient = null; - try { - httpclientSSL.close(); - } catch (IOException e) { - // noop - } - httpclientSSL = null; +// try { +// httpclientSSL.close(); +// } catch (IOException e) { +// // noop +// } +// httpclientSSL = null; } } diff --git a/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHome.java b/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHome.java index bbc2db9..e9a68e5 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHome.java @@ -12,6 +12,7 @@ import com.bwssystems.HABridge.api.hue.HueErrorResponse; import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.MultiCommandUtil; +import com.bwssystems.HABridge.hue.TimeDecode; import com.google.gson.Gson; public class HTTPHome implements Home { @@ -46,10 +47,13 @@ public class HTTPHome implements Home { String anUrl = BrightnessDecode.calculateReplaceIntensityValue(anItem.getItem().getAsString(), intensity, targetBri, targetBriInc, false); + anUrl = TimeDecode.replaceTimeValue(anUrl); String aBody = null; - if(anItem.getHttpBody()!= null && !anItem.getHttpBody().isEmpty()) + if(anItem.getHttpBody()!= null && !anItem.getHttpBody().isEmpty()) { aBody = BrightnessDecode.calculateReplaceIntensityValue(anItem.getHttpBody(), intensity, targetBri, targetBriInc, false); + aBody = TimeDecode.replaceTimeValue(aBody); + } // make call if (anHttpHandler.doHttpRequest(anUrl, anItem.getHttpVerb(), anItem.getContentType(), aBody, new Gson().fromJson(anItem.getHttpHeaders(), NameValue[].class)) == null) { diff --git a/src/main/java/com/bwssystems/HABridge/plugins/mqtt/MQTTHome.java b/src/main/java/com/bwssystems/HABridge/plugins/mqtt/MQTTHome.java index 0831407..8beb2d4 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/mqtt/MQTTHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/mqtt/MQTTHome.java @@ -15,6 +15,7 @@ import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.MultiCommandUtil; +import com.bwssystems.HABridge.hue.TimeDecode; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -81,13 +82,13 @@ public class MQTTHome implements Home { if (validMqtt) { String mqttObject = null; if(anItem.getItem().isJsonObject() || anItem.getItem().isJsonArray()) { - String theItem = aGsonHandler.toJson(anItem.getItem()); - mqttObject = BrightnessDecode.calculateReplaceIntensityValue(theItem, - intensity, targetBri, targetBriInc, false); + mqttObject = aGsonHandler.toJson(anItem.getItem()); } else - mqttObject = BrightnessDecode.calculateReplaceIntensityValue(anItem.getItem().getAsString(), - intensity, targetBri, targetBriInc, false); + mqttObject =anItem.getItem().getAsString(); + mqttObject = BrightnessDecode.calculateReplaceIntensityValue(mqttObject, + intensity, targetBri, targetBriInc, false); + mqttObject = TimeDecode.replaceTimeValue(mqttObject); if (mqttObject.substring(0, 1).equalsIgnoreCase("{")) mqttObject = "[" + mqttObject + "]"; MQTTMessage[] mqttMessages = aGsonHandler.fromJson(mqttObject, MQTTMessage[].class); diff --git a/src/main/java/com/bwssystems/HABridge/plugins/tcp/TCPHome.java b/src/main/java/com/bwssystems/HABridge/plugins/tcp/TCPHome.java index 6d8673a..9fc10d8 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/tcp/TCPHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/tcp/TCPHome.java @@ -16,6 +16,7 @@ import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.MultiCommandUtil; +import com.bwssystems.HABridge.hue.TimeDecode; public class TCPHome implements Home { private static final Logger log = LoggerFactory.getLogger(TCPHome.class); @@ -48,11 +49,11 @@ public class TCPHome implements Home { // noop } + theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true); + theUrlBody = TimeDecode.replaceTimeValue(theUrlBody); if (theUrlBody.startsWith("0x")) { - theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true); sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2)); } else { - theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false); sendData = theUrlBody.getBytes(); } diff --git a/src/main/java/com/bwssystems/HABridge/plugins/udp/UDPHome.java b/src/main/java/com/bwssystems/HABridge/plugins/udp/UDPHome.java index 5e0b5b8..bec5f9e 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/udp/UDPHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/udp/UDPHome.java @@ -15,6 +15,7 @@ import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.MultiCommandUtil; +import com.bwssystems.HABridge.hue.TimeDecode; import com.bwssystems.HABridge.util.UDPDatagramSender; public class UDPHome implements Home { @@ -46,22 +47,23 @@ public class UDPHome implements Home { try { IPAddress = InetAddress.getByName(hostAddr); } catch (UnknownHostException e) { - // noop + log.warn("Udp Call, unknown host, continuing..."); + return null; } + theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true); + theUrlBody = TimeDecode.replaceTimeValue(theUrlBody); if (theUrlBody.startsWith("0x")) { - theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true); sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2)); } else { - theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false); sendData = theUrlBody.getBytes(); } try { theUDPDatagramSender.sendUDPResponse(sendData, IPAddress, Integer.parseInt(port)); } catch (NumberFormatException e) { - // noop + log.warn("Udp Call, Number format exception on port, continuing..."); } catch (IOException e) { - // noop + log.warn("IO exception on udp call, continuing..."); } return null; } diff --git a/src/main/resources/public/views/configuration.html b/src/main/resources/public/views/configuration.html index e0cce72..32d6e4b 100644 --- a/src/main/resources/public/views/configuration.html +++ b/src/main/resources/public/views/configuration.html @@ -42,6 +42,7 @@ Name Type Target + Inactive Actions @@ -51,6 +52,7 @@ {{device.name}} {{device.deviceType}} {{device.targetDevice}} + {{device.inactive}}