Compare commits

...

109 Commits

Author SHA1 Message Date
Admin
2f1adf9d4b First round of testing for Security complete, now alpha 2017-03-31 14:57:48 -05:00
Admin
cd5417c2e0 Testing security impl 2017-03-30 15:55:36 -05:00
Admin
ba621fcb85 Continue with security impl, starting to gell.... 2017-03-29 16:41:59 -05:00
bwssystems
7442b0d0ca more security handling 2017-03-28 18:58:33 -05:00
Admin
c275926117 continue security update 2017-03-28 16:39:30 -05:00
Admin
895a9ec99b Continue adding security 2017-03-27 16:50:29 -05:00
Admin
6dfd70dfee Continue security 2017-03-24 16:33:12 -05:00
Admin
0bdb321fd7 Continue security update 2017-03-24 13:38:08 -05:00
Admin
b508a8a16a Continue with security update 2017-03-23 16:36:25 -05:00
Admin
ddee3a42a9 add file 2017-03-22 16:36:24 -05:00
Admin
b000215b26 Continue security implementation 2017-03-22 16:35:19 -05:00
Admin
a2b652907f Star security implementation 2017-03-20 16:30:01 -05:00
bwssystems
6bf1bbc8a8 Update Readme 2017-03-18 17:27:05 -05:00
bwssystems
1a402e425e Merged msbg array fix.
Fixed #558 for null pointer on startup without habridge.conf
2017-03-18 17:26:25 -05:00
BWS Systems
59ef6e88de Merge pull request #559 from msbg/Array-bugfix
Array iteration bugfix
2017-03-18 13:21:03 -05:00
Monica Goward
7d4f953c89 Array iteration bugfix 2017-03-18 17:32:43 +00:00
BWS Systems
3bcec27861 Merge pull request #557 from bwssytems/NewConnectors3.1
New connectors3.1

Fixes #454 Somfy Tahoma plugin enhancement
Fixes #430 hex value needed for dimming enhancement question
Fixes #423 Naming & Notes Enhancement Suggestions enhancement
Fixes #512 Dim handling in V4 question
Fixes #467 Available syntaxes besides ${intensity.percent} ? enhancement
Fixes #416 Use ${intensity.percent} with Harmony Hub enhancement question
Fixes #548 Removing Delay or Repeat in Edit-Device results in bad Config bug
Fixes #537 Minor documentation error bug
Fixes #237 Upgrade resulted in duplicates - Need to keep ha-bridge created user for device enhancement question
Fixes #324 Added a optional webhook for harmony-activity changes enhancement
Fixes #492 Use relative path in URLs enhancement
2017-03-17 16:55:23 -05:00
Admin
bb0ffeb570 Added device data value passing, off state change for brigthness, hex
intensity values, updatd ui to cgo back to device touched after
edit/add.
2017-03-17 16:53:21 -05:00
Admin
0c4292bfd7 Update scrollable table and try implement scroll to row. 2017-03-16 17:05:40 -05:00
Admin
0083c5854f Merge branch 'master' into NewConnectors3.1
Conflicts:
	src/main/java/com/bwssystems/HABridge/BridgeSettings.java
2017-03-16 15:18:39 -05:00
Admin
c13b9bd8f4 upate for editing with fields that need not be saved when empty. 2017-03-16 14:57:21 -05:00
BWS Systems
ca5a6c6667 Merge pull request #454 from msbg/master
Somfy Tahoma plugin
2017-03-16 13:59:43 -05:00
Monica Goward
c9c6d6e66d Cleanup json->pojo mapping 2017-03-16 13:13:53 +00:00
Monica Goward
c8b1827150 Fixes post merge to comply with updated js code json 2017-03-16 12:59:43 +00:00
Monica Goward
d7d83e866e Post latest merge from master 2017-03-16 11:55:02 +00:00
Admin
fb24e9d1a3 Updated whitelist handling to save users when created. Also, added
default test user for habridge UI. Made change state to be validated
against whitelist.
2017-03-13 16:37:42 -05:00
Admin
d15a1c58d0 update readme for harmony webhook addition 2017-03-10 16:22:48 -06:00
BWS Systems
08c87eb3aa Merge pull request #324 from CrEaK/master
Added a optional webhook for harmony-activity changes
2017-03-10 08:59:14 -06:00
BWS Systems
7da4bf13e0 Merge pull request #492 from hobbe/patch-1
Use relative path in URLs
2017-03-10 08:58:43 -06:00
BWS Systems
cd701ca02e Merge pull request #531 from digiltd/patch-2
delete kodivolume.md as there is now a page in the wiki
2017-03-09 09:50:59 -06:00
Sam Turner
1b676632fe delete kodivolume.md as there is now a page in the wiki 2017-03-09 14:58:25 +00:00
BWS Systems
62e366c028 Add pics 2017-03-08 16:03:51 -06:00
BWS Systems
838b86a266 Merge pull request #529 from bwssytems/NewConnectors2
Fixed null http handler issue for close.
Fixed uniqueid on copy.
Update TCP handling to save connection. Added debug to http handler.
Fixed hex handling of intensity. Added handling for \n, \t, \r and so forth in tcp,udp and mqtt.
Fixed HA group support. Added JSON check for new devices. Added Device level filtering. Added decimal percentage.

Home assistant service call for groups from HA bridge device bug question
Fixes #444 opened on Feb 5 by papa-legba

Add filtering for getting of devices in the hue api by IP enhancement
Fixes #526 opened 6 hours ago by bwssytems

Dim Values 0.0 to 1.0 enhancement question
Fixes #401 opened on Jan 25 by beandi

Avoid JSON-Parse error in on/dim/offURL by parsing before saving enhancement question
Fixes #326 opened on Dec 23, 2016 by luke-ff

Nest account linked- JsonSyntaxException: enhancement question
Fixes #266 opened on Nov 22, 2016 by smithski

Xiaomi Yeelight support - TCP requests to use \n \r enhancement question
Fixes #415 opened on Jan 28 by mihaifireball

newline enhancement question
Fixes #448 opened 29 days ago by mbreunich

Using Escape Character enhancement question
Fixes #495 opened 12 days ago by craiglt

how to use "${intensity.math(X/4)}" bug question
Fixes #124 opened on Jun 4, 2016 by painkillerde

Can't get bridge to talk to Netcat to send commands via mochad bug question
Fixes #458 opened 26 days ago by instructor1361

hass HA bridge idle problem enhancement question
Fixes #501 opened 11 days ago by erroldee

HA-Bridge 4.2.0 - NullPointer on DomoticzHome bug
Fixes #507 opened 9 days ago by kaaspad

Unique ID - Not Unique bug question
Fixes #513 opened 5 days ago by merlin051
2017-03-08 15:24:52 -06:00
Admin
ed8fc95782 Fixed HA group support. Added JSON check for new devices. Added Device
level filtering. Added decimal percentage.
2017-03-08 14:59:04 -06:00
Admin
f6cb41b880 Fixed hex handling of intensity. Added handling for \n, \t, \r and so
forth in tcp,udp and mqtt.
2017-03-07 16:36:25 -06:00
Admin
a578aa9fd8 Update TCP handling to save connection. Added debug to http handler. 2017-03-06 16:30:23 -06:00
Admin
7b97bd75ae fixed uniqueid on copy 2017-03-03 16:25:45 -06:00
Admin
4de14217b4 Fixed null http handler issue for close 2017-03-02 16:36:56 -06:00
Olivier B
02918dc49d Merge pull request #2 from hobbe/master
Merge master into patch-1
2017-03-01 14:21:06 +01:00
Olivier B
3a8c64aac6 Merge branch 'patch-1' into master 2017-03-01 14:13:53 +01:00
Olivier B
10df2f1c3e Merge pull request #1 from bwssytems/master
Merge bwssystem/ha-bridge into master
2017-03-01 14:10:18 +01:00
BWS Systems
c7cf48bb6b Merge pull request #499 from bwssytems/NewConnectors1
New connectors and fixes

Fixes #124 
Fixes #396 
Fixes #466 
Fixes #483 
Fixes #488
2017-02-25 10:31:58 -06:00
Admin
54e9303708 merge reqdme 2017-02-24 15:53:35 -06:00
Admin
6b4344bbe8 Merge remote-tracking branch 'origin/master' into NewConnectors1 2017-02-24 15:47:23 -06:00
Admin
745986f08f Finished Domoticz updates 2017-02-24 15:45:45 -06:00
Admin
5e59b33ed7 Fixed hex conversions. Added checks for formats. Finished Bulk add
issues. Fixing Domoticz home and handler  for user/pwd.
2017-02-23 16:47:06 -06:00
Olivier B
c45cb24b20 Use relative path in URLs
This change allows to use HA-Bridge web app from a sub-folder of the web server, eg. http://example.com/habridge.
2017-02-23 21:22:47 +01:00
Admin
adc34ddaa6 Fix issue with LIFx bcast being null. Continue fixing bulk add. 2017-02-22 16:28:31 -06:00
Monica Goward
88f34f3221 Update to use HABridge HTTPHandler 2017-02-22 21:44:17 +00:00
Monica Goward
15223630eb Merge remote-tracking branch 'remotes/bwssystems/master' 2017-02-22 20:50:04 +00:00
BWS Systems
2f456aa0d0 Merge pull request #476 from matsahm/patch-1
Update README.md for Google Home table of sayings
2017-02-20 07:50:47 -06:00
matsahm
201aaa8bca Update README.md 2017-02-18 11:57:32 +01:00
Admin
6e7b48aa5b updated version 2017-02-16 15:22:54 -06:00
Admin
afd1af4094 Merge remote-tracking branch 'origin/master' into NewConnectors1
Conflicts:
	README.md
	pom.xml
2017-02-16 15:22:06 -06:00
Admin
61156e9820 Updated edit screen field layout with bootstrap grid. Added error
checking for HTTP handler.
2017-02-16 13:36:23 -06:00
Admin
6116d37675 update version 2017-02-09 08:41:40 -06:00
Monica Goward
f8474f5f41 Initial fixes and cleanup prior to pull request 1 2017-02-09 14:26:16 +00:00
Admin
0305646b4f Updated LIFX connection for broadcast address and changed the item
collection to be asynchronous with LIFX.
2017-02-08 15:15:53 -06:00
Niklas
3fea7f4f1a Change to Generic HTTPClient 2017-02-08 08:29:37 +01:00
Niklas
44fbaa68f8 Merge remote-tracking branch 'upstream/master' 2017-02-08 08:28:53 +01:00
Admin
dd0032a567 updatre lifx 2017-02-07 15:50:21 -06:00
Monica Goward
470f6b3c15 Merge branch 'master' of https://github.com/bwssytems/ha-bridge 2017-02-06 21:24:44 +00:00
bwssystems
3016712ad8 Fixed closing homes to actually call the close on the homes. Updated the
lowest setting on the test dim function to b 1.

Fixes #438
Fixes #440
2017-02-05 14:37:35 -06:00
Monica Goward
2fbf26a5fa Merge branch 'master' of https://github.com/bwssytems/ha-bridge 2017-02-05 20:02:14 +00:00
BWS Systems
6b3ae1b971 Fixed where version api call is 2017-02-04 18:44:25 -06:00
bwssystems
71258c7e52 Fix some bugs for brightness decode in tcp and udp plugins
Fixes #434
Fixes #435
2017-02-04 18:40:13 -06:00
Monica Goward
ee066f1449 Cleanup 2017-02-04 19:12:20 +00:00
Monica Goward
e5871e61b5 Added some initial docs 2017-02-04 19:04:34 +00:00
Monica Goward
6b8a714959 Re-instating line endings that were erroneously removed 2017-02-04 18:40:28 +00:00
Monica Goward
cf3ec7cfe4 Merge branch 'master' of https://github.com/bwssytems/ha-bridge 2017-02-04 18:30:07 +00:00
Monica Goward
805aac9fde Add Somfy devices 2017-02-04 18:29:50 +00:00
Monica Goward
1221df4c96 add Somfy settings 2017-02-04 18:28:59 +00:00
Monica Goward
1c897e3b36 Add Somfy devices 2017-02-04 18:28:25 +00:00
Admin
0ac8061118 Updated LIFX impl 2017-02-03 09:29:49 -06:00
Monica Goward
65b0d6e470 HTML headers updates only 2017-02-03 12:08:46 +00:00
Monica Goward
99e2243e2d HTML headers updates only 2017-02-03 12:07:44 +00:00
Monica Goward
7a354619d0 Generated pojos from JSON Somfy return data 2017-02-03 12:01:47 +00:00
Monica Goward
2dc245bb96 HTML headers updates only 2017-02-03 11:51:05 +00:00
Admin
c679548bbd Beta impl of LIFX complete 2017-02-02 11:00:50 -06:00
Admin
16a248ba8e Starting impl of LIFX devices 2017-02-01 16:53:08 -06:00
BWS Systems
0ce23c0f00 Update version 2017-02-01 11:38:41 -06:00
BWS Systems
babf81ea31 Update Version 2017-02-01 11:36:51 -06:00
Admin
1c7260600a Minor update for HAL decode issue
Fixes #428
2017-02-01 11:31:04 -06:00
Admin
f1592a1998 Issue with not checking for null 2017-01-30 15:57:35 -06:00
Admin
24dd427fb4 Fixed immediate bugs for
Fixes #129 Hue pass-thru always set to purple
Fixes #406 Dimming with Home Assistant
Fixes #414 Domoticz error retrieving devices
2017-01-30 15:40:58 -06:00
BWS Systems
f8de640f5d added missed quotes 2017-01-27 15:50:00 -06:00
BWS Systems
a091262b80 Merge pull request #413 from bwssytems/targetV4.1.0
Target v4.1.0
2017-01-27 15:30:44 -06:00
BWS Systems
0bcc1628c8 Merge branch 'master' into targetV4.1.0 2017-01-27 15:30:05 -06:00
Admin
ffd95aad87 Remerge 2017-01-27 15:17:37 -06:00
Admin
b5e6e3ad60 v4.1.0 Added feature for Domoticz and fixed some major bugs, ie: mqtt,
and some enhancements.
2017-01-27 15:11:30 -06:00
Admin
611cc7be4a Updated REAMDE to new constructs. Added Time repalcement Added ability
inactivate a device.
2017-01-26 16:46:17 -06:00
Admin
4b7ba0fabe Finished Domoticz impl and secure calls for Home Assistant 2017-01-25 16:24:44 -06:00
Admin
6abe1b082c Rebased to master 2017-01-25 15:39:02 -06:00
Admin
b08a285bd0 One more update for JsonArray detection. 2017-01-25 15:23:48 -06:00
Admin
e68282a230 Merge remote-tracking branch 'origin/master' 2017-01-25 15:21:37 -06:00
Admin
c9d55e26ac Fix immediate issues for JSON decoding
Fixes #391
Fixes #392
Fixes #398
2017-01-25 15:19:46 -06:00
BWS Systems
5a3a02cb34 Added security note. 2017-01-25 12:14:59 -06:00
Admin
a9f48e1f9c Add domoticz web page 2017-01-25 09:16:33 -06:00
bwssystems
8b9fd355b4 More updates 2017-01-25 06:58:51 -06:00
Admin
b4da321368 Continue Domoitcz impl 2017-01-24 16:49:56 -06:00
BWS Systems
881f739c0b Added proxy instructions 2017-01-24 12:47:36 -06:00
BWS Systems
f148c2b9fc updated jar version 2017-01-24 10:28:14 -06:00
Admin
fd486588f5 Fixed more immediate issues
Fixes #272
Fixes #384
Fixes #387
Fixes #389
2017-01-24 09:31:48 -06:00
Admin
d118dd8523 Continue on domoticz impl 2017-01-24 08:29:59 -06:00
BWS Systems
58e1679529 updated jar version 2017-01-23 19:05:29 -06:00
Admin
104d341864 Starting to build Domoticz handler 2017-01-23 16:35:34 -06:00
Admin
7772a3de0f Immediate bug fixes
Fixes #378
Fixes #380

Added FAQ link in Help menu
2017-01-23 15:44:53 -06:00
Niklas
e36145f216 NPE-Check for harmony webhook 2016-12-27 13:33:27 +01:00
Niklas
8cd571c183 Added a optional webhook for harmony-activity changes 2016-12-23 19:57:34 +01:00
96 changed files with 7279 additions and 2384 deletions

311
README.md
View File

@@ -1,15 +1,43 @@
# ha-bridge
Emulates Philips Hue api to other home automation gateways such as an Amazon Echo or Google Home. The Bridge handles basic commands such as "On", "Off" and "brightness" commands of the hue protocol. This bridge can control most devices that have a distinct API.
Here are some diagrams to put this software in perspective.
The Echo Path looks like this:
```
+------------------------+ +------------------------+
+-------------+ | H A +------------------| | A +------------------+ |
| Amazon Echo |----->| U P | ha-bridge core |--->| P | Device to control| |
+-------------+ | E I +------------------| | I +------------------+ |
+------------------------+ +------------------------+
```
The Google Home Path looks like this:
```
+------------------------+ +------------------------+
+-------------+ | H A +------------------| | A +------------------+ |
| Google Home |----->| U P | ha-bridge core |--->| P | Device to control| |
+-------------+ | E I +------------------| | I +------------------+ |
+------------------------+ +------------------------+
```
THe Harmony Hub Path looks like this:
```
+------------------------+ +------------------------+
+-------------+ | H A +------------------| | A +------------------+ |
| Harmony Hub |----->| U P | ha-bridge core |--->| P | Device to control| |
+-------------+ | E I +------------------| | I +------------------+ |
+------------------------+ +------------------------+
```
**SECURITY RISK: If you are unsure on how this software operates and what it exposes to your network, please make sure you understand that it can allow root access to your system. It is best practice to not open this to the Internet through your router as there are no security protocols in place to protect the system. The License agreement states specifically that you use this at your own risk.**
**ATTENTION: This requires a physical Amazon Echo, Dot or Tap and does not work with prototype devices built using the Alexa Voice Service e.g. Amazon's Alexa AVS Sample App and Sam Machin's AlexaPi. The AVS version does not have any capability for Hue Bridge discovery!**
**NOTE: This software does require the user to have knwoledge on how processes run on Linux or Windows with java. Also, an understanding of networking basics will help as well. This system reveives upnp udp multicast packets from devices to be found, so that is some thing to understand. Please make sure you have all your devices use static IP addresses from your router. Most all questions have been answered already. PLEASE USE GOOGLE TO FIND YOUR ANSWERS!**
**NOTE: This software does require the user to have knowledge on how processes run on Linux or Windows with java. Also, an understanding of networking basics will help as well. This system receives upnp udp multicast packets from devices to be found, so that is something to understand. Please make sure you have all your devices use static IP addresses from your router. Most all questions have been answered already. PLEASE USE GOOGLE TO FIND YOUR ANSWERS!**
**NOTE: This software does not control Philips Hue devices directly. A physical Philips Hue Hub is required for that, by which the ha-bridge can then proxy all of your real Hue bridges behind this bridge.**
**FAQ: Please look here for the current FAQs! https://github.com/bwssytems/ha-bridge/wiki/HA-Bridge-FAQs**
In the cases of systems that require authorization and/or have API's that cannot be handled in the current method, a module may need to be built. The Harmony Hub is such a module and so is the Nest module. The Bridge has helpers to build devices for the gateway for the Logitech Harmony Hub, Vera, Vera Lite or Vera Edge, Nest and the ability to proxy all of your real Hue bridges behind this bridge.
In the cases of systems that require authorization and/or have API's that cannot be handled in the current method, a module may need to be built. The Harmony Hub is such a module and so is the Nest module. The Bridge has helpers to build devices for the gateway for the Logitech Harmony Hub, Vera, Vera Lite or Vera Edge, Nest, Somfy Tahoma and the ability to proxy all of your real Hue bridges behind this bridge.
Alternatively the Bridge supports custom calls as well using http/https/udp and tcp such as the LimitlessLED/MiLight bulbs using the UDP protocol. Binary data is supported with UDP/TCP.
@@ -31,16 +59,23 @@ 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.0.jar
java -jar ha-bridge-4.5.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.0.jar is in your /home/pi/habridge directory.
Create the directory and make sure that ha-bridge-4.5.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.0/ha-bridge-4.0.0.jar
pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.5.0/ha-bridge-4.5.0.jar
```
Create the directory and make sure that ha-bridge-4.5.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.5.0/ha-bridge-4.5.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
@@ -59,7 +94,8 @@ 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.0.jar
WorkingDirectory=/home/pi/habridge
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.5.0.jar
[Install]
WantedBy=multi-user.target
@@ -94,7 +130,8 @@ 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.0.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.5.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:
@@ -109,8 +146,56 @@ You should now be running the bridge. Check for errors:
```
pi@raspberrypi:~/habridge $ tail -f habridge-log.txt
```
## Run ha-bridge alongside web server already on port 80
These examples will help you proxy your current webserver requests to the ha-bridge running on a different port, such as 8080.
### Apache Example
Reverse proxy with Apache on Ubuntu linux:
a2enmod proxy
a2enmod proxy_http
a2enmod headers
Added the following lines to my Apache config file “000-default”
```
<VirtualHost *:80>
ProxyPass /api http://localhost:8080/api nocanon
ProxyPassReverse /api http://localhost:8080/api
ProxyRequests Off
AllowEncodedSlashes NoDecode
# Local reverse proxy authorization override
# Most unix distribution deny proxy by default (ie /etc/apache2/mods-enabled/proxy.conf in Ubuntu)
<Proxy http://localhost:8080/api*>
Order deny,allow
Allow from all
</Proxy>
….. (the rest of the VirtualHost config section) …..
</VirtualHost>
```
service apache2 restart
### lighthttpd Example
```
server.modules += ( "mod_proxy" )
proxy.server = (
"/api" =>
(
( "host" => "127.0.0.1",
"port" => "8080"
)
)
)
```
### nginx Example
```
location /api/ {
proxy_pass http://127.0.0.1:8080/api;
}
```
## Available Arguments
Arguments are now deprecated. The ha-bridge will use the old -D arguments and populate the configuration screen, Brisge Control Tab, which can now be saved to a file and will not be needed. There is only one optional argument that overrides and that is the location of the configuration file. The default is the relative path "data/habridge.config".
Arguments are now deprecated. The ha-bridge will use the old -D arguments and populate the configuration screen, Bridge Control Tab, which can now be saved to a file and will not be needed. There is only one optional argument that overrides and that is the location of the configuration file. The default is the relative path "data/habridge.config".
### -Dconfig.file=`<filepath>`
The default location for the configuration file to contain the settings for the bridge is the relative path from where the bridge is started in "data/habridge.config". If you would like a different filename or directory, specify -Dconfig.file=`<directory>/<filename>` explicitly. The command line example:
```
@@ -128,9 +213,14 @@ The default ip address for the bridge to listen on is all interfaces (0.0.0.0).
```
java -jar -Dserver.ip=192.168.1.1 ha-bridge-W.X.Y.jar
```
### -Dsecurity.key=`<Your Key To Encrypt Security Data>`
The default security key is encoded into the Java code. This should not be used as anyone with access to the code can decode your passworsd. To override what the default , specify -Dsecurity.key=`<Your Key To Encrypt Security Data>` explicitly on the command line. This is will prevent any issues if your config file gets haced. The command line example:
```
java -jar -Dsecurity.key=Xfawer354WertSdf321234asd ha-bridge-W.X.Y.jar
```
## HA Bridge Usage and Configuration
This section will cover the basics of configuration and where this configuration can be done. This requires that you have started your bridge process and then have pointed your
favorite web interface by going to the http://<my ip address>:<port> or http://localhost:<port> with port you have assigned. The default quick link is http://localhost for yoru reference.
favorite web interface by going to the http://<my ip address>:<port> or http://localhost:<port> with port you have assigned. The default quick link is http://localhost for your reference.
### The Bridge Devices Tab
This screen allows you to see your devices you have configured for the ha-bridge to present to a controller, such as an Amazon Echo/Dot. It gives you a count of devices as there have been reports that the Echo only supports a limited number, but has been growing as of late, YMMV. You can test each device from this page as this calls the ha-bridge just as a controller would, i.e. the Echo. This is useful to make sure your configuration for each device is correct and for trouble shooting. You can also manages your devices as well by editing and making a new device copy as well as deleting it.
@@ -141,6 +231,8 @@ This is where all of the configuration occurs for what ports and IP's the bridge
This field is used to test the bridge server with the UPNP IP Address and to make sure that the bridge is responding.
#### Bridge Control Buttons
These buttons are for managing the bridge. The Save button is enabled when there is a change to the configuration. The Bridge Reinitialize button will recycle the internal running of the bridge in the java process. The Stop button will stop the java process. The Refresh button will refresh the page and settings.
#### The Security Dialog
This is where you can set the different security settings for the ha-bridge.
#### Configuration Path and File
The default location for the configuration file to contain the settings for the bridge is the relative path from where the bridge is started in "data/habridge.config". If you would like a different filename or directory, specify `<directory>/<filename>` explicitly.
#### Device DB Path and File
@@ -154,31 +246,31 @@ The server defaults to running on port 80. To override what the default is, spec
#### UPNP Response Port
The upnp response port that will be used. The default is 50000.
#### Vera Names and IP Addresses
Provide IP Addresses of your Veras that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Vera and device/scene you configure.
Provide IP Addresses of your Veras that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Vera and device/scene you configure.
#### Harmony Names and IP Addresses
Provide IP Addresses of your Harmony Hubs that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the activity or buttons by the call it receives and send it to the target Harmony Hub and activity/button you configure.
#### Harmony Username
depracated
#### Harmony Password
depracated
Provide IP Addresses of your Harmony Hubs that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the activity or buttons by the call it receives and send it to the target Harmony Hub and activity/button you configure. Also, an option of webhook can be called when the activity changes on the harmony hub that will send an HTTP GET call to the the address of your choosing. This can contain the replacement variables of ${activity.id} and/or ${activity.label}. Example : http://192.168.0.1/activity/${activity.id}/${activity.label} OR http://hook?a=${activity.label}
#### Hue Names and IP Addresses
Provide IP Addresses of your Hue Bridges that you want to proxy through the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will passthru the call it receives to the target Hue and device you configure.
Don't forget - You will need to push the link button when you got to the Hue Tab the first time ater the process comes up. (The user name is not persistent when the process comes up.)
Don't forget - You will need to push the link button when you got to the Hue Tab the first time after the process comes up. (The user name is not persistent when the process comes up.)
#### HAL Names and IP Addresses
Provide IP Addresses of your HAL Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target HAL and device/scene you configure.
#### HAL Token
The token you generate or give to a HAL and must be the same for all HAL's you have identified. This needs to be given if you are using the HAL features.
#### MQTT Client IDs and IP Addresses
Provide Client ID and IP Addresses and ports of your MQTT Brokers that you want to utilize with the bridge. Also, you can provide the username and password if you have secured yourMQTT broker which is optional. When these Client ID and IP's are given, the bridge will be able to publish mqtt messages by the call it receives and send it to the target MQTT Broker you configure. The MQTT Messages Tab will become available to help you build messages.
Provide Client ID and IP Addresses and ports of your MQTT Brokers that you want to utilize with the bridge. Also, you can provide the username and password if you have secured your MQTT broker which is optional. When these Client ID and IP's are given, the bridge will be able to publish MQTT messages by the call it receives and send it to the target MQTT Broker you configure. The MQTT Messages Tab will become available to help you build messages.
#### Nest Username
The user name of the home.nest.com account for the Nest user. This needs to be given if you are using the Nest features. There is no need to give any ip address or host information as this contacts your cloud account.
#### Nest Password
The password for the user name of the home.nest.com account for the Nest user. This needs to be given if you are using the Nest features.
#### Nest Temp Farenheit
This setting allows the value being sent into the bridge to be interpreted as Farenheit or Celsius. The default is to have Farenheit.
#### Nest Temp Fahrenheit
This setting allows the value being sent into the bridge to be interpreted as Fahrenheit or Celsius. The default is to have Fahrenheit.
#### Somfy Tahoma Username
The user name used to login to www.tahomalink.com. This needs to be provided if you're using the Somfy Tahoma features (for connecting to IO Homecontrol used by Velux among others). There is no need to give any IP address or host information as this contacts your cloud account. *Note:* you have to 'turn on' a window to open it, and 'turn off' to close.
#### Somfy Tahoma Password
The password associated with the Somfy Tahoma username above
#### Button Press/Call Item Loop Sleep Interval (ms)
This setting is the time used in between button presses when there is multple buttons in a button device. It also controls the time between multiple items in a custom device call. This is defaulted to 100ms and the number represnts milliseonds (1000 milliseconds = 1 second).
This setting is the time used in between button presses when there is multiple buttons in a button device. It also controls the time between multiple items in a custom device call. This is defaulted to 100ms and the number represents milliseconds (1000 milliseconds = 1 second).
#### Log Messages to Buffer
This controls how many log messages will be kept and displayed on the log tab. This does not affect what is written to the standard output for logging. The default is 512. Changing this will incur more memory usage of the process.
#### UPNP Strict Handling
@@ -192,7 +284,7 @@ This screen displays the last 512 or number of rows defined in the config screen
The bottom part of the Logs Screen has configuration to change the logging levels as it is running. The ROOT is the basic setting and will turn on only top level logging. To set logging at a lower level, select the `Show All Loggers` checkbox and then you can set the explicit level on each of the processes components. The most helpful logger would be setting DEBUG for com.bwssystems.HABridge.hue.HueMulator component. Changing this and then selecting the `Update Log Levels` button applies the new log settings.
### Bridge Device Additions
You must configure devices before you will have any thing for the Echo or other controller that is connected to the ha-bridge to receive.
You must configure devices before you will have anything for the Echo or other controller that is connected to the ha-bridge to receive.
#### Helpers
The easy way to get devices configured is with the use of the helpers for the Vera or Harmony, Nest and Hue to create devices that the bridge will present.
@@ -202,7 +294,44 @@ 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://<your stuff here>` 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://<your secure call here>`. 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 intricacies 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 from device.db:
```
[{"item":<a String that is quoted or another JSON object>,"type":"<atype>"."count":X."delay":X."filterIPs":"<comma separated list of IP addresses that are valid>"."httpVerb":"<GET,PUT,POST>","httpBody":"<body info>","httpHeaders":[{"name":"header name","value":"header value"},{"name":"another header","value":"another value"}],"contentType":"<http content type i.e application/json>"},{"item":<another item>,"type":"<aType>"}]
```
The format of the example is in JSON where the JSON tags equate to the UI labels of the On Items/Dim Items/Off Items. i.e.: JSON item = UI Target Item, JSON type = UI Type, etc...
The Add/Edit tab will show you the fields to fill in for the above in a form, when you have completed putting in the things you want, make sure to hit the `Add` button at the right.
The format of the item can be the default HTTP request which executes the URLs formatted as `http://<your stuff here>` 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://<your secure call here>`. 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", "somfyDevice"
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:
```
@@ -212,44 +341,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://<ip_address>:<port>/<your stuff here>` to send a UDP request. TCP calls are handled the same way as `tcp://<ip_address>:<port>/<your stuff here>`. 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} or ${intensity.percent.hex} for 0-100 or ${intensity.decimal_percent} for 0.00-1.00 or ${intensity.byte} or ${intensity.byte.hex} 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)}" or "${intensity.math(X/4).hex}". 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.
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 parameters 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.
@@ -258,20 +386,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":"exec://C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1","type":"cmdDevice"},{"item":"exec://notepad.exe","type":"cmdDevice"}]
OR
[{"item":"/home/pi/scripts/dothisscript.sh","type":"cmdDevice"}]
```
#### 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.percent.hex}", "${intensity.decimal_percent}", "${intensity.byte}", "${intensity.byte.hex}", "${intensity.math(using X in your calc)}" and "${intensity.math(using X in your calc).hex}".
[{"item":"C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1"},
{"item":"notepad.exe"}]
You can control items that require special calculated values using ${intensity.math(<your expression using "X" as the value to operate on>)} i.e. "${intensity.math(X/4)}".
OR
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
/home/me/startsomething.sh
Also, device data can be inserted into your payloads by the use of "${device.name}", "${device.id}", "${device.uniqueid}", "${device.targetDevice}", "${device.mapId}", "${device.mapType}" and "${device.deviceType}". These work just like the dimming value replacements.
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"}]
OR
[{"item":"udp://192.168.1.1:5000/0x45${intensity.percent}55","type":"udpDevice"}]
[{"item":"exec://notepad.exe"}]
[{"item":"tcp://192.168.1.1:5000/This is the intensity real value ${intensity.byte}","type":"tcpDevice"}]
[{"item":{"clientId":"TestClient","topic":"Yep","message":"This is the time ${time.format(yyyy-MM-ddTHH:mm:ssXXX)}"},"type":"mqttDevice"}]
```
@@ -324,8 +459,8 @@ Dim a light | "Dim the <light name>"
Brighten a light | "Brighten the <light name>"
Set a light brightness to a certain percentage | "Set <light name> to 50%"
Dim/Brighten lights by a certain percentage | "Dim/Brighten <light name> by 50%"
Turn on/off all lights in room | “Turn on/off lights in <room name>"
Turn on/off all lights | “Turn on/off all of the lightsâ€<EFBFBD>
Turn on/off all lights in room | "Turn on/off lights in <room name>"
Turn on/off all lights | "Turn on/off all of the lights"
To see what Home thinks you said, you can ask "Hey Google, What did I say?" or check the history in the app.
@@ -365,19 +500,19 @@ 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
Dimming is also supported by using the expressions ${intensity.percent} for 0-100 or ${intensity.byte} for 0-255 for straight pass through of the value.
Dimming is also supported by using the expressions ${intensity.percent} for 0-100 or ${intensity.decimal_percent} for 0.00-1.00 or ${intensity.byte} for 0-255 for straight pass through of the value.
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.
@@ -389,8 +524,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.
@@ -402,12 +537,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
@@ -416,8 +547,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.
@@ -425,8 +556,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
@@ -451,12 +582,12 @@ 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
Update an existing device using it's ID that was given when the device was created and the update could contain any of the fields that are used and shown in the previous examples when adding a device.
Update an existing device using its ID that was given when the device was created and the update could contain any of the fields that are used and shown in the previous examples when adding a device.
**Note: You must supply all fields of the device in return as this is a replacement update for the given id.**
```
@@ -472,7 +603,7 @@ mapType | string | This identifies what type of source item was used from the he
deviceType | string | This identifies what type of device entry this is. It is used by the system and should be the values of "switch", "scene", "custom", "activity", "button", "thermo", "passthru", "exec", "UDP", "TCP" or "custom". | Required
targetDevice | string | A name given to the target when there are multiples of a given type in the configuration | Optional
onUrl | string | This is the URL or Data Description that is executed for an "on" request. | Required
dimUrl | string | This is the URL or Data Description that is executed for an "on" request when a intensity value is sent. | Optional
dimUrl | string | This is the URL or Data Description that is executed for an "on" request when an intensity value is sent. | Optional
offUrl | string | This is the URL or Data Description that is executed for an "off" request. | Optional
headers | string | This is a header or list of headers that is used for http/https calls when given. | Optional
httpVerb | string | This is used for "custom" calls that the user would like to execute. The values can only be "GET, "PUT", "POST". | Optional
@@ -485,8 +616,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
@@ -495,8 +626,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
@@ -511,15 +642,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
@@ -534,8 +665,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
@@ -548,7 +679,7 @@ This call returns a null json "{}".
### Get HA Bridge Version
Get current version of the HA bridge software.
```
GET http://host:port/api/devices/habridge/version
GET http://host:port/system/habridge/version
```
#### Response
Name | Type | Description
@@ -698,7 +829,7 @@ Show the Harmony Hub's current activity.
GET http://host:port/api/devices/harmony/show
```
#### Response
Only listing the relevant fields that are needed for identity of an activity. TThe example below is representative of an activity.
Only listing the relevant fields that are needed for identity of an activity. The example below is representative of an activity.
Name | Type | Description
-----|-------|-------------
@@ -862,7 +993,7 @@ Allows the user to set the internal state of the light on and off, modify the br
PUT http://host:port/api/<username>/lights/<id>/bridgeupdatestate
```
#### Body arguments
These are examples that can be used in the control of items iwthin the bridge, but for HUE passthru devices, the complete state object is sent.
These are examples that can be used in the control of items within the bridge, but for HUE passthru devices, the complete state object is sent.
Name | Type | Description
-----|-------|-------------
on | bool | On/Off state of the light. On=true, Off=false. Optional
@@ -1034,7 +1165,7 @@ Note that `192.168.1.1` and `12345` are replaced with the actual IP address and
### UPNP description service
The bridge provides the description service which is used by the calling app to interogate access details after it has decided the upnp multicast response is the correct device.
The bridge provides the description service which is used by the calling app to interrogate access details after it has decided the upnp multicast response is the correct device.
#### Get Description
```
GET http://host:80/description.xml
@@ -1089,7 +1220,7 @@ GET http://host:80/description.xml
</root>\n
```
## Development Mode
To turn on development mode so that it will not need an Harmony Hub for testing, use the following extra parm in the command line and the harmony ip and login info will not be needed:
To turn on development mode so that it will not need a Harmony Hub for testing, use the following extra parameter in the command line and the harmony ip and login info will not be needed:
```
java -jar -Ddev.mode=true ha-bridge-0.X.Y.jar
```

View File

@@ -1,144 +0,0 @@
# Kodi volume control using dim commands & JSON-RPC
You can use HA Bridge to adjust the Kodi software volume output. This allows you to use dim commands and set the volume level with percentage values.
### What is JSON-RPC?
The short answer is JSON-RPC an interface to communicate with Kodi. [See the official Kodi Wiki for more info](http://kodi.wiki/view/JSON-RPC_API)
### Setup Kodi to allow control through JSON-RPC
In Kodi navigate to Settings/Services/Control ([screenshot](http://kodi.wiki/view/Settings/Services/Control))
Turn **ON** the following:
- Allow control of Kodi via HTTP
- Allow remote control from applications on this system
- Allow remote control from applications on other systems
Change the **username** to something unique and set a strong **password**.
Make a note of the **PORT**
### Adding the device to HA Bridge
Access the HA Bridge Configuration in your browser and open the **Manual Add** tab.
#### Name
Give the device a unique name that doesnt include **“volume”** as it will cause conflicts with the Echos built in volume controls. A device name of **“cody sound”** works well.
#### Device type
Select **TCP** in the dropdown
### URLs
This section might seem a little long winded and if you know what you are doing then feel free to jump ahead.
#### Building the URL
We need to log into the Kodi web server without having to fill in the popup each time. You can do this by putting the username and password in the URL. It is not a good idea to do this on other websites as it does put your password in clear view, but for your local network it is fine.
Use the example below replacing the relevant sections with the details that you defined in the Kodi settings screen in the first step. Replacing the IP with the address of the machine that Kodi is running on.
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc
```
##### Testing the URL in a browser
Before you continue, open your custom URL in a browser (making sure Kodi is running). If all is working as it should you will see a big page of JSON that starts with:
``` json
{
"description": "JSON-RPC API of XBMC",
"id": "http://xbmc.org/jsonrpc/ServiceDescription.json",
```
If you dont see something that looks like the code above, then go back and double check your settings.
#### The JSON request
The URL is what connects you to Kodi, JSON is what is used to communicate with it. The JSON that is used to set the volume level is:
``` json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
##### Joining the URL and JSON
Join the two together by adding `?request=` to the end of the URL and then add the JSON to the end of the request. You will end up with something like:
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
### Testing the request in a browser
Go ahead and test the combined URL/JSON in a browser changing **100** to whatever level you want to set. Kodi should adjust the volume accordingly, try a few different levels to be sure it is working correctly.
The browser will reformat the URL each time you press return so dont build the URL in the browser bar without making a copy first.
Ideally build the URL in a text document which you can easily edit and then copy/paste each time.
### Prepare the three URLs
You want to end up with three full URLs in your text file, one for each of the commands.
**ON**
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
**DIM**
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":45},"id":1}
```
**OFF**
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":0},"id":1}
```
### Test the three URLS
You should now be able to control the volume of Kodi using the structured URL you built above in a browser.
If you cant get it to work in a browser then you wont be able to get it to work in HA Bridge.
### Manually adding the device
Add a new manual device and give it a name e.g. “Cody Sound”
Set `Device type` to `Custom`
Use the same URL for all three (ON, OFF, DIM)
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request=
```
* `HTTP Verb` to `POST`
* `Content type` to `application/json`
**Content body On**
```json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
**Content body Dim**
```json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":${intensity.percent}},"id":1}
```
**Content body Off**
```json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":0},"id":1}
```
### HA Bridge
Save and test the button in the Bridge Devices tab and hopefully it should turn the volume up in Kodi.
### Controlling the Device
You can use the commands as listed in the [README](https://github.com/bwssytems/ha-bridge#ask-alexa)
“Set Cody Sound to 50 percent”
“Cody Sound to 70 percent”
Remembering that “Turn on Cody Sound” will set the volume to 100%, and “Turn off Cody Sound” will mute.

16
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>4.0.0</version>
<version>4.5.0alpha</version>
<packaging>jar</packaging>
<name>HA Bridge</name>
@@ -48,7 +48,7 @@
<dependency>
<groupId>com.github.bwssytems</groupId>
<artifactId>nest-controller</artifactId>
<version>1.0.13</version>
<version>1.0.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -121,6 +121,16 @@
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>com.github.bwssytems</groupId>
<artifactId>lifx-sdk-java</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
<build>
@@ -217,4 +227,4 @@
</plugin>
</plugins>
</build>
</project>
</project>

View File

@@ -0,0 +1,25 @@
package com.bwssystems.HABridge;
import spark.Request;
public abstract class AuthFramework {
private static final String USER_SESSION_ID = "user";
public AuthFramework() {
// TODO Auto-generated constructor stub
}
public void addAuthenticatedUser(Request request, User u) {
request.session().attribute(USER_SESSION_ID, u);
}
public void removeAuthenticatedUser(Request request) {
request.session().removeAttribute(USER_SESSION_ID);
}
public User getAuthenticatedUser(Request request) {
return request.session().attribute(USER_SESSION_ID);
}
}

View File

@@ -3,6 +3,7 @@ package com.bwssystems.HABridge;
public class BridgeControlDescriptor {
private boolean reinit;
private boolean stop;
private boolean linkButton;
public BridgeControlDescriptor() {
super();
@@ -22,4 +23,12 @@ public class BridgeControlDescriptor {
public void setStop(boolean stop) {
this.stop = stop;
}
public boolean isLinkButton() {
return linkButton;
}
public void setLinkButton(boolean linkButton) {
this.linkButton = linkButton;
}
}

View File

@@ -0,0 +1,214 @@
package com.bwssystems.HABridge;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.Base64;
import java.util.HashMap;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
public class BridgeSecurity extends AuthFramework {
private static final Logger log = LoggerFactory.getLogger(BridgeSecurity.class);
private char[] habridgeKey;
private static final byte[] SALT = {
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
};
private BridgeSecurityDescriptor securityDescriptor;
private boolean settingsChanged;
public BridgeSecurity(char[] theKey, String theData) {
habridgeKey = theKey;
securityDescriptor = null;
settingsChanged = false;
String anError = null;
if(theData != null && !theData.isEmpty()) {
try {
securityDescriptor = new Gson().fromJson(decrypt(theData), BridgeSecurityDescriptor.class);
} catch (JsonSyntaxException e) {
anError = e.getMessage();
} catch (GeneralSecurityException e) {
anError = e.getMessage();
} catch (IOException e) {
anError = e.getMessage();
}
log.warn("Cound not get security data, using default security (none): " + anError);
}
if(theData == null || anError != null) {
securityDescriptor = new BridgeSecurityDescriptor();
}
}
public String getSecurityDescriptorData() throws UnsupportedEncodingException, GeneralSecurityException {
return encrypt(new Gson().toJson(securityDescriptor));
}
public boolean isUseLinkButton() {
return securityDescriptor.isUseLinkButton();
}
public String setPassword(User aUser) throws IOException {
String error = null;
if(aUser != null) {
error = aUser.validate();
if(error == null) {
if(securityDescriptor.getUsers() != null) {
User theUser = securityDescriptor.getUsers().get(aUser.getUsername());
if(theUser != null) {
theUser.setPassword(aUser.getPassword());
theUser.setPassword2(null);
settingsChanged = true;
}
else
error = "User not found";
}
else
error = "User not found";
}
}
else
error = "invalid user object given";
return error;
}
public String addUser(User aUser) throws IOException {
String error = null;
if(aUser != null) {
error = aUser.validate();
if(error == null) {
if(securityDescriptor.getUsers() == null)
securityDescriptor.setUsers(new HashMap<String, User>());
if(securityDescriptor.getUsers().get(aUser.getUsername()) == null) {
securityDescriptor.getUsers().put(aUser.getUsername(), aUser);
settingsChanged = true;
}
else
error = "Invalid request";
}
}
else
error = "invalid user object given";
return error;
}
public String delUser(User aUser) throws IOException {
String error = null;
if(aUser != null) {
if(securityDescriptor.getUsers() != null) {
if(securityDescriptor.getUsers().get(aUser.getUsername()) != null) {
securityDescriptor.getUsers().remove(aUser.getUsername());
settingsChanged = true;
}
else
error = "User not found";
}
}
else
error = "invalid user object given";
return error;
}
public void setExecGarden(String theGarden) {
securityDescriptor.setExecGarden(theGarden);
settingsChanged = true;
}
public String getExecGarden() {
return securityDescriptor.getExecGarden();
}
public void setUseLinkButton(boolean useThis) {
securityDescriptor.setUseLinkButton(useThis);
settingsChanged = true;
}
public boolean isSecureHueApi() {
return securityDescriptor.isSecureHueApi();
}
public void setSecureHueApi(boolean theState) {
securityDescriptor.setSecureHueApi(theState);
}
public SecurityInfo getSecurityInfo() {
SecurityInfo theInfo = new SecurityInfo();
theInfo.setExecGarden(getExecGarden());
theInfo.setUseLinkButton(isUseLinkButton());
theInfo.setSecureHueApi(isSecureHueApi());
theInfo.setSecure(isSecure());
return theInfo;
}
public LoginResult validatePassword(User targetUser) throws IOException {
LoginResult result = new LoginResult();
if(targetUser != null && targetUser.getUsername() != null) {
if(securityDescriptor.getUsers() != null && securityDescriptor.getUsers().get(targetUser.getUsername()) != null) {
User theUser = securityDescriptor.getUsers().get(targetUser.getUsername());
if(theUser.getPassword() != null) {
theUser.setPassword2(targetUser.getPassword());
if(theUser.validatePassword()) {
theUser.setPassword2(null);
result.setUser(targetUser);
}
else
result.setError("user or password not correct");
} else {
result.setError("input password is not set....");
}
}
else
result.setError("user or password not correct");
}
else
result.setError("input user not given");
return result;
}
public boolean isSecure() {
return securityDescriptor.isSecure();
}
public boolean isSettingsChanged() {
return settingsChanged;
}
public void setSettingsChanged(boolean settingsChanged) {
this.settingsChanged = settingsChanged;
}
private String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(habridgeKey));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return base64Encode(pbeCipher.doFinal(property.getBytes("UTF-8")));
}
private static String base64Encode(byte[] bytes) {
return Base64.getEncoder().encodeToString(bytes);
}
private String decrypt(String property) throws GeneralSecurityException, IOException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(habridgeKey));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
}
private static byte[] base64Decode(String property) throws IOException {
return Base64.getDecoder().decode(property);
}
}

View File

@@ -0,0 +1,62 @@
package com.bwssystems.HABridge;
import java.util.Map;
public class BridgeSecurityDescriptor {
private Map<String, User> users;
private boolean useLinkButton;
private String execGarden;
private boolean secureHueApi;
public BridgeSecurityDescriptor() {
super();
this.setUseLinkButton(false);
}
public Map<String, User> getUsers() {
return users;
}
public void setUsers(Map<String, User> users) {
this.users = users;
}
public boolean isUseLinkButton() {
return useLinkButton;
}
public void setUseLinkButton(boolean useLinkButton) {
this.useLinkButton = useLinkButton;
}
public String getExecGarden() {
return execGarden;
}
public void setExecGarden(String execGarden) {
this.execGarden = execGarden;
}
public boolean isSecureHueApi() {
return secureHueApi;
}
public void setSecureHueApi(boolean secureHueApi) {
this.secureHueApi = secureHueApi;
}
public boolean isSecure() {
boolean secureFlag = false;
if(users != null && !users.isEmpty()) {
for (Map.Entry<String, User> entry : users.entrySet())
{
if(entry.getValue().getPassword() != null && !entry.getValue().getPassword().isEmpty()) {
secureFlag = true;
break;
}
}
}
return secureFlag;
}
}

View File

@@ -1,6 +1,7 @@
package com.bwssystems.HABridge;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
@@ -10,6 +11,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.security.GeneralSecurityException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
@@ -26,12 +28,14 @@ public class BridgeSettings extends BackupHandler {
private static final Logger log = LoggerFactory.getLogger(BridgeSettings.class);
private BridgeSettingsDescriptor theBridgeSettings;
private BridgeControlDescriptor bridgeControl;
private BridgeSecurity bridgeSecurity;
public BridgeSettings() {
super();
bridgeControl = new BridgeControlDescriptor();
theBridgeSettings = new BridgeSettingsDescriptor();
String ipV6Stack = System.getProperty("ipV6Stack");
bridgeSecurity = null;
String ipV6Stack = System.getProperty("ipV6Stack");
if(ipV6Stack == null || !ipV6Stack.equalsIgnoreCase("true")) {
System.setProperty("java.net.preferIPv4Stack" , "true");
}
@@ -43,9 +47,13 @@ public class BridgeSettings extends BackupHandler {
public BridgeSettingsDescriptor getBridgeSettingsDescriptor() {
return theBridgeSettings;
}
public BridgeSecurity getBridgeSecurity() {
return bridgeSecurity;
}
public void buildSettings() {
String addressString = null;
String theVeraAddress = null;
String theSomfyAddress = null;
String theHarmonyAddress = null;
String configFileProperty = System.getProperty("config.file");
if(configFileProperty == null) {
@@ -103,6 +111,23 @@ public class BridgeSettings extends BackupHandler {
}
}
theBridgeSettings.setHarmonyAddress(theHarmonyList);
theSomfyAddress = System.getProperty("somfy.address");
IpList theSomfyList = null;
if(theSomfyAddress != null) {
try {
theSomfyList = new Gson().fromJson(theSomfyAddress, IpList.class);
} catch (Exception e) {
try {
theSomfyList = new Gson().fromJson("{devices:[{name:default,ip:" + theSomfyAddress + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse somfy.address, not set with message: " + e.getMessage(), e);
theSomfyList = null;
}
}
}
theBridgeSettings.setSomfyAddress(theSomfyList);
theBridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true")));
theBridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
theBridgeSettings.setButtonsleep(Integer.parseInt(System.getProperty("button.sleep", Configuration.DEFAULT_BUTTON_SLEEP)));
@@ -134,16 +159,10 @@ public class BridgeSettings extends BackupHandler {
if(theBridgeSettings.getUpnpDeviceDb() == null)
theBridgeSettings.setUpnpDeviceDb(Configuration.DEVICE_DB_DIRECTORY);
if(theBridgeSettings.getNumberoflogmessages() == null)
if(theBridgeSettings.getNumberoflogmessages() == null || theBridgeSettings.getNumberoflogmessages() <= 0)
theBridgeSettings.setNumberoflogmessages(new Integer(Configuration.NUMBER_OF_LOG_MESSAGES));
if(theBridgeSettings.getNumberoflogmessages() <= 0)
theBridgeSettings.setNumberoflogmessages(new Integer(Configuration.NUMBER_OF_LOG_MESSAGES));
if(theBridgeSettings.getButtonsleep() == null)
theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP));
if(theBridgeSettings.getButtonsleep() < 0)
if(theBridgeSettings.getButtonsleep() == null || theBridgeSettings.getButtonsleep() < 0)
theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP));
theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera());
@@ -153,11 +172,21 @@ public class BridgeSettings extends BackupHandler {
theBridgeSettings.setHalconfigured(theBridgeSettings.isValidHal());
theBridgeSettings.setMqttconfigured(theBridgeSettings.isValidMQTT());
theBridgeSettings.setHassconfigured(theBridgeSettings.isValidHass());
if(serverPortOverride != null)
theBridgeSettings.setDomoticzconfigured(theBridgeSettings.isValidDomoticz());
theBridgeSettings.setSomfyconfigured(theBridgeSettings.isValidSomfy());
// Lifx is either configured or not, so it does not need an update.
if(serverPortOverride != null)
theBridgeSettings.setServerPort(serverPortOverride);
if(serverIpOverride != null)
theBridgeSettings.setWebaddress(serverIpOverride);
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");
setupInternalTestUser();
String theKey = System.getProperty("security.key");
if(theKey == null)
theKey = "IWantMyPasswordsToBeAbleToBeDecodedPleaseSeeTheReadme";
bridgeSecurity = new BridgeSecurity(theKey.toCharArray(), theBridgeSettings.getSecurityData());
}
public void loadConfig() {
@@ -186,12 +215,34 @@ public class BridgeSettings extends BackupHandler {
log.debug("Save HA Bridge settings.");
Path configPath = Paths.get(theBridgeSettings.getConfigfile());
JsonTransformer aRenderer = new JsonTransformer();
if(bridgeSecurity.isSettingsChanged()) {
try {
newBridgeSettings.setSecurityData(bridgeSecurity.getSecurityDescriptorData());
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bridgeSecurity.setSettingsChanged(false);
}
String jsonValue = aRenderer.render(newBridgeSettings);
configWriter(jsonValue, configPath);
_loadConfig(configPath);
}
public void updateConfigFile() {
log.debug("Save HA Bridge settings.");
Path configPath = Paths.get(theBridgeSettings.getConfigfile());
JsonTransformer aRenderer = new JsonTransformer();
String jsonValue = aRenderer.render(theBridgeSettings);
configWriter(jsonValue, configPath);
_loadConfig(configPath);
}
private void configWriter(String content, Path filePath) {
if(Files.exists(filePath) && !Files.isWritable(filePath)){
log.error("Error file is not writable: " + filePath);
@@ -222,7 +273,8 @@ public class BridgeSettings extends BackupHandler {
perms.add(PosixFilePermission.OWNER_WRITE);
try {
Files.setPosixFilePermissions(filePath, perms);
if(System.getProperty("os.name").toLowerCase().indexOf("win") <= 0)
Files.setPosixFilePermissions(filePath, perms);
} catch(UnsupportedOperationException e) {
log.info("Cannot set permissions for config file on this system as it is not supported. Continuing");
}
@@ -283,4 +335,9 @@ public class BridgeSettings extends BackupHandler {
}
return addressString;
}
private void setupInternalTestUser() {
theBridgeSettings.setupInternalTestUser();
if(theBridgeSettings.isSettingsChanged())
this.updateConfigFile();
}
}

View File

@@ -1,12 +1,21 @@
package com.bwssystems.HABridge;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import com.bwssystems.HABridge.api.hue.HueConstants;
import com.bwssystems.HABridge.api.hue.HueError;
import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.api.hue.WhitelistEntry;
public class BridgeSettingsDescriptor {
private static final String DEFAULT_INTERNAL_USER = "thehabridgeuser";
private static final String DEFAULT_USER_DESCRIPTION = "default_test_user";
private String upnpconfigaddress;
private Integer serverport;
private Integer upnpresponseport;
@@ -38,6 +47,12 @@ public class BridgeSettingsDescriptor {
private IpList hassaddress;
private boolean hassconfigured;
private String hubversion;
private IpList domoticzaddress;
private boolean domoticzconfigured;
private IpList somfyaddress;
private boolean somfyconfigured;
private boolean lifxconfigured;
private String securityData;
public BridgeSettingsDescriptor() {
super();
@@ -45,6 +60,7 @@ public class BridgeSettingsDescriptor {
this.traceupnp = false;
this.nestconfigured = false;
this.veraconfigured = false;
this.somfyconfigured = false;
this.harmonyconfigured = false;
this.hueconfigured = false;
this.halconfigured = false;
@@ -90,9 +106,15 @@ public class BridgeSettingsDescriptor {
public IpList getVeraAddress() {
return veraaddress;
}
public IpList getSomfyAddress() {
return somfyaddress;
}
public void setVeraAddress(IpList veraAddress) {
this.veraaddress = veraAddress;
}
public void setSomfyAddress(IpList somfyAddress) {
this.somfyaddress = somfyAddress;
}
public IpList getHarmonyAddress() {
return harmonyaddress;
}
@@ -126,9 +148,15 @@ public class BridgeSettingsDescriptor {
public boolean isVeraconfigured() {
return veraconfigured;
}
public boolean isSomfyconfigured() {
return somfyconfigured;
}
public void setVeraconfigured(boolean veraconfigured) {
this.veraconfigured = veraconfigured;
}
public void setSomfyconfigured(boolean somfyconfigured) {
this.somfyconfigured = somfyconfigured;
}
public boolean isHarmonyconfigured() {
return harmonyconfigured;
}
@@ -249,6 +277,30 @@ public class BridgeSettingsDescriptor {
public void setHubversion(String hubversion) {
this.hubversion = hubversion;
}
public IpList getDomoticzaddress() {
return domoticzaddress;
}
public void setDomoticzaddress(IpList domoticzaddress) {
this.domoticzaddress = domoticzaddress;
}
public boolean isDomoticzconfigured() {
return domoticzconfigured;
}
public void setDomoticzconfigured(boolean domoticzconfigured) {
this.domoticzconfigured = domoticzconfigured;
}
public boolean isLifxconfigured() {
return lifxconfigured;
}
public void setLifxconfigured(boolean lifxconfigured) {
this.lifxconfigured = lifxconfigured;
}
public String getSecurityData() {
return securityData;
}
public void setSecurityData(String securityData) {
this.securityData = securityData;
}
public Boolean isValidVera() {
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
return false;
@@ -306,4 +358,99 @@ public class BridgeSettingsDescriptor {
return false;
return true;
}
public Boolean isValidDomoticz() {
if(this.getDomoticzaddress() == null || this.getDomoticzaddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getDomoticzaddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
return true;
}
public Boolean isValidSomfy() {
if(this.getSomfyAddress() == null || this.getSomfyAddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getSomfyAddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
return true;
}
public Boolean isValidLifx() {
return this.isLifxconfigured();
}
public HueError[] validateWhitelistUser(String aUser, String userDescription, boolean strict) {
String validUser = null;
boolean found = false;
if (aUser != null && !aUser.equalsIgnoreCase("undefined") && !aUser.equalsIgnoreCase("null")
&& !aUser.equalsIgnoreCase("")) {
if (whitelist != null) {
Set<String> theUserIds = whitelist.keySet();
Iterator<String> userIterator = theUserIds.iterator();
while (userIterator.hasNext()) {
validUser = userIterator.next();
if (validUser.equals(aUser))
found = true;
}
}
}
if(!found && !strict) {
newWhitelistUser(aUser, userDescription);
found = true;
}
if (!found) {
return HueErrorResponse.createResponse("1", "/api/" + aUser, "unauthorized user", null, null, null).getTheErrors();
}
return null;
}
public void newWhitelistUser(String aUser, String userDescription) {
if (whitelist == null) {
whitelist = new HashMap<>();
}
if(userDescription == null)
userDescription = "auto insert user";
whitelist.put(aUser, WhitelistEntry.createEntry(userDescription));
setSettingsChanged(true);
}
public String createWhitelistUser(String userDescription) {
String aUser = getNewUserID();
newWhitelistUser(aUser, userDescription);
return aUser;
}
private String getNewUserID() {
UUID uid = UUID.randomUUID();
StringTokenizer st = new StringTokenizer(uid.toString(), "-");
String newUser = "";
while (st.hasMoreTokens()) {
newUser = newUser + st.nextToken();
}
return newUser;
}
public String getInternalTestUser() {
return DEFAULT_INTERNAL_USER;
}
public void setupInternalTestUser() {
boolean found = false;
if(whitelist != null) {
for (String key : whitelist.keySet()) {
if(key.equals(DEFAULT_INTERNAL_USER)) {
found = true;
break;
}
}
}
if(!found) {
newWhitelistUser(DEFAULT_INTERNAL_USER, DEFAULT_USER_DESCRIPTION);
}
}
}

View File

@@ -1,80 +1,87 @@
package com.bwssystems.HABridge;
import java.util.ArrayList;
public class DeviceMapTypes {
public final static String[] CUSTOM_DEVICE = { "custom", "Custom"};
public final static String[] VERA_DEVICE = { "veraDevice", "Vera Device"};
public final static String[] VERA_SCENE = { "veraScene", "Vera Scene"};
public final static String[] HARMONY_ACTIVITY = { "harmonyActivity", "Harmony Activity"};
public final static String[] HARMONY_BUTTON = { "harmonyButton", "Harmony Button"};
public final static String[] NEST_HOMEAWAY = { "nestHomeAway", "Nest Home Status"};
public final static String[] NEST_THERMO_SET = { "nestThermoSet", "Nest Thermostat"};
public final static String[] HUE_DEVICE = { "hueDevice", "Hue Device"};
public final static String[] HAL_DEVICE = { "halDevice", "HAL Device"};
public final static String[] HAL_BUTTON = { "halButton", "HAL Button"};
public final static String[] HAL_HOME = { "halHome", "HAL Home Status"};
public final static String[] HAL_THERMO_SET = { "halThermoSet", "HAL Thermostat"};
public final static String[] MQTT_MESSAGE = { "mqttMessage", "MQTT Message"};
public final static String[] EXEC_DEVICE_COMPAT = { "exec", "Execute Script/Program"};
public final static String[] CMD_DEVICE = { "cmdDevice", "Execute Command/Script/Program"};
public final static String[] HASS_DEVICE = { "hassDevice", "HomeAssistant Device"};
public final static String[] TCP_DEVICE = { "tcpDevice", "TCP Device"};
public final static String[] TCP_DEVICE_COMPAT = { "TCP", "TCP Device"};
public final static String[] UDP_DEVICE = { "udpDevice", "UDP Device"};
public final static String[] UDP_DEVICE_COMPAT = { "UDP", "UDP Device"};
public final static String[] HTTP_DEVICE = { "httpDevice", "HTTP Device"};
public final static int typeIndex = 0;
public final static int displayIndex = 1;
ArrayList<String[]> deviceMapTypes;
public DeviceMapTypes() {
super();
deviceMapTypes = new ArrayList<String[]>();
deviceMapTypes.add(CMD_DEVICE);
deviceMapTypes.add(HAL_DEVICE);
deviceMapTypes.add(HAL_HOME);
deviceMapTypes.add(HAL_THERMO_SET);
deviceMapTypes.add(HAL_BUTTON);
deviceMapTypes.add(HASS_DEVICE);
deviceMapTypes.add(HTTP_DEVICE);
deviceMapTypes.add(HUE_DEVICE);
deviceMapTypes.add(MQTT_MESSAGE);
deviceMapTypes.add(NEST_HOMEAWAY);
deviceMapTypes.add(NEST_THERMO_SET);
deviceMapTypes.add(TCP_DEVICE);
deviceMapTypes.add(UDP_DEVICE);
deviceMapTypes.add(VERA_DEVICE);
deviceMapTypes.add(VERA_SCENE);
deviceMapTypes.add(HARMONY_ACTIVITY);
deviceMapTypes.add(HARMONY_BUTTON);
}
public static int getTypeIndex() {
return typeIndex;
}
public static int getDisplayIndex() {
return displayIndex;
}
public ArrayList<String[]> getDeviceMapTypes() {
return deviceMapTypes;
}
public Boolean validateType(String type) {
if(type == null || type.trim().isEmpty())
return false;
for(String[] mapType : deviceMapTypes) {
if(type.trim().contentEquals(mapType[typeIndex]))
return true;
}
if(type.trim().contentEquals(EXEC_DEVICE_COMPAT[typeIndex]))
return true;
if(type.trim().contentEquals(TCP_DEVICE_COMPAT[typeIndex]))
return true;
if(type.trim().contentEquals(UDP_DEVICE_COMPAT[typeIndex]))
return true;
return false;
}
package com.bwssystems.HABridge;
import java.util.ArrayList;
public class DeviceMapTypes {
public final static String[] CUSTOM_DEVICE = { "custom", "Custom"};
public final static String[] VERA_DEVICE = { "veraDevice", "Vera Device"};
public final static String[] VERA_SCENE = { "veraScene", "Vera Scene"};
public final static String[] HARMONY_ACTIVITY = { "harmonyActivity", "Harmony Activity"};
public final static String[] HARMONY_BUTTON = { "harmonyButton", "Harmony Button"};
public final static String[] NEST_HOMEAWAY = { "nestHomeAway", "Nest Home Status"};
public final static String[] NEST_THERMO_SET = { "nestThermoSet", "Nest Thermostat"};
public final static String[] HUE_DEVICE = { "hueDevice", "Hue Device"};
public final static String[] HAL_DEVICE = { "halDevice", "HAL Device"};
public final static String[] HAL_BUTTON = { "halButton", "HAL Button"};
public final static String[] HAL_HOME = { "halHome", "HAL Home Status"};
public final static String[] HAL_THERMO_SET = { "halThermoSet", "HAL Thermostat"};
public final static String[] MQTT_MESSAGE = { "mqttMessage", "MQTT Message"};
public final static String[] EXEC_DEVICE_COMPAT = { "exec", "Execute Script/Program"};
public final static String[] CMD_DEVICE = { "cmdDevice", "Execute Command/Script/Program"};
public final static String[] HASS_DEVICE = { "hassDevice", "HomeAssistant Device"};
public final static String[] TCP_DEVICE = { "tcpDevice", "TCP Device"};
public final static String[] TCP_DEVICE_COMPAT = { "TCP", "TCP Device"};
public final static String[] UDP_DEVICE = { "udpDevice", "UDP Device"};
public final static String[] UDP_DEVICE_COMPAT = { "UDP", "UDP Device"};
public final static String[] HTTP_DEVICE = { "httpDevice", "HTTP Device"};
public final static String[] DOMOTICZ_DEVICE = { "domoticzDevice", "Domoticz Device"};
public final static String[] SOMFY_DEVICE = { "somfyDevice", "Somfy Device"};
public final static String[] LIFX_DEVICE = { "lifxDevice", "LIFX Device"};
public final static int typeIndex = 0;
public final static int displayIndex = 1;
ArrayList<String[]> deviceMapTypes;
public DeviceMapTypes() {
super();
deviceMapTypes = new ArrayList<String[]>();
deviceMapTypes.add(CMD_DEVICE);
deviceMapTypes.add(DOMOTICZ_DEVICE);
deviceMapTypes.add(HAL_DEVICE);
deviceMapTypes.add(HAL_HOME);
deviceMapTypes.add(HAL_THERMO_SET);
deviceMapTypes.add(HAL_BUTTON);
deviceMapTypes.add(HARMONY_ACTIVITY);
deviceMapTypes.add(HARMONY_BUTTON);
deviceMapTypes.add(HASS_DEVICE);
deviceMapTypes.add(HTTP_DEVICE);
deviceMapTypes.add(HUE_DEVICE);
deviceMapTypes.add(LIFX_DEVICE);
deviceMapTypes.add(MQTT_MESSAGE);
deviceMapTypes.add(NEST_HOMEAWAY);
deviceMapTypes.add(NEST_THERMO_SET);
deviceMapTypes.add(SOMFY_DEVICE);
deviceMapTypes.add(TCP_DEVICE);
deviceMapTypes.add(UDP_DEVICE);
deviceMapTypes.add(VERA_DEVICE);
deviceMapTypes.add(VERA_SCENE);
deviceMapTypes.add(SOMFY_DEVICE);
}
public static int getTypeIndex() {
return typeIndex;
}
public static int getDisplayIndex() {
return displayIndex;
}
public ArrayList<String[]> getDeviceMapTypes() {
return deviceMapTypes;
}
public Boolean validateType(String type) {
if(type == null || type.trim().isEmpty())
return false;
for(String[] mapType : deviceMapTypes) {
if(type.trim().contentEquals(mapType[typeIndex]))
return true;
}
if(type.trim().contentEquals(EXEC_DEVICE_COMPAT[typeIndex]))
return true;
if(type.trim().contentEquals(TCP_DEVICE_COMPAT[typeIndex]))
return true;
if(type.trim().contentEquals(UDP_DEVICE_COMPAT[typeIndex]))
return true;
return false;
}
}

View File

@@ -45,6 +45,8 @@ public class HABridge {
log.info("HA Bridge (v" + theVersion.getVersion() + ") starting....");
bridgeSettings = new BridgeSettings();
// sparkjava config directive to set html static file location for Jetty
staticFileLocation("/public");
while(!bridgeSettings.getBridgeControl().isStop()) {
bridgeSettings.buildSettings();
log.info("HA Bridge initializing....");
@@ -52,8 +54,9 @@ public class HABridge {
ipAddress(bridgeSettings.getBridgeSettingsDescriptor().getWebaddress());
// sparkjava config directive to set port for the web server to listen on
port(bridgeSettings.getBridgeSettingsDescriptor().getServerPort());
// sparkjava config directive to set html static file location for Jetty
staticFileLocation("/public");
if(!bridgeSettings.getBridgeControl().isReinit())
init();
bridgeSettings.getBridgeControl().setReinit(false);
// setup system control api first
theSystem = new SystemControl(bridgeSettings, theVersion);
theSystem.setupServer();
@@ -65,14 +68,14 @@ public class HABridge {
else {
//Setup the device connection homes through the manager
homeManager = new HomeManager();
homeManager.buildHomes(bridgeSettings.getBridgeSettingsDescriptor(), udpSender);
homeManager.buildHomes(bridgeSettings, udpSender);
// setup the class to handle the resource setup rest api
theResources = new DeviceResource(bridgeSettings.getBridgeSettingsDescriptor(), homeManager);
theResources = new DeviceResource(bridgeSettings, homeManager);
// setup the class to handle the upnp response rest api
theSettingResponder = new UpnpSettingsResource(bridgeSettings.getBridgeSettingsDescriptor());
theSettingResponder.setupServer();
// setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), homeManager);
theHueMulator = new HueMulator(bridgeSettings, theResources.getDeviceRepository(), homeManager);
theHueMulator.setupServer();
// wait for the sparkjava initialization of the rest api classes to be complete
awaitInitialization();
@@ -87,9 +90,17 @@ public class HABridge {
bridgeSettings.save(bridgeSettings.getBridgeSettingsDescriptor());
homeManager.closeHomes();
udpSender.closeResponseSocket();
udpSender = null;
}
bridgeSettings.getBridgeControl().setReinit(false);
stop();
if(!bridgeSettings.getBridgeControl().isStop()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
log.info("HA Bridge (v" + theVersion.getVersion() + ") exiting....");
System.exit(0);

View File

@@ -4,6 +4,6 @@ import com.bwssystems.HABridge.devicemanagmeent.ResourceHandler;
import com.bwssystems.HABridge.hue.HueMulatorHandler;
public interface Home extends HueMulatorHandler, ResourceHandler {
public Home createHome(BridgeSettingsDescriptor bridgeSettings);
public Home createHome(BridgeSettings bridgeSettings);
public void closeHome();
}

View File

@@ -1,17 +1,21 @@
package com.bwssystems.HABridge;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.bwssystems.HABridge.devicemanagmeent.ResourceHandler;
import com.bwssystems.HABridge.plugins.NestBridge.NestHome;
import com.bwssystems.HABridge.plugins.domoticz.DomoticzHome;
import com.bwssystems.HABridge.plugins.exec.CommandHome;
import com.bwssystems.HABridge.plugins.hal.HalHome;
import com.bwssystems.HABridge.plugins.harmony.HarmonyHome;
import com.bwssystems.HABridge.plugins.hass.HassHome;
import com.bwssystems.HABridge.plugins.http.HTTPHome;
import com.bwssystems.HABridge.plugins.hue.HueHome;
import com.bwssystems.HABridge.plugins.lifx.LifxHome;
import com.bwssystems.HABridge.plugins.mqtt.MQTTHome;
import com.bwssystems.HABridge.plugins.somfy.SomfyHome;
import com.bwssystems.HABridge.plugins.tcp.TCPHome;
import com.bwssystems.HABridge.plugins.udp.UDPHome;
import com.bwssystems.HABridge.plugins.vera.VeraHome;
@@ -27,7 +31,7 @@ public class HomeManager {
}
// factory method
public void buildHomes(BridgeSettingsDescriptor bridgeSettings, UDPDatagramSender aUdpDatagramSender) {
public void buildHomes(BridgeSettings bridgeSettings, UDPDatagramSender aUdpDatagramSender) {
Home aHome = null;
//setup the harmony connection if available
aHome = new HarmonyHome(bridgeSettings);
@@ -77,10 +81,22 @@ public class HomeManager {
aHome = new UDPHome(bridgeSettings, aUdpDatagramSender);
homeList.put(DeviceMapTypes.UDP_DEVICE[DeviceMapTypes.typeIndex], aHome);
homeList.put(DeviceMapTypes.UDP_DEVICE_COMPAT[DeviceMapTypes.typeIndex], aHome);
// Setup Vera Home if available
aHome = new VeraHome(bridgeSettings);
resourceList.put(DeviceMapTypes.VERA_DEVICE[DeviceMapTypes.typeIndex], aHome);
resourceList.put(DeviceMapTypes.VERA_SCENE[DeviceMapTypes.typeIndex], aHome);
//setup the Domoticz configuration if available
aHome = new DomoticzHome(bridgeSettings);
homeList.put(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex], aHome);
resourceList.put(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex], aHome);
//setup the Somfy configuration if available
aHome = new SomfyHome(bridgeSettings);
homeList.put(DeviceMapTypes.SOMFY_DEVICE[DeviceMapTypes.typeIndex], aHome);
resourceList.put(DeviceMapTypes.SOMFY_DEVICE[DeviceMapTypes.typeIndex], aHome);
//setup the Lifx configuration if available
aHome = new LifxHome(bridgeSettings);
resourceList.put(DeviceMapTypes.LIFX_DEVICE[DeviceMapTypes.typeIndex], aHome);
homeList.put(DeviceMapTypes.LIFX_DEVICE[DeviceMapTypes.typeIndex], aHome);
}
public Home findHome(String type) {
@@ -91,6 +107,11 @@ public class HomeManager {
}
public void closeHomes() {
Collection<Home> theHomes = homeList.values();
for(Home aHome : theHomes) {
aHome.closeHome();
}
homeList.clear();
homeList = null;
}
}

View File

@@ -0,0 +1,26 @@
package com.bwssystems.HABridge;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LinkButtonPressed extends TimerTask {
private static final Logger log = LoggerFactory.getLogger(LinkButtonPressed.class);
private BridgeControlDescriptor linkDescriptor;
private Timer myTimer;
public LinkButtonPressed(BridgeControlDescriptor theDescriptor, Timer aTimer) {
linkDescriptor = theDescriptor;
myTimer = aTimer;
}
@Override
public void run() {
log.info("Link button time ended....");
linkDescriptor.setLinkButton(false);
myTimer.cancel();
}
}

View File

@@ -0,0 +1,22 @@
package com.bwssystems.HABridge;
public class LoginResult {
private String error;
private User user;
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View File

@@ -2,10 +2,12 @@ package com.bwssystems.HABridge;
public class NamedIP {
private String name;
private String ip;
private String ip;
private String webhook;
private String port;
private String username;
private String password;
private Boolean secure;
public String getName() {
return name;
@@ -19,7 +21,13 @@ public class NamedIP {
public void setIp(String ip) {
this.ip = ip;
}
public String getPort() {
public String getWebhook() {
return webhook;
}
public void setWebhook(final String webhook) {
this.webhook = webhook;
}
public String getPort() {
return port;
}
public void setPort(String port) {
@@ -37,4 +45,10 @@ public class NamedIP {
public void setPassword(String password) {
this.password = password;
}
public Boolean getSecure() {
return secure;
}
public void setSecure(Boolean secure) {
this.secure = secure;
}
}

View File

@@ -0,0 +1,33 @@
package com.bwssystems.HABridge;
public class SecurityInfo {
private boolean useLinkButton;
private String execGarden;
private boolean secureHueApi;
private boolean isSecure;
public boolean isUseLinkButton() {
return useLinkButton;
}
public void setUseLinkButton(boolean useLinkButton) {
this.useLinkButton = useLinkButton;
}
public String getExecGarden() {
return execGarden;
}
public void setExecGarden(String execGarden) {
this.execGarden = execGarden;
}
public boolean isSecureHueApi() {
return secureHueApi;
}
public void setSecureHueApi(boolean secureHueApi) {
this.secureHueApi = secureHueApi;
}
public boolean isSecure() {
return isSecure;
}
public void setSecure(boolean isSecure) {
this.isSecure = isSecure;
}
}

View File

@@ -4,6 +4,8 @@ import static spark.Spark.get;
import static spark.Spark.options;
import static spark.Spark.post;
import static spark.Spark.put;
import static spark.Spark.before;
import static spark.Spark.halt;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -12,6 +14,8 @@ import java.net.MulticastSocket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Timer;
import java.util.Base64;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
@@ -55,17 +59,36 @@ public class SystemControl {
// This function sets up the sparkjava rest calls for the hue api
public void setupServer() {
log.info("System control service started....");
before(SYSTEM_CONTEXT + "/*", (request, response) -> {
if(bridgeSettings.getBridgeSecurity().isSecure()) {
String pathInfo = request.pathInfo();
if(pathInfo == null || (!pathInfo.equals(SYSTEM_CONTEXT + "/login") && !pathInfo.equals(SYSTEM_CONTEXT + "/habridge/version"))) {
User authUser = bridgeSettings.getBridgeSecurity().getAuthenticatedUser(request);
if(authUser == null) {
halt(401, "{\"message\":\"User not authenticated\"}");
}
}
}
});
// http://ip_address:port/system/habridge/version gets the version of this bridge instance
get (SYSTEM_CONTEXT + "/habridge/version", "application/json", (request, response) -> {
get (SYSTEM_CONTEXT + "/habridge/version", (request, response) -> {
log.debug("Get HA Bridge version: v" + version.getVersion());
response.status(HttpStatus.SC_OK);
return "{\"version\":\"" + version.getVersion() + "\"}";
response.type("application/json");
return "{\"version\":\"" + version.getVersion() + "\",\"isSecure\":" + bridgeSettings.getBridgeSecurity().isSecure() + "}";
});
// http://ip_address:port/system/habridge/testuser gets the valid test user for calling the api
get (SYSTEM_CONTEXT + "/habridge/testuser", (request, response) -> {
log.debug("Get HA Bridge testuser: " + bridgeSettings.getBridgeSettingsDescriptor().getInternalTestUser());
response.status(HttpStatus.SC_OK);
response.type("application/json");
return "{\"user\":\"" + bridgeSettings.getBridgeSettingsDescriptor().getInternalTestUser() + "\"}";
});
// http://ip_address:port/system/logmsgs gets the log messages for the bridge
get (SYSTEM_CONTEXT + "/logmsgs", "application/json", (request, response) -> {
get (SYSTEM_CONTEXT + "/logmsgs", (request, response) -> {
log.debug("Get logmsgs.");
response.status(HttpStatus.SC_OK);
String logMsgs;
int count = -1;
if(cyclicBufferAppender == null)
@@ -86,24 +109,192 @@ public class SystemControl {
}
}
logMsgs = logMsgs + "]";
response.status(200);
response.status(HttpStatus.SC_OK);
response.type("application/json");
return logMsgs;
});
// http://ip_address:port/system/logmgmt/loggers gets the logger info for the bridge
get (SYSTEM_CONTEXT + "/logmgmt/loggers/:all", "application/json", (request, response) -> {
get (SYSTEM_CONTEXT + "/logmgmt/loggers/:all", (request, response) -> {
log.debug("Get loggers info with showAll argument: " + request.params(":all"));
Boolean showAll = false;
if(request.params(":all").equals("true"))
showAll = true;
theLogServiceMgr.setShowAll(showAll);
theLogServiceMgr.init();
response.status(200);
response.status(HttpStatus.SC_OK);
response.type("application/json");
return theLogServiceMgr.getConfiguredLoggers();
}, new JsonTransformer());
// http://ip_address:port/system/setpassword CORS request
options(SYSTEM_CONTEXT + "/setpassword", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
// http://ip_address:port/system/setpassword which sets a password for a given user
post(SYSTEM_CONTEXT + "/setpassword", (request, response) -> {
log.debug("setpassword....");
String theDecodedPayload = new String(Base64.getDecoder().decode(request.body()));
User theUser = new Gson().fromJson(theDecodedPayload, User.class);
String errorMessage = bridgeSettings.getBridgeSecurity().setPassword(theUser);
if(errorMessage != null) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "{\"message\":\"" + errorMessage + "\"}";
} else {
response.status(HttpStatus.SC_OK);
bridgeSettings.save(bridgeSettings.getBridgeSettingsDescriptor());
}
if(errorMessage == null)
errorMessage = "{}";
response.type("application/json");
return errorMessage;
});
// http://ip_address:port/system/adduser CORS request
options(SYSTEM_CONTEXT + "/adduser", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
// http://ip_address:port/system/adduser which adds a new user
put(SYSTEM_CONTEXT + "/adduser", (request, response) -> {
log.debug("adduser....");
String theDecodedPayload = new String(Base64.getDecoder().decode(request.body()));
User theUser = new Gson().fromJson(theDecodedPayload, User.class);
String errorMessage = theUser.validate();
if(errorMessage != null) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "{\"message\":\"" + errorMessage + "\"}";
} else {
errorMessage = bridgeSettings.getBridgeSecurity().addUser(theUser);
if(errorMessage == null) {
response.status(HttpStatus.SC_OK);
bridgeSettings.save(bridgeSettings.getBridgeSettingsDescriptor());
} else {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "{\"message\":\"" + errorMessage + "\"}";
}
}
if(errorMessage == null)
errorMessage = "{}";
response.type("application/json");
return errorMessage;
});
// http://ip_address:port/system/deluser CORS request
options(SYSTEM_CONTEXT + "/deluser", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
// http://ip_address:port/system/deluser which dels a user
put(SYSTEM_CONTEXT + "/deluser", (request, response) -> {
log.debug("deluser....");
String theDecodedPayload = new String(Base64.getDecoder().decode(request.body()));
User theUser = new Gson().fromJson(theDecodedPayload, User.class);
String errorMessage = bridgeSettings.getBridgeSecurity().delUser(theUser);
if(errorMessage != null) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "{\"message\":\"" + errorMessage + "\"}";
} else {
response.status(HttpStatus.SC_OK);
bridgeSettings.save(bridgeSettings.getBridgeSettingsDescriptor());
}
if(errorMessage == null)
errorMessage = "{}";
response.type("application/json");
return errorMessage;
});
// http://ip_address:port/system/login CORS request
options(SYSTEM_CONTEXT + "/login", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
// http://ip_address:port/system/login validates the login
post(SYSTEM_CONTEXT + "/login", (request, response) -> {
log.debug("login....");
String theDecodedPayload = new String(Base64.getDecoder().decode(request.body()));
User theUser = new Gson().fromJson(theDecodedPayload, User.class);
LoginResult result = bridgeSettings.getBridgeSecurity().validatePassword(theUser);
if(result.getUser() != null)
bridgeSettings.getBridgeSecurity().addAuthenticatedUser(request, theUser);
response.status(HttpStatus.SC_OK);
response.type("application/json");
return result;
}, new JsonTransformer());
// http://ip_address:port/system/presslinkbutton CORS request
options(SYSTEM_CONTEXT + "/presslinkbutton", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
// http://ip_address:port/system/presslinkbutton which sets the link button for device registration
put(SYSTEM_CONTEXT + "/presslinkbutton", (request, response) -> {
log.info("Link button pressed....");
bridgeSettings.getBridgeControl().setLinkButton(true);
Timer theTimer = new Timer();
theTimer.schedule(new LinkButtonPressed(bridgeSettings.getBridgeControl(), theTimer), 30000);
response.status(HttpStatus.SC_OK);
response.type("application/json");
return "";
}, new JsonTransformer());
// http://ip_address:port/system/securityinfo gets the security info for the bridge
get (SYSTEM_CONTEXT + "/securityinfo", (request, response) -> {
log.debug("Get security info");
response.status(HttpStatus.SC_OK);
response.type("application/json");
return bridgeSettings.getBridgeSecurity().getSecurityInfo();
}, new JsonTransformer());
// http://ip_address:port/system/changesecurityinfo CORS request
options(SYSTEM_CONTEXT + "/changesecurityinfo", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
// http://ip_address:port/system/changesecurityinfo which sets the security settings other than passwords and users
post(SYSTEM_CONTEXT + "/changesecurityinfo", (request, response) -> {
log.debug("changesecurityinfo....");
SecurityInfo theInfo = new Gson().fromJson(request.body(), SecurityInfo.class);
if(theInfo.getExecGarden() != null)
bridgeSettings.getBridgeSecurity().setExecGarden(theInfo.getExecGarden());
bridgeSettings.getBridgeSecurity().setUseLinkButton(theInfo.isUseLinkButton());
bridgeSettings.getBridgeSecurity().setSecureHueApi(theInfo.isSecureHueApi());
bridgeSettings.save(bridgeSettings.getBridgeSettingsDescriptor());
response.status(HttpStatus.SC_OK);
response.type("application/json");
return bridgeSettings.getBridgeSecurity().getSecurityInfo();
}, new JsonTransformer());
// http://ip_address:port/system/logmgmt/update CORS request
options(SYSTEM_CONTEXT + "/logmgmt/update", "application/json", (request, response) -> {
options(SYSTEM_CONTEXT + "/logmgmt/update", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
@@ -112,28 +303,28 @@ public class SystemControl {
return "";
});
// http://ip_address:port/system/logmgmt/update which changes logging parameters for the process
put(SYSTEM_CONTEXT + "/logmgmt/update", "application/json", (request, response) -> {
put(SYSTEM_CONTEXT + "/logmgmt/update", (request, response) -> {
log.debug("update loggers: " + request.body());
response.status(200);
LoggerInfo updateLoggers[];
updateLoggers = new Gson().fromJson(request.body(), LoggerInfo[].class);
LoggingForm theModel = theLogServiceMgr.getModel();
theModel.setUpdatedLoggers(Arrays.asList(updateLoggers));
theLogServiceMgr.updateLogLevels();
response.status(HttpStatus.SC_OK);
response.type("application/json");
return theLogServiceMgr.getConfiguredLoggers();
}, new JsonTransformer());
// http://ip_address:port/system/settings which returns the bridge configuration settings
get(SYSTEM_CONTEXT + "/settings", "application/json", (request, response) -> {
get(SYSTEM_CONTEXT + "/settings", (request, response) -> {
log.debug("bridge settings requested from " + request.ip());
response.status(200);
response.status(HttpStatus.SC_OK);
response.type("application/json");
return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer());
// http://ip_address:port/system/settings CORS request
options(SYSTEM_CONTEXT + "/settings", "application/json", (request, response) -> {
options(SYSTEM_CONTEXT + "/settings", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
@@ -142,17 +333,17 @@ public class SystemControl {
return "";
});
// http://ip_address:port/system/settings which returns the bridge configuration settings
put(SYSTEM_CONTEXT + "/settings", "application/json", (request, response) -> {
put(SYSTEM_CONTEXT + "/settings", (request, response) -> {
log.debug("save bridge settings requested from " + request.ip() + " with body: " + request.body());
BridgeSettingsDescriptor newBridgeSettings = new Gson().fromJson(request.body(), BridgeSettingsDescriptor.class);
bridgeSettings.save(newBridgeSettings);
response.status(200);
response.status(HttpStatus.SC_OK);
response.type("application/json");
return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer());
// http://ip_address:port/system/control/reinit CORS request
options(SYSTEM_CONTEXT + "/control/reinit", "application/json", (request, response) -> {
options(SYSTEM_CONTEXT + "/control/reinit", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
@@ -161,12 +352,14 @@ public class SystemControl {
return "";
});
// http://ip_address:port/system/control/reinit sets the parameter reinit the server
put(SYSTEM_CONTEXT + "/control/reinit", "application/json", (request, response) -> {
put(SYSTEM_CONTEXT + "/control/reinit", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.type("application/json");
return reinit();
});
// http://ip_address:port/system/control/stop CORS request
options(SYSTEM_CONTEXT + "/control/stop", "application/json", (request, response) -> {
options(SYSTEM_CONTEXT + "/control/stop", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
@@ -175,19 +368,22 @@ public class SystemControl {
return "";
});
// http://ip_address:port/system/control/stop sets the parameter stop the server
put(SYSTEM_CONTEXT + "/control/stop", "application/json", (request, response) -> {
put(SYSTEM_CONTEXT + "/control/stop", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.type("application/json");
return stop();
});
// http://ip_address:port/system/backup/available returns a list of config backup filenames
get (SYSTEM_CONTEXT + "/backup/available", "application/json", (request, response) -> {
get (SYSTEM_CONTEXT + "/backup/available", (request, response) -> {
log.debug("Get backup filenames");
response.status(HttpStatus.SC_OK);
response.type("application/json");
return bridgeSettings.getBackups();
}, new JsonTransformer());
// http://ip_address:port/system/backup/create CORS request
options(SYSTEM_CONTEXT + "/backup/create", "application/json", (request, response) -> {
options(SYSTEM_CONTEXT + "/backup/create", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "PUT");
@@ -195,16 +391,18 @@ public class SystemControl {
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
put (SYSTEM_CONTEXT + "/backup/create", "application/json", (request, response) -> {
put (SYSTEM_CONTEXT + "/backup/create", (request, response) -> {
log.debug("Create backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
BackupFilename returnFilename = new BackupFilename();
returnFilename.setFilename(bridgeSettings.backup(aFilename.getFilename()));
response.status(HttpStatus.SC_OK);
response.type("application/json");
return returnFilename;
}, new JsonTransformer());
// http://ip_address:port/system/backup/delete CORS request
options(SYSTEM_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
options(SYSTEM_CONTEXT + "/backup/delete", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
@@ -212,18 +410,20 @@ public class SystemControl {
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (SYSTEM_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
post (SYSTEM_CONTEXT + "/backup/delete", (request, response) -> {
log.debug("Delete backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null)
bridgeSettings.deleteBackup(aFilename.getFilename());
else
log.warn("No filename given for delete backup.");
return null;
response.status(HttpStatus.SC_OK);
response.type("application/json");
return "";
}, new JsonTransformer());
// http://ip_address:port/system/backup/restore CORS request
options(SYSTEM_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
options(SYSTEM_CONTEXT + "/backup/restore", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
@@ -231,7 +431,7 @@ public class SystemControl {
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (SYSTEM_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
post (SYSTEM_CONTEXT + "/backup/restore", (request, response) -> {
log.debug("Restore backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null) {
@@ -240,6 +440,8 @@ public class SystemControl {
}
else
log.warn("No filename given for restore backup.");
response.status(HttpStatus.SC_OK);
response.type("application/json");
return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer());
}

View File

@@ -0,0 +1,65 @@
package com.bwssystems.HABridge;
import spark.utils.StringUtils;
public class User {
private int id;
private String username;
private String password;
private String password2;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword2() {
return password2;
}
public void setPassword2(String password2) {
this.password2 = password2;
}
public String validate() {
String error = null;
if(StringUtils.isEmpty(username)) {
error = "You have to enter a username";
} else if(StringUtils.isEmpty(password)) {
error = "You have to enter a password";
} else if(!password.equals(password2)) {
error = "The two passwords do not match";
}
return error;
}
public boolean validatePassword() {
if(password != null && password2 != null)
return password.equals(password2);
return false;
}
}

View File

@@ -3,11 +3,20 @@ package com.bwssystems.HABridge.api.hue;
import java.util.List;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.google.gson.annotations.SerializedName;
public class GroupResponse {
@SerializedName("action")
private DeviceState action;
@SerializedName("lights")
private String[] lights;
@SerializedName("name")
private String name;
@SerializedName("type")
private String type;
@SerializedName("class")
String class_name;
public DeviceState getAction() {
return action;
}
@@ -27,7 +36,19 @@ public class GroupResponse {
this.name = name;
}
public static GroupResponse createGroupResponse(List<DeviceDescriptor> deviceList) {
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getClass_name() {
return class_name;
}
public void setClass_name(String class_name) {
this.class_name = class_name;
}
public static GroupResponse createDefaultGroupResponse(List<DeviceDescriptor> deviceList) {
String[] theList = new String[deviceList.size()];
int i = 0;
for (DeviceDescriptor device : deviceList) {
@@ -38,6 +59,23 @@ public class GroupResponse {
theResponse.setAction(DeviceState.createDeviceState());
theResponse.setName("Lightset 0");
theResponse.setLights(theList);
theResponse.setType("LightGroup");
return theResponse;
}
public static GroupResponse createOtherGroupResponse(List<DeviceDescriptor> deviceList) {
String[] theList = new String[deviceList.size()];
int i = 0;
for (DeviceDescriptor device : deviceList) {
theList[i] = device.getId();
i++;
}
GroupResponse theResponse = new GroupResponse();
theResponse.setAction(DeviceState.createDeviceState());
theResponse.setName("AGroup");
theResponse.setLights(theList);
theResponse.setType("Room");
theResponse.setClass_name("Other");
return theResponse;
}
}

View File

@@ -12,7 +12,7 @@ import com.google.gson.JsonObject;
public class HueApiResponse {
private Map<String, DeviceResponse> lights;
private Map<String, JsonObject> scenes;
private Map<String, JsonObject> groups;
private Map<String, GroupResponse> groups;
private Map<String, JsonObject> schedules;
private Map<String, JsonObject> sensors;
private Map<String, JsonObject> rules;
@@ -44,11 +44,11 @@ public class HueApiResponse {
this.scenes = scenes;
}
public Map<String, JsonObject> getGroups() {
public Map<String, GroupResponse> getGroups() {
return groups;
}
public void setGroups(Map<String, JsonObject> groups) {
public void setGroups(Map<String, GroupResponse> groups) {
this.groups = groups;
}

View File

@@ -1,7 +1,7 @@
package com.bwssystems.HABridge.api.hue;
public class HueConstants {
public final static String HUB_VERSION = "01036562";
public final static String HUB_VERSION = "01036659";
public final static String API_VERSION = "1.15.0";
public final static String MODEL_ID = "BSB002";
public final static String UUID_PREFIX = "2f402f80-da50-11e1-9b23-";

View File

@@ -56,6 +56,24 @@ public class DeviceDescriptor{
@SerializedName("contentBodyDim")
@Expose
private String contentBodyDim;
@SerializedName("inactive")
@Expose
private boolean inactive;
@SerializedName("noState")
@Expose
private boolean noState;
@SerializedName("offState")
@Expose
private boolean offState;
@SerializedName("requesterAddress")
@Expose
private String requesterAddress;
@SerializedName("description")
@Expose
private String description;
@SerializedName("comments")
@Expose
private String comments;
private DeviceState deviceState;
@@ -197,4 +215,73 @@ public class DeviceDescriptor{
this.deviceState = deviceState;
}
}
public boolean isInactive() {
return inactive;
}
public void setInactive(boolean inactive) {
this.inactive = inactive;
}
public boolean isNoState() {
return noState;
}
public void setNoState(boolean noState) {
this.noState = noState;
}
public boolean isOffState() {
return offState;
}
public void setOffState(boolean offState) {
this.offState = offState;
}
public String getRequesterAddress() {
return requesterAddress;
}
public void setRequesterAddress(String requesterAddress) {
this.requesterAddress = requesterAddress;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getComments() {
return comments;
}
public void setComments(String comments) {
this.comments = comments;
}
public boolean containsType(String aType) {
if(aType == null)
return false;
if(this.mapType != null && this.mapType.contains(aType))
return true;
if(this.deviceType != null && this.deviceType.contains(aType))
return true;
if(this.onUrl != null && this.onUrl.contains(aType))
return true;
if(this.dimUrl != null && this.dimUrl.contains(aType))
return true;
if(this.offUrl != null && this.offUrl.contains(aType))
return true;
return false;
}
}

View File

@@ -74,6 +74,42 @@ public class DeviceRepository extends BackupHandler {
return list;
}
public List<DeviceDescriptor> findActive() {
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>();
for(DeviceDescriptor aDevice : new ArrayList<DeviceDescriptor>(devices.values())) {
if(!aDevice.isInactive())
list.add(aDevice);
}
return list;
}
public List<DeviceDescriptor> findAllByRequester(String anAddress) {
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>(devices.values());
List<DeviceDescriptor> theReturnList = new ArrayList<DeviceDescriptor>();
Iterator<DeviceDescriptor> anIterator = list.iterator();
DeviceDescriptor theDevice;
String theRequesterAddress;
HashMap<String,String > addressMap;
while (anIterator.hasNext()) {
theDevice = anIterator.next();
theRequesterAddress = theDevice.getRequesterAddress();
addressMap = new HashMap<String, String>();
if(theRequesterAddress != null) {
if (theRequesterAddress.contains(",")) {
String[] theArray = theRequesterAddress.split(",");
for (String v : theArray) {
addressMap.put(v.trim(), v.trim());
}
} else
addressMap.put(theRequesterAddress, theRequesterAddress);
}
if (theRequesterAddress == null || theRequesterAddress.length() == 0 || addressMap.containsKey(anAddress))
theReturnList.add(theDevice);
}
return theReturnList;
}
public DeviceDescriptor findOne(String id) {
return devices.get(id);
}

View File

@@ -1,9 +1,11 @@
package com.bwssystems.HABridge.devicemanagmeent;
import static spark.Spark.get;
import static spark.Spark.halt;
import static spark.Spark.options;
import static spark.Spark.post;
import static spark.Spark.put;
import static spark.Spark.before;
import static spark.Spark.delete;
import java.util.Arrays;
@@ -15,15 +17,19 @@ import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.DeviceMapTypes;
import com.bwssystems.HABridge.HomeManager;
import com.bwssystems.HABridge.User;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.BackupFilename;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.dao.DeviceRepository;
import com.bwssystems.HABridge.dao.ErrorMessage;
import com.bwssystems.HABridge.util.JsonTransformer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
spark core server for bridge configuration
@@ -33,11 +39,15 @@ public class DeviceResource {
private static final Logger log = LoggerFactory.getLogger(DeviceResource.class);
private DeviceRepository deviceRepository;
private HomeManager homeManager;
private BridgeSettings bridgeSettings;
private Gson aGsonHandler;
private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post"));
public DeviceResource(BridgeSettingsDescriptor theSettings, HomeManager aHomeManager) {
this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
public DeviceResource(BridgeSettings theSettings, HomeManager aHomeManager) {
bridgeSettings = theSettings;
this.deviceRepository = new DeviceRepository(bridgeSettings.getBridgeSettingsDescriptor().getUpnpDeviceDb());
homeManager = aHomeManager;
aGsonHandler = new GsonBuilder().create();
setupEndpoints();
}
@@ -47,6 +57,15 @@ public class DeviceResource {
private void setupEndpoints() {
log.info("HABridge device management service started.... ");
before(API_CONTEXT + "/*", (request, response) -> {
// This never gets called as the HueMulator class covers this path. This is here for backup
if(bridgeSettings.getBridgeSecurity().isSecure()) {
User authUser = bridgeSettings.getBridgeSecurity().getAuthenticatedUser(request);
if(authUser == null) {
halt(401, "{\"message\":\"User not authenticated\"}");
}
}
});
// http://ip_address:port/api/devices CORS request
options(API_CONTEXT, "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
@@ -65,14 +84,44 @@ public class DeviceResource {
else {
devices = new Gson().fromJson("[" + request.body() + "]", DeviceDescriptor[].class);
}
CallItem[] callItems = null;
String errorMessage = null;
for(int i = 0; i < devices.length; i++) {
if(devices[i].getContentBody() != null ) {
if (devices[i].getContentType() == null || devices[i].getHttpVerb() == null || !supportedVerbs.contains(devices[i].getHttpVerb().toLowerCase())) {
response.status(HttpStatus.SC_BAD_REQUEST);
log.debug("Bad http verb in create a Device(s): " + request.body());
return new ErrorMessage("Bad http verb in create a Device(s): " + request.body() + " ");
errorMessage = "Bad http verb in create device(s) for name: " + devices[i].getName() + " with verb: " + devices[i].getHttpVerb();
log.debug(errorMessage);
return new ErrorMessage(errorMessage);
}
}
try {
if(devices[i].getOnUrl() != null && !devices[i].getOnUrl().isEmpty())
callItems = aGsonHandler.fromJson(devices[i].getOnUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "Bad on URL JSON in create device(s) for name: " + devices[i].getName() + " with on URL: " + devices[i].getOnUrl();
log.debug(errorMessage);
return new ErrorMessage(errorMessage);
}
try {
if(devices[i].getDimUrl() != null && !devices[i].getDimUrl().isEmpty())
callItems = aGsonHandler.fromJson(devices[i].getDimUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "Bad dim URL JSON in create device(s) for name: " + devices[i].getName() + " with dim URL: " + devices[i].getDimUrl();
log.debug(errorMessage);
return new ErrorMessage(errorMessage);
}
try {
if(devices[i].getOffUrl() != null && !devices[i].getOffUrl().isEmpty())
callItems = aGsonHandler.fromJson(devices[i].getOffUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "Bad off URL JSON in create device(s) for name: " + devices[i].getName() + " with off URL: " + devices[i].getOffUrl();
log.debug(errorMessage);
return new ErrorMessage(errorMessage);
}
}
deviceRepository.save(devices);
@@ -213,7 +262,25 @@ public class DeviceResource {
return homeManager.findResource(DeviceMapTypes.HASS_DEVICE[DeviceMapTypes.typeIndex]).getItems(DeviceMapTypes.HASS_DEVICE[DeviceMapTypes.typeIndex]);
}, new JsonTransformer());
get (API_CONTEXT + "/map/types", "application/json", (request, response) -> {
get (API_CONTEXT + "/domoticz/devices", "application/json", (request, response) -> {
log.debug("Get Domoticz Clients");
response.status(HttpStatus.SC_OK);
return homeManager.findResource(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex]).getItems(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex]);
}, new JsonTransformer());
get (API_CONTEXT + "/lifx/devices", "application/json", (request, response) -> {
log.debug("Get LIFX devices");
response.status(HttpStatus.SC_OK);
return homeManager.findResource(DeviceMapTypes.LIFX_DEVICE[DeviceMapTypes.typeIndex]).getItems(DeviceMapTypes.LIFX_DEVICE[DeviceMapTypes.typeIndex]);
}, new JsonTransformer());
get (API_CONTEXT + "/somfy/devices", "application/json", (request, response) -> {
log.debug("Get somfy devices");
response.status(HttpStatus.SC_OK);
return homeManager.findResource(DeviceMapTypes.SOMFY_DEVICE[DeviceMapTypes.typeIndex]).getItems(DeviceMapTypes.SOMFY_DEVICE[DeviceMapTypes.typeIndex]);
}, new JsonTransformer());
get (API_CONTEXT + "/map/types", "application/json", (request, response) -> {
log.debug("Get map types");
return new DeviceMapTypes().getDeviceMapTypes();
}, new JsonTransformer());

View File

@@ -1,24 +1,25 @@
package com.bwssystems.HABridge.hue;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.Conversion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.java.dev.eval.Expression;
public class BrightnessDecode {
private static final Logger log = LoggerFactory.getLogger(BrightnessDecode.class);
private static final String INTENSITY_PERCENT = "${intensity.percent}";
private static final String INTENSITY_DECIMAL_PERCENT = "${intensity.decimal_percent}";
private static final String INTENSITY_BYTE = "${intensity.byte}";
private static final String INTENSITY_MATH = "${intensity.math(";
private static final String INTENSITY_MATH_VALUE = "X";
private static final String INTENSITY_MATH_CLOSE = ")}";
private static final String INTENSITY_MATH_CLOSE_HEX = ").hex}";
private static final String INTENSITY_PERCENT_HEX = "${intensity.percent.hex}";
private static final String INTENSITY_BYTE_HEX = "${intensity.byte.hex}";
public static int calculateIntensity(int setIntensity, Integer targetBri, Integer targetBriInc) {
if (targetBri != null) {
@@ -26,7 +27,7 @@ public class BrightnessDecode {
} else if (targetBriInc != null) {
if ((setIntensity + targetBriInc) <= 0)
setIntensity = targetBriInc;
else if ((setIntensity + targetBriInc) > 255)
else if ((setIntensity + targetBriInc) > 254)
setIntensity = targetBriInc;
else
setIntensity = setIntensity + targetBriInc;
@@ -38,7 +39,7 @@ public class BrightnessDecode {
* 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: intensity.byte : 0-255 brightness. this is raw from
* currently provides: intensity.byte : 0-254 brightness. this is raw from
* the echo intensity.percent : 0-100, adjusted for the vera
* intensity.math(X*1) : where X is the value from the interface call and
* can use net.java.dev.eval math
@@ -47,51 +48,79 @@ public class BrightnessDecode {
if (request == null) {
return null;
}
if (request.contains(INTENSITY_BYTE)) {
if (isHex) {
BigInteger bigInt = BigInteger.valueOf(intensity);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_BYTE, hexValue);
} else {
String intensityByte = String.valueOf(intensity);
request = request.replace(INTENSITY_BYTE, intensityByte);
}
} else if (request.contains(INTENSITY_PERCENT)) {
int percentBrightness = (int) Math.round(intensity / 255.0 * 100);
if (isHex) {
BigInteger bigInt = BigInteger.valueOf(percentBrightness);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_PERCENT, hexValue);
} else {
String intensityPercent = String.valueOf(percentBrightness);
request = request.replace(INTENSITY_PERCENT, intensityPercent);
}
} else if (request.contains(INTENSITY_MATH)) {
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),
request.indexOf(INTENSITY_MATH_CLOSE));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
try {
boolean notDone = true;
String replaceValue = null;
String replaceTarget = null;
int percentBrightness = (int) Math.round(intensity / 255.0 * 100);
float decimalBrightness = (float) (intensity / 255.0);
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
String mathDescriptor = null;
while(notDone) {
notDone = false;
if (request.contains(INTENSITY_BYTE)) {
if (isHex) {
replaceValue = convertToHex(intensity);
} else {
replaceValue = String.valueOf(intensity);
}
replaceTarget = INTENSITY_BYTE;
notDone = true;
} else if (request.contains(INTENSITY_BYTE_HEX)) {
replaceValue = convertToHex(intensity);
replaceTarget = INTENSITY_BYTE_HEX;
notDone = true;
} else if (request.contains(INTENSITY_PERCENT)) {
if (isHex) {
replaceValue = convertToHex(percentBrightness);
} else {
replaceValue = String.valueOf(percentBrightness);
}
replaceTarget = INTENSITY_PERCENT;
notDone = true;
} else if (request.contains(INTENSITY_PERCENT_HEX)) {
replaceValue = convertToHex(percentBrightness);
replaceTarget = INTENSITY_PERCENT_HEX;
notDone = true;
} else if (request.contains(INTENSITY_DECIMAL_PERCENT)) {
replaceValue = String.format("%1.2f", decimalBrightness);
replaceTarget = INTENSITY_DECIMAL_PERCENT;
notDone = true;
} else if (request.contains(INTENSITY_MATH_CLOSE)) {
mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),
request.indexOf(INTENSITY_MATH_CLOSE));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
log.debug("Math eval is: " + mathDescriptor + ", Where " + INTENSITY_MATH_VALUE + " is: "
+ String.valueOf(intensity));
Expression exp = new Expression(mathDescriptor);
BigDecimal result = exp.eval(variables);
Integer endResult = Math.round(result.floatValue());
if (isHex) {
BigInteger bigInt = BigInteger.valueOf(endResult);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, hexValue);
} else {
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE,
endResult.toString());
Integer endResult = calculateMath(variables, mathDescriptor);
if(endResult != null) {
if (isHex) {
replaceValue = convertToHex(endResult);
} else {
replaceValue = endResult.toString();
}
replaceTarget = INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE;
notDone = true;
}
} else if (request.contains(INTENSITY_MATH_CLOSE_HEX)) {
mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),
request.indexOf(INTENSITY_MATH_CLOSE_HEX));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
Integer endResult = calculateMath(variables, mathDescriptor);
if(endResult != null) {
if (isHex) {
replaceValue = convertToHex(endResult);
} else {
replaceValue = endResult.toString();
}
replaceTarget = INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE_HEX;
notDone = true;
}
} catch (Exception e) {
log.warn("Could not execute Math: " + mathDescriptor, e);
}
if(notDone)
request = request.replace(replaceTarget, replaceValue);
}
return request;
}
@@ -100,4 +129,28 @@ public class BrightnessDecode {
public static String calculateReplaceIntensityValue(String request, int theIntensity, Integer targetBri, Integer targetBriInc, boolean isHex) {
return replaceIntensityValue(request, calculateIntensity(theIntensity, targetBri, targetBriInc), isHex);
}
}
// Apache Commons Conversion utils likes little endian too much
private static String convertToHex(int theValue) {
String destHex = "00";
String hexValue = Conversion.intToHex(theValue, 0, destHex, 0, 2);
byte[] theBytes = hexValue.getBytes();
byte[] newBytes = new byte[2];
newBytes[0] = theBytes[1];
newBytes[1] = theBytes[0];
return new String(newBytes);
}
private static Integer calculateMath(Map<String, BigDecimal> variables, String mathDescriptor) {
Integer endResult = null;
try {
Expression exp = new Expression(mathDescriptor);
BigDecimal result = exp.eval(variables);
endResult = Math.round(result.floatValue());
} catch (Exception e) {
log.warn("Could not execute Math: " + mathDescriptor, e);
endResult = null;
}
return endResult;
}
}

View File

@@ -0,0 +1,21 @@
package com.bwssystems.HABridge.hue;
import java.util.List;
public class ColorDecode {
public static String convertCIEtoRGB(List<Double> xy) {
double x;
double y;
double Y;
x = xy.get(0) * 100;
y = xy.get(1) * 100;
Y= y;
double R = 3.240479*((x*Y)/y) + -1.537150*Y + -0.498535*(((1-x-y)*Y)/y);
double G = -0.969256*((x*Y)/y) + 1.875992*Y + 0.041556*(((1-x-y)*Y)/y);
double B = 0.055648*((x*Y)/y) + -0.204043*Y + 1.057311*(((1-x-y)*Y)/y);
return null;
}
}

View File

@@ -0,0 +1,66 @@
package com.bwssystems.HABridge.hue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
public class DeviceDataDecode {
private static final Logger log = LoggerFactory.getLogger(DeviceDataDecode.class);
private static final String DEVICE_ID = "${device.id}";
private static final String DEVICE_UNIQUEID = "${device.uniqueid}";
private static final String DEVICE_NAME = "${device.name}";
private static final String DEVICE_MAPID = "${device.mapId}";
private static final String DEVICE_MAPTYPE = "${device.mapType}";
private static final String DEVICE_DEVICETYPE = "${device.deviceType}";
private static final String DEVICE_TARGETDEVICE = "${device.targetDevice}";
public static String replaceDeviceData(String request, DeviceDescriptor device) {
if (request == null) {
return null;
}
boolean notDone = true;
while(notDone) {
notDone = false;
if (request.contains(DEVICE_ID)) {
request = request.replace(DEVICE_ID, device.getId());
notDone = true;
}
if (request.contains(DEVICE_UNIQUEID)) {
request = request.replace(DEVICE_UNIQUEID, device.getUniqueid());
notDone = true;
}
if (request.contains(DEVICE_NAME)) {
request = request.replace(DEVICE_NAME, device.getName());
notDone = true;
}
if (request.contains(DEVICE_MAPID)) {
request = request.replace(DEVICE_MAPID, device.getMapId());
notDone = true;
}
if (request.contains(DEVICE_MAPTYPE)) {
request = request.replace(DEVICE_MAPTYPE, device.getMapType());
notDone = true;
}
if (request.contains(DEVICE_DEVICETYPE)) {
request = request.replace(DEVICE_DEVICETYPE, device.getDeviceType());
notDone = true;
}
if (request.contains(DEVICE_TARGETDEVICE)) {
request = request.replace(DEVICE_TARGETDEVICE, device.getTargetDevice());
notDone = true;
}
log.debug("Request <<" + request + ">>, not done: " + notDone);
}
return request;
}
}

View File

@@ -1,28 +1,31 @@
package com.bwssystems.HABridge.hue;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.DeviceMapTypes;
import com.bwssystems.HABridge.HomeManager;
import com.bwssystems.HABridge.User;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.UserCreateRequest;
import com.bwssystems.HABridge.api.hue.DeviceResponse;
import com.bwssystems.HABridge.api.hue.DeviceState;
import com.bwssystems.HABridge.api.hue.GroupResponse;
import com.bwssystems.HABridge.api.hue.HueApiResponse;
import com.bwssystems.HABridge.api.hue.HueConfig;
import com.bwssystems.HABridge.api.hue.HueError;
import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.api.hue.HuePublicConfig;
import com.bwssystems.HABridge.api.hue.StateChangeBody;
import com.bwssystems.HABridge.api.hue.WhitelistEntry;
import com.bwssystems.HABridge.dao.*;
import com.bwssystems.HABridge.plugins.hue.HueDeviceIdentifier;
import com.bwssystems.HABridge.plugins.hue.HueHome;
import com.bwssystems.HABridge.util.JsonTransformer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import static spark.Spark.before;
import static spark.Spark.get;
import static spark.Spark.halt;
import static spark.Spark.options;
import static spark.Spark.post;
import static spark.Spark.put;
@@ -33,12 +36,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
/**
* Based on Armzilla's HueMulator - a Philips Hue emulator using sparkjava rest server
@@ -52,13 +51,15 @@ public class HueMulator {
private HomeManager homeManager;
private HueHome myHueHome;
private BridgeSettingsDescriptor bridgeSettings;
private BridgeSettings bridgeSettingMaster;
private Gson aGsonHandler;
private DeviceMapTypes validMapTypes;
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HomeManager aHomeManager) {
public HueMulator(BridgeSettings bridgeMaster, DeviceRepository aDeviceRepository, HomeManager aHomeManager) {
repository = aDeviceRepository;
validMapTypes = new DeviceMapTypes();
bridgeSettings = theBridgeSettings;
bridgeSettingMaster = bridgeMaster;
bridgeSettings = bridgeSettingMaster.getBridgeSettingsDescriptor();
homeManager= aHomeManager;
myHueHome = (HueHome) homeManager.findHome(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]);
aGsonHandler = new GsonBuilder().create();
@@ -67,22 +68,56 @@ public class HueMulator {
// This function sets up the sparkjava rest calls for the hue api
public void setupServer() {
log.info("Hue emulator service started....");
before(HUE_CONTEXT + "/*", (request, response) -> {
if(bridgeSettingMaster.getBridgeSecurity().isSecure()) {
String pathInfo = request.pathInfo();
if(pathInfo != null && pathInfo.contains(HUE_CONTEXT + "/devices")) {
User authUser = bridgeSettingMaster.getBridgeSecurity().getAuthenticatedUser(request);
if(authUser == null) {
halt(401, "{\"message\":\"User not authenticated\"}");
}
} else if (bridgeSettingMaster.getBridgeSecurity().isSecureHueApi()) {
User authUser = bridgeSettingMaster.getBridgeSecurity().getAuthenticatedUser(request);
if(authUser == null) {
halt(401, "{\"message\":\"User not authenticated\"}");
}
}
}
});
// http://ip_address:port/api/{userId}/groups returns json objects of
// all groups configured
get(HUE_CONTEXT + "/:userid/groups", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
return basicListHandler("groups", request.params(":userid"), request.ip());
});
return groupsListHandler(request.params(":userid"), request.ip());
} , new JsonTransformer());
// http://ip_address:port/api/{userId}/groups/{groupId} returns json
// object for specified group. Only 0 is supported
get(HUE_CONTEXT + "/:userid/groups/:groupid", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
return groupsListHandler(request.params(":groupid"), request.params(":userid"), request.ip());
return groupsIdHandler(request.params(":groupid"), request.params(":userid"), request.ip());
} , new JsonTransformer());
// http://ip_address:port/:userid/groups CORS request
options(HUE_CONTEXT + "/:userid/groups", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html");
return "";
});
// http://ip_address:port/:userid/groups
// dummy handler
post(HUE_CONTEXT + "/:userid/groups", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
log.debug("group add requested from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
return "[{\"success\":{\"id\":\"1\"}}]";
});
// http://ip_address:port/api/{userId}/scenes returns json objects of
// all scenes configured
get(HUE_CONTEXT + "/:userid/scenes", "application/json", (request, response) -> {
@@ -91,6 +126,24 @@ public class HueMulator {
response.status(HttpStatus.SC_OK);
return basicListHandler("scenes", request.params(":userid"), request.ip());
});
// http://ip_address:port/:userid/scenes CORS request
options(HUE_CONTEXT + "/:userid/scenes", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html");
return "";
});
// http://ip_address:port/:userid/scenes
// dummy handler
post(HUE_CONTEXT + "/:userid/scenes", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
log.debug("scene add requested from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
return "[{\"success\":{\"id\":\"1\"}}]";
});
// http://ip_address:port/api/{userId}/schedules returns json objects of
// all schedules configured
get(HUE_CONTEXT + "/:userid/schedules", "application/json", (request, response) -> {
@@ -99,6 +152,24 @@ public class HueMulator {
response.status(HttpStatus.SC_OK);
return basicListHandler("schedules", request.params(":userid"), request.ip());
});
// http://ip_address:port/:userid/schedules CORS request
options(HUE_CONTEXT + "/:userid/schedules", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html");
return "";
});
// http://ip_address:port/:userid/schedules
// dummy handler
post(HUE_CONTEXT + "/:userid/schedules", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
log.debug("schedules add requested from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
return "[{\"success\":{\"id\":\"1\"}}]";
});
// http://ip_address:port/api/{userId}/sensors returns json objects of
// all sensors configured
get(HUE_CONTEXT + "/:userid/sensors", "application/json", (request, response) -> {
@@ -107,6 +178,24 @@ public class HueMulator {
response.status(HttpStatus.SC_OK);
return basicListHandler("sensors", request.params(":userid"), request.ip());
});
// http://ip_address:port/:userid/sensors CORS request
options(HUE_CONTEXT + "/:userid/sensors", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html");
return "";
});
// http://ip_address:port/:userid/sensors
// dummy handler
post(HUE_CONTEXT + "/:userid/sensors", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
log.debug("sensors add requested from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
return "[{\"success\":{\"id\":\"1\"}}]";
});
// http://ip_address:port/api/{userId}/rules returns json objects of all
// rules configured
get(HUE_CONTEXT + "/:userid/rules", "application/json", (request, response) -> {
@@ -115,6 +204,24 @@ public class HueMulator {
response.status(HttpStatus.SC_OK);
return basicListHandler("rules", request.params(":userid"), request.ip());
});
// http://ip_address:port/:userid/rules CORS request
options(HUE_CONTEXT + "/:userid/rules", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html");
return "";
});
// http://ip_address:port/:userid/rules
// dummy handler
post(HUE_CONTEXT + "/:userid/rules", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
log.debug("rules add requested from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
return "[{\"success\":{\"id\":\"1\"}}]";
});
// http://ip_address:port/api/{userId}/resourcelinks returns json
// objects of all resourcelinks configured
get(HUE_CONTEXT + "/:userid/resourcelinks", "application/json", (request, response) -> {
@@ -123,6 +230,24 @@ public class HueMulator {
response.status(HttpStatus.SC_OK);
return basicListHandler("resourcelinks", request.params(":userid"), request.ip());
});
// http://ip_address:port/:userid/resourcelinks CORS request
options(HUE_CONTEXT + "/:userid/resourcelinks", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html");
return "";
});
// http://ip_address:port/:userid/resourcelinks
// dummy handler
post(HUE_CONTEXT + "/:userid/resourcelinks", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
log.debug("resourcelinks add requested from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
return "[{\"success\":{\"id\":\"1\"}}]";
});
// http://ip_address:port/api/{userId}/lights returns json objects of
// all lights configured
get(HUE_CONTEXT + "/:userid/lights", "application/json", (request, response) -> {
@@ -194,6 +319,28 @@ public class HueMulator {
response.status(HttpStatus.SC_OK);
return getConfig(request.params(":userid"), request.ip());
} , new JsonTransformer());
// http://ip_address:port/:userid/config CORS request
options(HUE_CONTEXT + "/:userid/config", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html");
return "";
});
// http://ip_address:port/:userid/config uses json
// object to set the config. this is to handle swupdates
put(HUE_CONTEXT + "/:userid/config", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
log.debug("Config change requested from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
HueConfig aConfig = aGsonHandler.fromJson(request.body(), HueConfig.class);
if(aConfig.getPortalservices() != null) {
return "[{\"success\":{\"/config/portalservices\":true}}]";
}
return "[{\"success\":{\"/config/name\":\"My bridge\"}}]";
});
// http://ip_address:port/api/{userId} returns json objects for the full
// state
@@ -263,7 +410,7 @@ public class HueMulator {
}
private String formatSuccessHueResponse(StateChangeBody stateChanges, String body, String lightId,
DeviceState deviceState, Integer targetBri, Integer targetBriInc) {
DeviceState deviceState, Integer targetBri, Integer targetBriInc, boolean offState) {
String responseString = "[";
boolean notFirstChange = false;
@@ -276,7 +423,9 @@ public class HueMulator {
}
if (deviceState != null) {
deviceState.setOn(stateChanges.isOn());
if(!deviceState.isOn() && deviceState.getBri() == 255)
if(!deviceState.isOn() && deviceState.getBri() == 254)
deviceState.setBri(0);
if(!deviceState.isOn() && offState)
deviceState.setBri(0);
}
notFirstChange = true;
@@ -416,7 +565,7 @@ public class HueMulator {
}
if(deviceState.isOn() && deviceState.getBri() <= 0)
deviceState.setBri(255);
deviceState.setBri(254);
if(!deviceState.isOn() && (targetBri != null || targetBriInc != null))
deviceState.setOn(true);
@@ -426,50 +575,6 @@ public class HueMulator {
return responseString;
}
private String getNewUserID() {
UUID uid = UUID.randomUUID();
StringTokenizer st = new StringTokenizer(uid.toString(), "-");
String newUser = "";
while (st.hasMoreTokens()) {
newUser = newUser + st.nextToken();
}
return newUser;
}
private HueError[] validateWhitelistUser(String aUser, boolean strict) {
String validUser = null;
boolean found = false;
if (aUser != null && !aUser.equalsIgnoreCase("undefined") && !aUser.equalsIgnoreCase("null")
&& !aUser.equalsIgnoreCase("")) {
if (bridgeSettings.getWhitelist() != null) {
Set<String> theUserIds = bridgeSettings.getWhitelist().keySet();
Iterator<String> userIterator = theUserIds.iterator();
while (userIterator.hasNext()) {
validUser = userIterator.next();
if (validUser.equals(aUser))
found = true;
}
}
if (!found && !strict) {
if (bridgeSettings.getWhitelist() == null) {
Map<String, WhitelistEntry> awhitelist = new HashMap<>();
bridgeSettings.setWhitelist(awhitelist);
}
bridgeSettings.getWhitelist().put(aUser, WhitelistEntry.createEntry("auto insert user"));
bridgeSettings.setSettingsChanged(true);
found = true;
}
}
if (!found) {
log.debug("Valudate user, No User supplied");
return HueErrorResponse.createResponse("1", "/api/" + aUser, "unauthorized user", null, null, null).getTheErrors();
}
return null;
}
private Boolean filterByRequester(String requesterFilterList, String anAddress) {
if (requesterFilterList == null || requesterFilterList.length() == 0)
return true;
@@ -490,20 +595,48 @@ public class HueMulator {
private String basicListHandler(String type, String userId, String requestIp) {
log.debug("hue " + type + " list requested: " + userId + " from " + requestIp);
HueError[] theErrors = validateWhitelistUser(userId, false);
if (theErrors != null)
HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors != null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
return aGsonHandler.toJson(theErrors);
}
return "{}";
}
private Object groupsListHandler(String groupId, String userId, String requestIp) {
log.debug("hue group 0 list requested: " + userId + " from " + requestIp);
private Object groupsListHandler(String userId, String requestIp) {
log.debug("hue group list requested: " + userId + " from " + requestIp);
HueError[] theErrors = null;
theErrors = validateWhitelistUser(userId, false);
Map<String, GroupResponse> groupResponseMap = null;
theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
groupResponseMap = new HashMap<String, GroupResponse>();
groupResponseMap.put("1", (GroupResponse) this.groupsIdHandler("1", userId, requestIp));
return groupResponseMap;
}
return theErrors;
}
private Object groupsIdHandler(String groupId, String userId, String requestIp) {
log.debug("hue group id: <" + groupId + "> requested: " + userId + " from " + requestIp);
HueError[] theErrors = null;
theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
if (groupId.equalsIgnoreCase("0")) {
GroupResponse theResponse = GroupResponse.createGroupResponse(repository.findAll());
GroupResponse theResponse = GroupResponse.createDefaultGroupResponse(repository.findActive());
return theResponse;
}
if (!groupId.equalsIgnoreCase("0")) {
GroupResponse theResponse = GroupResponse.createOtherGroupResponse(repository.findActive());
return theResponse;
}
theErrors = HueErrorResponse.createResponse("3", userId + "/groups/" + groupId, "Object not found", null, null, null).getTheErrors();
@@ -518,20 +651,40 @@ public class HueMulator {
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue lights list requested: " + userId + " from " + requestIp);
log.debug("hue lights list requested: " + userId + " from " + requestIp);
theErrors = validateWhitelistUser(userId, false);
theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors == null) {
List<DeviceDescriptor> deviceList = repository.findAll();
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
List<DeviceDescriptor> deviceList = repository.findAllByRequester(requestIp);
// List<DeviceDescriptor> deviceList = repository.findActive();
deviceResponseMap = new HashMap<String, DeviceResponse>();
for (DeviceDescriptor device : deviceList) {
DeviceResponse deviceResponse = null;
if ((device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]))) {
HueDeviceIdentifier deviceId = aGsonHandler.fromJson(device.getOnUrl(), HueDeviceIdentifier.class);
deviceResponse = myHueHome.getHueDeviceInfo(deviceId, device);
if(!device.isInactive()) {
if (device.containsType(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) {
CallItem[] callItems = null;
try {
if(device.getOnUrl() != null)
callItems = aGsonHandler.fromJson(device.getOnUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
log.warn("Could not decode Json for url items to get Hue state for device: " + device.getName());
callItems = null;
}
for (int i = 0; callItems != null && i < callItems.length; i++) {
if((callItems[i].getType() != null && callItems[i].getType().equals(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) ||
(callItems[i].getItem().getAsString().contains("hueName"))) {
deviceResponse = myHueHome.getHueDeviceInfo(callItems[i], device);
i = callItems.length;
}
}
}
if (deviceResponse == null)
deviceResponse = DeviceResponse.createResponse(device);
deviceResponseMap.put(device.getId(), deviceResponse);
}
if (deviceResponse == null)
deviceResponse = DeviceResponse.createResponse(device);
deviceResponseMap.put(device.getId(), deviceResponse);
}
}
@@ -545,37 +698,59 @@ public class HueMulator {
UserCreateRequest aNewUser = null;
String newUser = null;
String aDeviceType = null;
boolean toContinue = false;
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested: " + body + " from " + ipAddress);
log.debug("hue api user create requested: " + body + " from " + ipAddress);
if (body != null && !body.isEmpty()) {
aNewUser = aGsonHandler.fromJson(body, UserCreateRequest.class);
newUser = aNewUser.getUsername();
aDeviceType = aNewUser.getDevicetype();
}
if (newUser == null)
newUser = getNewUserID();
validateWhitelistUser(newUser, false);
if (aDeviceType == null)
aDeviceType = "<not given>";
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested for device type: " + aDeviceType + " and username: "
+ newUser + (followingSlash ? " /api/ called" : ""));
log.debug("hue api user create requested for device type: " + aDeviceType + " and username: " + newUser + (followingSlash ? " /api/ called" : ""));
return "[{\"success\":{\"username\":\"" + newUser + "\"}}]";
if(bridgeSettingMaster.getBridgeSecurity().isUseLinkButton() && bridgeSettingMaster.getBridgeControl().isLinkButton())
toContinue = true;
else if(!bridgeSettingMaster.getBridgeSecurity().isUseLinkButton())
toContinue = true;
if(toContinue) {
log.debug("hue api user create requested: " + body + " from " + ipAddress);
if (body != null && !body.isEmpty()) {
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();
}
if (aDeviceType == null)
aDeviceType = "<not given>";
if (newUser == null) {
newUser = bridgeSettings.createWhitelistUser(aDeviceType);
}
else {
bridgeSettings.validateWhitelistUser(newUser, aDeviceType, false);
}
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested for device type: " + aDeviceType + " and username: "
+ newUser + (followingSlash ? " /api/ called" : ""));
log.debug("hue api user create requested for device type: " + aDeviceType + " and username: " + newUser + (followingSlash ? " /api/ called" : ""));
return "[{\"success\":{\"username\":\"" + newUser + "\"}}]";
}
return aGsonHandler.toJson(HueErrorResponse.createResponse("1", "/api/", "unauthorized user", null, null, null).getTheErrors());
}
private Object getConfig(String userId, String ipAddress) {
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api/:userid/config config requested: " + userId + " from " + ipAddress);
log.debug("hue api config requested: " + userId + " from " + ipAddress);
if (validateWhitelistUser(userId, true) != null) {
log.debug("Valudate user, No User supplied, returning public config");
if (bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton()) != null) {
log.debug("hue api config requested, No User supplied, returning public config");
HuePublicConfig apiResponse = HuePublicConfig.createConfig("Philips hue",
bridgeSettings.getUpnpConfigAddress(), bridgeSettings.getHubversion());
return apiResponse;
@@ -590,28 +765,21 @@ public class HueMulator {
@SuppressWarnings("unchecked")
private Object getFullState(String userId, String ipAddress) {
log.debug("hue api full state requested: " + userId + " from " + ipAddress);
HueError[] theErrors = validateWhitelistUser(userId, false);
HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors != null)
return theErrors;
HueApiResponse apiResponse = new HueApiResponse("Philips hue", bridgeSettings.getUpnpConfigAddress(),
bridgeSettings.getWhitelist(), bridgeSettings.getHubversion());
Object aReturn = this.lightsListHandler(userId, ipAddress);
Map<String, DeviceResponse> deviceList = new HashMap<String, DeviceResponse>();
if(aReturn.getClass() == deviceList.getClass()) {
deviceList = (Map<String, DeviceResponse>) aReturn;
apiResponse.setLights(deviceList);
}
else {
return aReturn;
}
apiResponse.setLights((Map<String, DeviceResponse>) this.lightsListHandler(userId, ipAddress));
apiResponse.setGroups((Map<String, GroupResponse>) this.groupsListHandler(userId, ipAddress));
return apiResponse;
}
private Object getLight(String userId, String lightId, String ipAddress) {
log.debug("hue light requested: " + lightId + " for user: " + userId + " from " + ipAddress);
HueError[] theErrors = validateWhitelistUser(userId, false);
HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors != null)
return theErrors;
@@ -623,10 +791,25 @@ public class HueMulator {
log.debug("found device named: " + device.getName());
}
DeviceResponse lightResponse = null;
if ((device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]))) {
HueDeviceIdentifier deviceId = aGsonHandler.fromJson(device.getOnUrl(), HueDeviceIdentifier.class);
lightResponse = myHueHome.getHueDeviceInfo(deviceId, device);
} else
if (device.containsType(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) {
CallItem[] callItems = null;
try {
if(device.getOnUrl() != null)
callItems = aGsonHandler.fromJson(device.getOnUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
log.warn("Could not decode Json for url items to get Hue state for device: " + device.getName());
callItems = null;
}
for (int i = 0; callItems != null && i < callItems.length; i++) {
if((callItems[i].getType() != null && callItems[i].getType().equals(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) || callItems[i].getItem().getAsString().startsWith("{\"ipAddress\":\"")) {
lightResponse = myHueHome.getHueDeviceInfo(callItems[i], device);
i = callItems.length;
}
}
}
if (lightResponse == null)
lightResponse = DeviceResponse.createResponse(device);
return lightResponse;
@@ -640,10 +823,14 @@ public class HueMulator {
Integer targetBri = null;
Integer targetBriInc = null;
log.debug("Update state requested: " + userId + " from " + ipAddress + " body: " + body);
HueError[] theErrors = validateWhitelistUser(userId, false);
HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
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,
@@ -668,7 +855,7 @@ public class HueMulator {
if (state == null)
state = DeviceState.createDeviceState();
responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state, targetBri, targetBriInc);
responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state, targetBri, targetBriInc, device.isOffState());
device.setDeviceState(state);
return responseString;
@@ -686,11 +873,14 @@ public class HueMulator {
aMultiUtil.setDelayDefault(bridgeSettings.getButtonsleep());
aMultiUtil.setSetCount(1);
log.debug("hue state change requested: " + userId + " from " + ipAddress + " body: " + body);
HueError[] theErrors = validateWhitelistUser(userId, false);
HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
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,
@@ -713,8 +903,10 @@ public class HueMulator {
}
state = device.getDeviceState();
if (state == null)
if (state == null) {
state = DeviceState.createDeviceState();
device.setDeviceState(state);
}
if (targetBri != null || targetBriInc != null) {
url = device.getDimUrl();
@@ -730,11 +922,11 @@ public class HueMulator {
}
// code for backwards compatibility
if(!(device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]))) {
if(device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) {
if(url == null)
url = device.getOnUrl();
}
if (url != null) {
if (url != null && !url.equals("")) {
if (!url.startsWith("[")) {
if (url.startsWith("{\"item"))
url = "[" + url + "]";
@@ -759,8 +951,8 @@ public class HueMulator {
}
for (int i = 0; callItems != null && i < callItems.length; i++) {
if(!filterByRequester(callItems[i].getFilterIPs(), ipAddress)) {
log.debug("filter for requester address not present in list: " + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress);
if(!filterByRequester(device.getRequesterAddress(), ipAddress) || !filterByRequester(callItems[i].getFilterIPs(), ipAddress)) {
log.warn("filter for requester address not present in: (device)" + device.getRequesterAddress() + " OR then (item)" + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress);
continue;
}
if (callItems[i].getCount() != null && callItems[i].getCount() > 0)
@@ -802,8 +994,13 @@ public class HueMulator {
}
if (responseString == null || !responseString.contains("[{\"error\":")) {
responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state, targetBri, targetBriInc);
device.setDeviceState(state);
if(!device.isNoState()) {
responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state, targetBri, targetBriInc, device.isOffState());
device.setDeviceState(state);
} else {
DeviceState dummyState = DeviceState.createDeviceState();
responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, dummyState, targetBri, targetBriInc, device.isOffState());
}
}
return responseString;

View File

@@ -0,0 +1,44 @@
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;
}
boolean notDone = true;
while(notDone) {
notDone = false;
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()));
notDone = true;
} catch (Exception e) {
log.warn("Could not format current time: " + timeFormatDescriptor, e);
}
}
}
return request;
}
}

View File

@@ -7,7 +7,7 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.DeviceMapTypes;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
@@ -32,7 +32,7 @@ public class NestHome implements com.bwssystems.HABridge.Home {
private Boolean isFarenheit;
private Boolean validNest;
public NestHome(BridgeSettingsDescriptor bridgeSettings) {
public NestHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -94,10 +94,10 @@ public class NestHome implements com.bwssystems.HABridge.Home {
public void closeHome() {
if(theSession != null) {
theNest.endNestSession();
theNest = null;
theSession = null;
nestItems = null;
}
theNest = null;
theSession = null;
nestItems = null;
}
@Override
@@ -111,65 +111,73 @@ public class NestHome implements com.bwssystems.HABridge.Home {
+ "\",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
} else if (anItem.getType() != null && anItem.getType().trim().equalsIgnoreCase(DeviceMapTypes.NEST_HOMEAWAY[DeviceMapTypes.typeIndex])) {
NestInstruction homeAway = aGsonHandler.fromJson(anItem.getItem().toString(), NestInstruction.class);
NestInstruction homeAway = null;
if(anItem.getItem().isJsonObject())
homeAway = aGsonHandler.fromJson(anItem.getItem(), NestInstruction.class);
else
homeAway = aGsonHandler.fromJson(anItem.getItem().getAsString(), NestInstruction.class);
theNest.getHome(homeAway.getName()).setAway(homeAway.getAway());
} else if (anItem.getType() != null && anItem.getType().trim().equalsIgnoreCase(DeviceMapTypes.NEST_THERMO_SET[DeviceMapTypes.typeIndex])) {
NestInstruction thermoSetting = aGsonHandler.fromJson(anItem.getItem().toString(), NestInstruction.class);
if (thermoSetting.getControl().equalsIgnoreCase("temp")) {
if (targetBri != null) {
if (isFarenheit)
thermoSetting
.setTemp(
String.valueOf((Double
.parseDouble(BrightnessDecode.calculateReplaceIntensityValue(thermoSetting.getTemp(),
intensity, targetBri, targetBriInc, false)) - 32.0) / 1.8));
else
thermoSetting
.setTemp(
String.valueOf(Double.parseDouble(BrightnessDecode.calculateReplaceIntensityValue(thermoSetting.getTemp(),
intensity, targetBri, targetBriInc, false))));
log.debug("Setting thermostat: " + thermoSetting.getName() + " to "
+ thermoSetting.getTemp() + "C");
theNest.getThermostat(thermoSetting.getName())
.setTargetTemperature(Float.parseFloat(thermoSetting.getTemp()));
}
} else if (thermoSetting.getControl().contains("range")
|| thermoSetting.getControl().contains("heat")
|| thermoSetting.getControl().contains("cool")
|| thermoSetting.getControl().contains("off")) {
log.debug("Setting thermostat target type: " + thermoSetting.getName() + " to "
+ thermoSetting.getControl());
theNest.getThermostat(thermoSetting.getName()).setTargetType(thermoSetting.getControl());
} else if (thermoSetting.getControl().contains("fan")) {
log.debug("Setting thermostat fan mode: " + thermoSetting.getName() + " to "
+ thermoSetting.getControl().substring(4));
NestInstruction thermoSetting = null;
if(anItem.getItem().isJsonObject())
thermoSetting = aGsonHandler.fromJson(anItem.getItem(), NestInstruction.class);
else
thermoSetting = aGsonHandler.fromJson(anItem.getItem().getAsString(), NestInstruction.class);
if (thermoSetting.getControl().equalsIgnoreCase("temp")) {
if (targetBri != null) {
if (isFarenheit)
thermoSetting
.setTemp(
String.valueOf((Double
.parseDouble(BrightnessDecode.calculateReplaceIntensityValue(thermoSetting.getTemp(),
intensity, targetBri, targetBriInc, false)) - 32.0) / 1.8));
else
thermoSetting
.setTemp(
String.valueOf(Double.parseDouble(BrightnessDecode.calculateReplaceIntensityValue(thermoSetting.getTemp(),
intensity, targetBri, targetBriInc, false))));
log.debug("Setting thermostat: " + thermoSetting.getName() + " to "
+ thermoSetting.getTemp() + "C");
theNest.getThermostat(thermoSetting.getName())
.setFanMode(thermoSetting.getControl().substring(4));
} else {
log.warn("no valid Nest control info: " + thermoSetting.getControl());
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"no valid Nest control info\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
.setTargetTemperature(Float.parseFloat(thermoSetting.getTemp()));
}
} else if (thermoSetting.getControl().contains("range")
|| thermoSetting.getControl().contains("heat")
|| thermoSetting.getControl().contains("cool")
|| thermoSetting.getControl().contains("off")) {
log.debug("Setting thermostat target type: " + thermoSetting.getName() + " to "
+ thermoSetting.getControl());
theNest.getThermostat(thermoSetting.getName()).setTargetType(thermoSetting.getControl());
} else if (thermoSetting.getControl().contains("fan")) {
log.debug("Setting thermostat fan mode: " + thermoSetting.getName() + " to "
+ thermoSetting.getControl().substring(4));
theNest.getThermostat(thermoSetting.getName())
.setFanMode(thermoSetting.getControl().substring(4));
} else {
log.warn("no valid Nest control info: " + thermoSetting.getControl());
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"no valid Nest control info\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
}
}
return responseString;
}
@Override
public com.bwssystems.HABridge.Home createHome(BridgeSettingsDescriptor bridgeSettings) {
public com.bwssystems.HABridge.Home createHome(BridgeSettings bridgeSettings) {
theSession = null;
theNest = null;
nestItems = null;
validNest = bridgeSettings.isValidNest();
validNest = bridgeSettings.getBridgeSettingsDescriptor().isValidNest();
aGsonHandler = null;
log.info("Nest Home created." + (validNest ? "" : " No Nest configured."));
if(validNest) {
aGsonHandler = new GsonBuilder().create();
isFarenheit = bridgeSettings.isFarenheit();
isFarenheit = bridgeSettings.getBridgeSettingsDescriptor().isFarenheit();
try {
theSession = new NestSession(bridgeSettings.getNestuser(), bridgeSettings.getNestpwd());
theSession = new NestSession(bridgeSettings.getBridgeSettingsDescriptor().getNestuser(), bridgeSettings.getBridgeSettingsDescriptor().getNestpwd());
theNest = new Nest(theSession);
} catch (LoginException e) {
log.error("Caught Login Exception, setting Nest to invalid....");

View File

@@ -0,0 +1,55 @@
package com.bwssystems.HABridge.plugins.domoticz;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class DeviceResult {
@SerializedName("Description")
@Expose
private String description;
@SerializedName("Name")
@Expose
private String name;
@SerializedName("Type")
@Expose
private String type;
@SerializedName("idx")
@Expose
private String idx;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getIdx() {
return idx;
}
public void setIdx(String idx) {
this.idx = idx;
}
}

View File

@@ -0,0 +1,99 @@
package com.bwssystems.HABridge.plugins.domoticz;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Devices {
@SerializedName("ActTime")
@Expose
private Integer actTime;
@SerializedName("AllowWidgetOrdering")
@Expose
private Boolean allowWidgetOrdering;
@SerializedName("ServerTime")
@Expose
private String serverTime;
@SerializedName("Sunrise")
@Expose
private String sunrise;
@SerializedName("Sunset")
@Expose
private String sunset;
@SerializedName("result")
@Expose
private List<DeviceResult> result = null;
@SerializedName("status")
@Expose
private String status;
@SerializedName("title")
@Expose
private String title;
public Integer getActTime() {
return actTime;
}
public void setActTime(Integer actTime) {
this.actTime = actTime;
}
public Boolean getAllowWidgetOrdering() {
return allowWidgetOrdering;
}
public void setAllowWidgetOrdering(Boolean allowWidgetOrdering) {
this.allowWidgetOrdering = allowWidgetOrdering;
}
public String getServerTime() {
return serverTime;
}
public void setServerTime(String serverTime) {
this.serverTime = serverTime;
}
public String getSunrise() {
return sunrise;
}
public void setSunrise(String sunrise) {
this.sunrise = sunrise;
}
public String getSunset() {
return sunset;
}
public void setSunset(String sunset) {
this.sunset = sunset;
}
public List<DeviceResult> getResult() {
return result;
}
public void setResult(List<DeviceResult> result) {
this.result = result;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}

View File

@@ -0,0 +1,39 @@
package com.bwssystems.HABridge.plugins.domoticz;
public class DomoticzDevice {
private String devicetype;
private String devicename;
private String idx;
private String domoticzaddress;
private String domoticzname;
public String getDevicetype() {
return devicetype;
}
public void setDevicetype(String devicetype) {
this.devicetype = devicetype;
}
public String getDevicename() {
return devicename;
}
public void setDevicename(String devicename) {
this.devicename = devicename;
}
public String getIdx() {
return idx;
}
public void setIdx(String idx) {
this.idx = idx;
}
public String getDomoticzaddress() {
return domoticzaddress;
}
public void setDomoticzaddress(String domoticzaddress) {
this.domoticzaddress = domoticzaddress;
}
public String getDomoticzname() {
return domoticzname;
}
public void setDomoticzname(String domoticzname) {
this.domoticzname = domoticzname;
}
}

View File

@@ -0,0 +1,121 @@
package com.bwssystems.HABridge.plugins.domoticz;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.NameValue;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import com.google.gson.Gson;
public class DomoticzHandler {
private static final Logger log = LoggerFactory.getLogger(DomoticzHandler.class);
private static final String GET_REQUEST = "/json.htm?type=";
private static final String DEVICES_TYPE = "devices";
private static final String SCENES_TYPE = "scenes";
private static final String FILTER_USED = "&used=true";
private NamedIP domoticzAddress;
public DomoticzHandler(NamedIP addressName) {
super();
domoticzAddress = addressName;
}
public List<DomoticzDevice> getDevices(HTTPHandler httpClient) {
return getDomoticzDevices(GET_REQUEST, DEVICES_TYPE, FILTER_USED, httpClient);
}
public List<DomoticzDevice> getScenes(HTTPHandler httpClient) {
return getDomoticzDevices(GET_REQUEST, SCENES_TYPE, null, httpClient);
}
private List<DomoticzDevice> getDomoticzDevices(String rootRequest, String type, String postpend, HTTPHandler httpClient) {
Devices theDomoticzApiResponse = null;
List<DomoticzDevice> deviceList = null;
String theUrl = null;
String theData;
if(postpend != null && !postpend.isEmpty())
theUrl = buildUrl(rootRequest + type + postpend);
else
theUrl = buildUrl(rootRequest + type);
theData = httpClient.doHttpRequest(theUrl, null, null, null, buildHeaders());
if(theData != null) {
log.debug("GET " + type + " DomoticzApiResponse - data: " + theData);
theDomoticzApiResponse = new Gson().fromJson(theData, Devices.class);
if(theDomoticzApiResponse.getResult() == null) {
log.warn("Cannot get any devices for type " + type + " for Domoticz " + domoticzAddress.getName() + " as response is not parsable.");
return deviceList;
}
deviceList = new ArrayList<DomoticzDevice>();
Iterator<DeviceResult> theDeviceNames = theDomoticzApiResponse.getResult().iterator();
while(theDeviceNames.hasNext()) {
DeviceResult theDevice = theDeviceNames.next();
DomoticzDevice aNewDomoticzDevice = new DomoticzDevice();
aNewDomoticzDevice.setDevicetype(theDevice.getType());
aNewDomoticzDevice.setDevicename(theDevice.getName());
aNewDomoticzDevice.setIdx(theDevice.getIdx());
aNewDomoticzDevice.setDomoticzaddress(domoticzAddress.getIp() + ":" + domoticzAddress.getPort());
aNewDomoticzDevice.setDomoticzname(domoticzAddress.getName());
deviceList.add(aNewDomoticzDevice);
}
}
else {
log.warn("Get Domoticz device types " + type + " for " + domoticzAddress.getName() + " - returned null, no data.");
}
return deviceList;
}
public String buildUrl(String thePayload) {
String newUrl = null;
if(thePayload != null && !thePayload.isEmpty()) {
if(domoticzAddress.getSecure() != null && domoticzAddress.getSecure())
newUrl = "https://";
else
newUrl = "http://";
newUrl = newUrl + domoticzAddress.getIp();
if(domoticzAddress.getPort() != null && !domoticzAddress.getPort().isEmpty())
newUrl = newUrl + ":" + domoticzAddress.getPort();
if(thePayload.startsWith("/"))
newUrl = newUrl + thePayload;
else
newUrl = newUrl + "/" + thePayload;
}
return newUrl;
}
public NameValue[] buildHeaders() {
NameValue[] headers = null;
if(domoticzAddress.getUsername() != null && !domoticzAddress.getUsername().isEmpty()
&& domoticzAddress.getPassword() != null && !domoticzAddress.getPassword().isEmpty()) {
NameValue theAuth = new NameValue();
theAuth.setName("Authorization");
String encoding = Base64.getEncoder().encodeToString((domoticzAddress.getUsername() + ":" + domoticzAddress.getPassword()).getBytes());
theAuth.setValue("Basic " + encoding);
headers = new NameValue[1];
headers[0] = theAuth;
}
return headers;
}
public NamedIP getDomoticzAddress() {
return domoticzAddress;
}
public void setDomoticzAddress(NamedIP DomoticzAddress) {
this.domoticzAddress = DomoticzAddress;
}
}

View File

@@ -0,0 +1,168 @@
package com.bwssystems.HABridge.plugins.domoticz;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.hue.HueError;
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.plugins.http.HTTPHandler;
import com.google.gson.Gson;
public class DomoticzHome implements Home {
private static final Logger log = LoggerFactory.getLogger(DomoticzHome.class);
private Map<String, DomoticzHandler> domoticzs;
private Boolean validDomoticz;
private HTTPHandler httpClient;
public DomoticzHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@Override
public Object getItems(String type) {
if(!validDomoticz)
return null;
log.debug("consolidating devices for hues");
List<DomoticzDevice> theResponse = null;
Iterator<String> keys = domoticzs.keySet().iterator();
List<DomoticzDevice> deviceList = new ArrayList<DomoticzDevice>();
while(keys.hasNext()) {
String key = keys.next();
theResponse = domoticzs.get(key).getDevices(httpClient);
if(theResponse != null)
addDomoticzDevices(deviceList, theResponse, key);
else {
log.warn("Cannot get lights for Domoticz with name: " + key + ", skipping this Domoticz.");
continue;
}
theResponse = domoticzs.get(key).getScenes(httpClient);
if(theResponse != null)
addDomoticzDevices(deviceList, theResponse, key);
else
log.warn("Cannot get Scenes for Domoticz with name: " + key);
}
return deviceList;
}
private Boolean addDomoticzDevices(List<DomoticzDevice> theDeviceList, List<DomoticzDevice> theSourceList, String theKey) {
if(!validDomoticz)
return null;
Iterator<DomoticzDevice> devices = theSourceList.iterator();
while(devices.hasNext()) {
DomoticzDevice theDevice = devices.next();
theDeviceList.add(theDevice);
}
return true;
}
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
Devices theDomoticzApiResponse = null;
String responseString = null;
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && (theUrl.startsWith("http://") || theUrl.startsWith("https://"))) {
String intermediate = theUrl.substring(theUrl.indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
} else
hostAddr = hostPortion;
DomoticzHandler theHandler = findHandlerByAddress(hostAddr);
if(theHandler != null){
String theData;
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody,
intensity, targetBri, targetBriInc, false);
theData = httpClient.doHttpRequest(theHandler.buildUrl(anUrl), null, null, null, theHandler.buildHeaders());
try {
theDomoticzApiResponse = new Gson().fromJson(theData, Devices.class);
if(theDomoticzApiResponse.getStatus().equals("OK"))
responseString = null;
else {
log.warn("Call failed for Domoticz " + theHandler.getDomoticzAddress().getName() + " with status " + theDomoticzApiResponse.getStatus() + " for item " + theDomoticzApiResponse.getTitle());
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
} catch (Exception e) {
log.warn("Cannot interrpret result from call for Domoticz " + theHandler.getDomoticzAddress().getName() + " as response is not parsable.");
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
} else {
log.warn("Domoticz Call could not complete, no address found: " + theUrl);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
} else {
log.warn("Domoticz Call to be presented as http(s)://<ip_address>(:<port>)/payload, format of request unknown: " + theUrl);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
return responseString;
}
@Override
public Home createHome(BridgeSettings bridgeSettings) {
validDomoticz = bridgeSettings.getBridgeSettingsDescriptor().isValidDomoticz();
log.info("Domoticz Home created." + (validDomoticz ? "" : " No Domoticz devices configured."));
if(!validDomoticz)
return null;
httpClient = new HTTPHandler();
domoticzs = new HashMap<String, DomoticzHandler>();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getDomoticzaddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aDomoticz = theList.next();
try {
domoticzs.put(aDomoticz.getName(), new DomoticzHandler(aDomoticz));
} catch (Exception e) {
log.error("Cannot get Domoticz client (" + aDomoticz.getName() + ") setup, Exiting with message: " + e.getMessage(), e);
return null;
}
}
return this;
}
private DomoticzHandler findHandlerByAddress(String hostAddress) {
DomoticzHandler aHandler = null;
boolean found = false;
Iterator<String> keys = domoticzs.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
aHandler = domoticzs.get(key);
if(aHandler != null && aHandler.getDomoticzAddress().getIp().equals(hostAddress)) {
found = true;
break;
}
}
if(!found)
aHandler = null;
return aHandler;
}
@Override
public void closeHome() {
if(httpClient != null)
httpClient.closeHandler();
}
}

View File

@@ -5,17 +5,20 @@ import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
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);
private String execGarden;;
public CommandHome(BridgeSettingsDescriptor bridgeSettings) {
public CommandHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -25,24 +28,33 @@ public class CommandHome implements Home {
log.debug("Exec Request called with url: " + anItem.getItem().getAsString());
String responseString = null;
String intermediate;
if (anItem.getItem().toString().contains("exec://"))
intermediate = anItem.getItem().getAsString().substring(anItem.getItem().toString().indexOf("://") + 3);
if (anItem.getItem().getAsString().contains("exec://"))
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 = DeviceDataDecode.replaceDeviceData(intermediate, device);
intermediate = TimeDecode.replaceTimeValue(intermediate);
if(execGarden != null) {
if(System.getProperty("os.name").toLowerCase().indexOf("win") > 0)
intermediate = execGarden + "\\" + intermediate;
else
intermediate = execGarden + "/" + 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);
@@ -61,8 +73,9 @@ public class CommandHome implements Home {
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
public Home createHome(BridgeSettings bridgeSettings) {
log.info("Command Home for system program execution created.");
this.execGarden = bridgeSettings.getBridgeSecurity().getExecGarden();
return this;
}

View File

@@ -9,26 +9,19 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.NameValue;
import com.bwssystems.HABridge.api.hue.HueError;
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.plugins.http.HTTPHandler;
import com.google.gson.Gson;
public class HalHome implements Home {
private static final Logger log = LoggerFactory.getLogger(HalHome.class);
private Map<String, HalInfo> hals;
private Boolean validHal;
private HTTPHandler anHttpHandler;
public HalHome(BridgeSettingsDescriptor bridgeSettings) {
public HalHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -105,52 +98,30 @@ public class HalHome implements Home {
Iterator<HalDevice> devices = theSourceList.iterator();
while(devices.hasNext()) {
HalDevice theDevice = devices.next();
HalDevice aNewHalDevice = new HalDevice();
aNewHalDevice.setHaldevicetype(theDevice.getHaldevicetype());
aNewHalDevice.setHaldevicename(theDevice.getHaldevicename());
aNewHalDevice.setButtons(theDevice.getButtons());
aNewHalDevice.setHaladdress(hals.get(theKey).getHalAddress().getIp());
aNewHalDevice.setHalname(theKey);
theDeviceList.add(aNewHalDevice);
theDeviceList.add(theDevice);
}
anHttpHandler = new HTTPHandler();
return true;
}
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
log.debug("executing HUE api request to HAL Http " + anItem.getItem().getAsString());
String responseString = null;
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(anItem.getItem().getAsString(),
intensity, targetBri, targetBriInc, false);
String aBody;
aBody = BrightnessDecode.calculateReplaceIntensityValue(anItem.getHttpBody(),
intensity, targetBri, targetBriInc, false);
// make call
if (anHttpHandler.doHttpRequest(anUrl, anItem.getHttpVerb(), anItem.getContentType(), aBody,
new Gson().fromJson(anItem.getHttpHeaders(), NameValue[].class)) == null) {
log.warn("Error on calling url to change device state: " + anUrl);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
return responseString;
// Not a device handler
return null;
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
validHal = bridgeSettings.isValidHal();
public Home createHome(BridgeSettings bridgeSettings) {
validHal = bridgeSettings.getBridgeSettingsDescriptor().isValidHal();
log.info("HAL Home created." + (validHal ? "" : " No HAL devices configured."));
if(!validHal)
return null;
hals = new HashMap<String, HalInfo>();
Iterator<NamedIP> theList = bridgeSettings.getHaladdress().getDevices().iterator();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getHaladdress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aHal = theList.next();
try {
hals.put(aHal.getName(), new HalInfo(aHal, bridgeSettings.getHaltoken()));
hals.put(aHal.getName(), new HalInfo(aHal, bridgeSettings.getBridgeSettingsDescriptor().getHaltoken()));
} catch (Exception e) {
log.error("Cannot get hal client (" + aHal.getName() + ") setup, Exiting with message: " + e.getMessage(), e);
return null;

View File

@@ -121,6 +121,8 @@ public class HalInfo {
HalDevice aNewHalDevice = new HalDevice();
aNewHalDevice.setHaldevicetype(deviceType);
aNewHalDevice.setHaldevicename(theDevice.getDeviceName());
aNewHalDevice.setHaladdress(halAddress.getIp());
aNewHalDevice.setHalname(halAddress.getName());
deviceList.add(aNewHalDevice);
}
@@ -147,8 +149,12 @@ public class HalInfo {
theData = httpClient.doHttpRequest(theUrl, null, null, null, null);
if (theData != null) {
log.debug("GET IrData for IR Device " + theHalDevice.getHaldevicename() + " HalApiResponse - data: " + theData);
theHalApiResponse = new Gson().fromJson(theData, DeviceElements.class);
if (theHalApiResponse.getDeviceElements() == null) {
try {
theHalApiResponse = new Gson().fromJson(theData, DeviceElements.class);
} catch (Exception e) {
theHalApiResponse = null;
}
if (theHalApiResponse == null || theHalApiResponse.getDeviceElements() == null) {
StatusDescription theStatus = new Gson().fromJson(theData, StatusDescription.class);
if (theStatus.getStatus() == null) {
log.warn("Cannot get buttons for IR Device " + theHalDevice.getHaldevicename() + " for hal "
@@ -179,4 +185,11 @@ public class HalInfo {
this.halAddress = halAddress;
}
public void closeInfo() {
if(httpClient != null)
httpClient.closeHandler();
httpClient = null;
halAddress = null;
theToken = null;
}
}

View File

@@ -10,13 +10,14 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.DeviceMapTypes;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.IpList;
import com.bwssystems.HABridge.NamedIP;
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.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -31,7 +32,7 @@ public class HarmonyHome implements Home {
private Boolean validHarmony;
private Gson aGsonHandler;
public HarmonyHome(BridgeSettingsDescriptor bridgeSettings) {
public HarmonyHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -135,7 +136,11 @@ public class HarmonyHome implements Home {
} else {
if(anItem.getType().trim().equalsIgnoreCase(DeviceMapTypes.HARMONY_ACTIVITY[DeviceMapTypes.typeIndex]))
{
RunActivity anActivity = aGsonHandler.fromJson(anItem.getItem(), RunActivity.class);
RunActivity anActivity = null;
if(anItem.getItem().isJsonObject())
anActivity = aGsonHandler.fromJson(anItem.getItem(), RunActivity.class);
else
anActivity = aGsonHandler.fromJson(anItem.getItem().getAsString(), RunActivity.class);
if(anActivity.getHub() == null || anActivity.getHub().isEmpty())
anActivity.setHub(device.getTargetDevice());
HarmonyHandler myHarmony = getHarmonyHandler(anActivity.getHub());
@@ -148,10 +153,17 @@ public class HarmonyHome implements Home {
myHarmony.startActivity(anActivity);
}
} else if(anItem.getType().trim().equalsIgnoreCase(DeviceMapTypes.HARMONY_BUTTON[DeviceMapTypes.typeIndex])) {
String url = anItem.getItem().toString();
String url = null;
if(anItem.getItem().isJsonObject() || anItem.getItem().isJsonArray()) {
url = aGsonHandler.toJson(anItem.getItem());
} else
url = anItem.getItem().getAsString();
if (url.substring(0, 1).equalsIgnoreCase("{")) {
url = "[" + url + "]";
}
url = BrightnessDecode.calculateReplaceIntensityValue(url, intensity, targetBri, targetBriInc, false);
ButtonPress[] deviceButtons = aGsonHandler.fromJson(url, ButtonPress[].class);
Integer theCount = 1;
for(int z = 0; z < deviceButtons.length; z++) {
@@ -185,9 +197,9 @@ public class HarmonyHome implements Home {
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
public Home createHome(BridgeSettings bridgeSettings) {
isDevMode = Boolean.parseBoolean(System.getProperty("dev.mode", "false"));
validHarmony = bridgeSettings.isValidHarmony();
validHarmony = bridgeSettings.getBridgeSettingsDescriptor().isValidHarmony();
log.info("Harmony Home created." + (validHarmony ? "" : " No Harmony devices configured.") + (isDevMode ? " DevMode is set." : ""));
if(validHarmony || isDevMode) {
hubs = new HashMap<String, HarmonyServer>();
@@ -202,16 +214,16 @@ public class HarmonyHome implements Home {
theList.add(devModeIp);
IpList thedevList = new IpList();
thedevList.setDevices(theList);
bridgeSettings.setHarmonyAddress(thedevList);
bridgeSettings.getBridgeSettingsDescriptor().setHarmonyAddress(thedevList);
}
Iterator<NamedIP> theList = bridgeSettings.getHarmonyAddress().getDevices().iterator();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getHarmonyAddress().getDevices().iterator();
while(theList.hasNext() && validHarmony) {
NamedIP aHub = theList.next();
boolean loopControl = true;
int retryCount = 0;
while(loopControl) {
try {
hubs.put(aHub.getName(), HarmonyServer.setup(bridgeSettings, isDevMode, aHub));
hubs.put(aHub.getName(), HarmonyServer.setup(bridgeSettings.getBridgeSettingsDescriptor(), isDevMode, aHub));
loopControl = false;
} catch (Exception e) {
if(retryCount > 3) {

View File

@@ -4,6 +4,8 @@ import static java.lang.String.format;
import javax.inject.Inject;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import org.apache.http.client.methods.HttpGet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,69 +20,98 @@ import net.whistlingfish.harmony.HarmonyClientModule;
import net.whistlingfish.harmony.config.Activity;
import net.whistlingfish.harmony.protocol.OAReplyProvider;
import java.net.URLEncoder;
public class HarmonyServer {
private static final String ACTIVIY_ID = "${activity.id}";
private static final String ACTIVIY_LABEL = "${activity.label}";
@Inject
private HarmonyClient harmonyClient;
private HarmonyHandler myHarmony;
private DevModeResponse devResponse;
private OAReplyProvider dummyProvider;
private NamedIP myNameAndIP;
private Boolean isDevMode;
private HTTPHandler httpClient;
private Logger log = LoggerFactory.getLogger(HarmonyServer.class);
public HarmonyServer(NamedIP theHarmonyAddress) {
super();
myHarmony = null;
dummyProvider = null;
myNameAndIP = theHarmonyAddress;
isDevMode = false;
}
public HarmonyServer(NamedIP theHarmonyAddress) {
super();
myHarmony = null;
dummyProvider = null;
myNameAndIP = theHarmonyAddress;
isDevMode = false;
httpClient = new HTTPHandler();
}
public static HarmonyServer setup(BridgeSettingsDescriptor bridgeSettings, Boolean harmonyDevMode, NamedIP theHarmonyAddress) throws Exception {
if(!bridgeSettings.isValidHarmony() && harmonyDevMode) {
return new HarmonyServer(theHarmonyAddress);
}
Injector injector = null;
if(!harmonyDevMode)
injector = Guice.createInjector(new HarmonyClientModule());
HarmonyServer mainObject = new HarmonyServer(theHarmonyAddress);
if(!harmonyDevMode)
injector.injectMembers(mainObject);
mainObject.execute(bridgeSettings, harmonyDevMode);
return mainObject;
}
private void execute(BridgeSettingsDescriptor mySettings, Boolean harmonyDevMode) throws Exception {
Boolean noopCalls = Boolean.parseBoolean(System.getProperty("noop.calls", "false"));
isDevMode = harmonyDevMode;
String modeString = "";
if(dummyProvider != null)
log.debug("something is very wrong as dummyProvider is not null...");
if(isDevMode)
modeString = " (development mode)";
else if(noopCalls)
modeString = " (no op calls to harmony)";
log.info("setup initiated " + modeString + "....");
if(isDevMode)
{
harmonyClient = null;
devResponse = new DevModeResponse();
public static HarmonyServer setup(
BridgeSettingsDescriptor bridgeSettings,
Boolean harmonyDevMode,
NamedIP theHarmonyAddress
) throws Exception {
if (!bridgeSettings.isValidHarmony() && harmonyDevMode) {
return new HarmonyServer(theHarmonyAddress);
}
else {
devResponse = null;
harmonyClient.addListener(new ActivityChangeListener() {
@Override
public void activityStarted(Activity activity) {
log.info(format("activity changed: [%d] %s", activity.getId(), activity.getLabel()));
}
});
harmonyClient.connect(myNameAndIP.getIp());
Injector injector = null;
if (!harmonyDevMode) {
injector = Guice.createInjector(new HarmonyClientModule());
}
HarmonyServer mainObject = new HarmonyServer(theHarmonyAddress);
if (!harmonyDevMode) {
injector.injectMembers(mainObject);
}
mainObject.execute(bridgeSettings, harmonyDevMode);
return mainObject;
}
private void execute(BridgeSettingsDescriptor mySettings, Boolean harmonyDevMode) throws Exception {
Boolean noopCalls = Boolean.parseBoolean(System.getProperty("noop.calls", "false"));
isDevMode = harmonyDevMode;
String modeString = "";
if (dummyProvider != null) {
log.debug("something is very wrong as dummyProvider is not null...");
}
if (isDevMode) {
modeString = " (development mode)";
} else if (noopCalls) {
modeString = " (no op calls to harmony)";
}
log.info("setup initiated " + modeString + "....");
if (isDevMode) {
harmonyClient = null;
devResponse = new DevModeResponse();
} else {
devResponse = null;
harmonyClient.addListener(new ActivityChangeListener() {
@Override
public void activityStarted(Activity activity) {
String webhook = myNameAndIP.getWebhook();
if(webhook != null) {
try {
// Replacing variables
webhook = webhook.replace(ACTIVIY_ID, activity.getId().toString());
webhook = webhook.replace(ACTIVIY_LABEL, URLEncoder.encode(activity.getLabel(), "UTF-8"));
log.info(format("calling webhook: %s", webhook));
// Calling webhook
httpClient.doHttpRequest(webhook, HttpGet.METHOD_NAME, null, null, null);
} catch (Exception e) {
log.warn("could not call webhook: " + webhook, e);
}
}
log.info(format("activity changed: [%d] %s", activity.getId(), activity.getLabel()));
}
});
harmonyClient.connect(myNameAndIP.getIp());
}
myHarmony = new HarmonyHandler(harmonyClient, noopCalls, devResponse);
}
}
public HarmonyHandler getMyHarmony() {
return myHarmony;
}
public HarmonyHandler getMyHarmony() {
return myHarmony;
}
}

View File

@@ -4,6 +4,7 @@ public class HassDevice {
private State deviceState;
private String deviceName;
private String domain;
private Boolean secure;
private String hassaddress;
private String hassname;
public State getDeviceState() {
@@ -24,6 +25,12 @@ public class HassDevice {
public void setDomain(String domain) {
this.domain = domain;
}
public Boolean getSecure() {
return secure;
}
public void setSecure(Boolean secure) {
this.secure = secure;
}
public String getHassaddress() {
return hassaddress;
}

View File

@@ -9,7 +9,7 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
@@ -26,23 +26,23 @@ public class HassHome implements Home {
private Boolean validHass;
private Gson aGsonHandler;
public HassHome(BridgeSettingsDescriptor bridgeSettings) {
public HassHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
public Home createHome(BridgeSettings bridgeSettings) {
hassMap = null;
aGsonHandler = null;
validHass = bridgeSettings.isValidHass();
validHass = bridgeSettings.getBridgeSettingsDescriptor().isValidHass();
log.info("HomeAssistant Home created." + (validHass ? "" : " No HomeAssistants configured."));
if(validHass) {
hassMap = new HashMap<String,HomeAssistant>();
aGsonHandler =
new GsonBuilder()
.create();
Iterator<NamedIP> theList = bridgeSettings.getHassaddress().getDevices().iterator();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getHassaddress().getDevices().iterator();
while(theList.hasNext() && validHass) {
NamedIP aHass = theList.next();
try {
@@ -125,9 +125,13 @@ public class HassHome implements Home {
+ lightId + "state\"}}]";
} else {
HassCommand hassCommand = aGsonHandler.fromJson(anItem.getItem(), HassCommand.class);
HassCommand hassCommand = null;
if(anItem.getItem().isJsonObject())
hassCommand = aGsonHandler.fromJson(anItem.getItem(), HassCommand.class);
else
hassCommand = aGsonHandler.fromJson(anItem.getItem().getAsString(), HassCommand.class);
hassCommand.setBri(BrightnessDecode.replaceIntensityValue(hassCommand.getBri(),
BrightnessDecode.calculateIntensity(intensity, targetBri, targetBriInc), false));
BrightnessDecode.calculateIntensity(intensity, targetBri, targetBriInc), false));
HomeAssistant homeAssistant = getHomeAssistant(hassCommand.getHassName());
if (homeAssistant == null) {
log.warn("Should not get here, no HomeAssistants available");
@@ -147,10 +151,14 @@ public class HassHome implements Home {
public void closeHome() {
if(!validHass)
return;
if(hassMap == null)
return;
Iterator<String> keys = hassMap.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
hassMap.get(key).closeClient();
}
hassMap = null;
}
}

View File

@@ -36,7 +36,17 @@ public class HomeAssistant {
public Boolean callCommand(HassCommand aCommand) {
log.debug("calling HomeAssistant: " + aCommand.getHassName() + " - "
+ aCommand.getEntityId() + " - " + aCommand.getState() + " - " + aCommand.getBri());
String aUrl = "http://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/services/" + aCommand.getEntityId().substring(0, aCommand.getEntityId().indexOf("."));
String aUrl = null;
if(hassAddress.getSecure() != null && hassAddress.getSecure())
aUrl = "https";
else
aUrl = "http";
String domain = aCommand.getEntityId().substring(0, aCommand.getEntityId().indexOf("."));
aUrl = aUrl + "://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/services/";
if(domain.equals("group"))
aUrl = aUrl + "homeassistant";
else
aUrl = aUrl + domain;
String aBody = "{\"entity_id\":\"" + aCommand.getEntityId() + "\"";
NameValue[] headers = null;
if(hassAddress.getPassword() != null && !hassAddress.getPassword().isEmpty()) {
@@ -49,7 +59,7 @@ public class HomeAssistant {
if(aCommand.getState().equalsIgnoreCase("on")) {
aUrl = aUrl + "/turn_on";
if(aCommand.getBri() != null)
aBody = aBody + ",\"state\":\"on\",\"attributes\":{\"brightness\":" + aCommand.getBri() + "}}";
aBody = aBody + ",\"brightness\":" + aCommand.getBri() + "}";
else
aBody = aBody + "}";
}
@@ -57,6 +67,7 @@ public class HomeAssistant {
aUrl = aUrl + "/turn_off";
aBody = aBody + "}";
}
log.debug("Calling HomeAssistant with url: " + aUrl);
String theData = anHttpHandler.doHttpRequest(aUrl, HttpPost.METHOD_NAME, "application/json", aBody, headers);
log.debug("call Command return is: <" + theData + ">");
return true;
@@ -75,7 +86,11 @@ public class HomeAssistant {
headers = new NameValue[1];
headers[0] = password;
}
theUrl = "http://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/states";
if(hassAddress.getSecure() != null && hassAddress.getSecure())
theUrl = "https";
else
theUrl = "http";
theUrl = theUrl + "://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/states";
theData = anHttpHandler.doHttpRequest(theUrl, HttpGet.METHOD_NAME, "application/json", null, headers);
if(theData != null) {
log.debug("GET Hass States - data: " + theData);
@@ -95,5 +110,6 @@ public class HomeAssistant {
protected void closeClient() {
anHttpHandler.closeHandler();
anHttpHandler = null;
}
}

View File

@@ -5,22 +5,17 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,28 +24,20 @@ 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 CloseableHttpClient httpClient;
private RequestConfig globalConfig;
public HTTPHandler() {
httpClient = HttpClients.createDefault();
// Trust own CA and all self-signed certs
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();
httpClient = HttpClients.custom().setDefaultRequestConfig(globalConfig).build();
}
// This function executes the url from the device repository against the
// target as http or https as defined
public String doHttpRequest(String url, String httpVerb, String contentType, String body, NameValue[] headers) {
log.debug("doHttpRequest with url: " + url + " with http command: " + httpVerb + " with body: " + body);
HttpUriRequest request = null;
String theContent = null;
URI theURI = null;
@@ -94,32 +81,56 @@ public class HTTPHandler {
request.setHeader(headers[i].getName(), headers[i].getValue());
}
}
HttpResponse response;
try {
HttpResponse response;
if (url.startsWith("https"))
response = httpclientSSL.execute(request);
else
for(int retryCount = 0; retryCount < 2; retryCount++) {
response = httpClient.execute(request);
log.debug((httpVerb == null ? "GET" : httpVerb) + " execute on URL responded: "
+ response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300) {
if (response.getEntity() != null) {
try {
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); // read
// content
// for
// data
EntityUtils.consume(response.getEntity()); // close out
// inputstream
// ignore
// content
} catch (Exception e) {
log.debug("Error ocurred in handling response entity after successful call, still responding success. "
+ e.getMessage(), e);
log.debug((httpVerb == null ? "GET" : httpVerb) + " execute (" + retryCount + ") on URL responded: "
+ response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300) {
if (response.getEntity() != null) {
try {
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); // read
// content
// for
// data
EntityUtils.consume(response.getEntity()); // close out
// inputstream
// ignore
// content
} catch (Exception e) {
log.debug("Error ocurred in handling response entity after successful call, still responding success. "
+ e.getMessage(), e);
}
log.debug("Successfull response - The http response is <<<" + theContent + ">>>");
}
retryCount = 2;
} else {
log.warn("HTTP response code was not an expected successful response of between 200 - 299, the code was: " + response.getStatusLine());
try {
String someContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); // read
// content
// for
// data
EntityUtils.consume(response.getEntity()); // close out
// inputstream
// ignore
// content
log.debug("Unsuccessfull response - The http response is <<<" + someContent + ">>>");
} catch (Exception e) {
//noop
}
if (response.getStatusLine().getStatusCode() == 504) {
log.warn("HTTP response code was 504, retrying...");
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// noop
}
}
else
retryCount = 2;
}
if (theContent == null)
theContent = "";
}
} catch (IOException e) {
log.warn("Error calling out to HA gateway: IOException in log", e);
@@ -127,23 +138,22 @@ public class HTTPHandler {
return theContent;
}
public HttpClient getHttpClient() {
// public HttpClient getHttpClient() {
// return httpClient;
// }
public CloseableHttpClient getHttpClient() {
return httpClient;
}
public CloseableHttpClient getHttpclientSSL() {
return httpclientSSL;
}
public void closeHandler() {
httpClient = null;
try {
httpclientSSL.close();
httpClient.close();
} catch (IOException e) {
// noop
}
httpclientSSL = null;
httpClient = null;
}
}

View File

@@ -3,7 +3,7 @@ package com.bwssystems.HABridge.plugins.http;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.NameValue;
@@ -11,14 +11,16 @@ import com.bwssystems.HABridge.api.hue.HueError;
import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode;
import com.google.gson.Gson;
public class HTTPHome implements Home {
private static final Logger log = LoggerFactory.getLogger(HTTPHome.class);
private HTTPHandler anHttpHandler;
public HTTPHome(BridgeSettingsDescriptor bridgeSettings) {
public HTTPHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -28,27 +30,36 @@ public class HTTPHome implements Home {
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
String responseString = null;
//Backwards Compatibility Items
if(anItem.getHttpVerb() == null || anItem.getHttpVerb().isEmpty())
{
if(device.getHttpVerb() != null && !device.getHttpVerb().isEmpty())
anItem.setHttpVerb(device.getHttpVerb());
}
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && (theUrl.startsWith("http://") || theUrl.startsWith("https://"))) {
//Backwards Compatibility Items
if(anItem.getHttpVerb() == null || anItem.getHttpVerb().isEmpty())
{
if(device.getHttpVerb() != null && !device.getHttpVerb().isEmpty())
anItem.setHttpVerb(device.getHttpVerb());
}
if(anItem.getHttpHeaders() == null || anItem.getHttpHeaders().isEmpty()) {
if(device.getHeaders() != null && !device.getHeaders().isEmpty() )
anItem.setHttpHeaders(device.getHeaders());
}
log.debug("executing HUE api request to Http "
+ (anItem.getHttpVerb() == null ? "GET" : anItem.getHttpVerb()) + ": "
+ anItem.getItem().getAsString());
if(anItem.getHttpHeaders() == null || anItem.getHttpHeaders().isEmpty()) {
if(device.getHeaders() != null && !device.getHeaders().isEmpty() )
anItem.setHttpHeaders(device.getHeaders());
}
log.debug("executing HUE api request to Http "
+ (anItem.getHttpVerb() == null ? "GET" : anItem.getHttpVerb()) + ": "
+ anItem.getItem().getAsString());
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(anItem.getItem().getAsString(),
intensity, targetBri, targetBriInc, false);
String aBody;
aBody = BrightnessDecode.calculateReplaceIntensityValue(anItem.getHttpBody(),
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(theUrl,
intensity, targetBri, targetBriInc, false);
anUrl = DeviceDataDecode.replaceDeviceData(anUrl, device);
anUrl = TimeDecode.replaceTimeValue(anUrl);
String aBody = null;
if(anItem.getHttpBody()!= null && !anItem.getHttpBody().isEmpty()) {
aBody = BrightnessDecode.calculateReplaceIntensityValue(anItem.getHttpBody(),
intensity, targetBri, targetBriInc, false);
aBody = DeviceDataDecode.replaceDeviceData(aBody, device);
aBody = TimeDecode.replaceTimeValue(aBody);
}
// make call
if (anHttpHandler.doHttpRequest(anUrl, anItem.getHttpVerb(), anItem.getContentType(), aBody,
new Gson().fromJson(anItem.getHttpHeaders(), NameValue[].class)) == null) {
@@ -57,11 +68,18 @@ public class HTTPHome implements Home {
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
} else {
log.warn("HTTP Call to be presented as http(s)://<ip_address>(:<port>)/payload, format of request unknown: " + theUrl);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
return responseString;
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
public Home createHome(BridgeSettings bridgeSettings) {
anHttpHandler = new HTTPHandler();
log.info("Http Home created.");
return this;
@@ -75,7 +93,9 @@ public class HTTPHome implements Home {
@Override
public void closeHome() {
anHttpHandler.closeHandler();
if(anHttpHandler != null)
anHttpHandler.closeHandler();
anHttpHandler = null;
}
}

View File

@@ -8,7 +8,7 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
@@ -25,7 +25,7 @@ public class HueHome implements Home {
private Boolean validHue;
private Gson aGsonHandler;
public HueHome(BridgeSettingsDescriptor bridgeSettings) {
public HueHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -65,9 +65,17 @@ public class HueHome implements Home {
return deviceList;
}
public DeviceResponse getHueDeviceInfo(HueDeviceIdentifier deviceId, DeviceDescriptor device) {
public DeviceResponse getHueDeviceInfo(CallItem anItem, DeviceDescriptor device) {
if(!validHue)
return null;
HueDeviceIdentifier deviceId = null;
if(anItem.getItem().isJsonObject())
deviceId = aGsonHandler.fromJson(anItem.getItem(), HueDeviceIdentifier.class);
else
deviceId = aGsonHandler.fromJson(anItem.getItem().getAsString(), HueDeviceIdentifier.class);
if(deviceId.getHueName() == null || deviceId.getHueName().isEmpty())
deviceId.setHueName(device.getTargetDevice());
DeviceResponse deviceResponse = null;
HueInfo aHueInfo = hues.get(device.getTargetDevice());
deviceResponse = aHueInfo.getHueDeviceInfo(deviceId.getDeviceId(), device);
@@ -80,7 +88,11 @@ public class HueHome implements Home {
if(!validHue)
return null;
String responseString = null;
HueDeviceIdentifier deviceId = aGsonHandler.fromJson(anItem.getItem(), HueDeviceIdentifier.class);
HueDeviceIdentifier deviceId = null;
if(anItem.getItem().isJsonObject())
deviceId = aGsonHandler.fromJson(anItem.getItem(), HueDeviceIdentifier.class);
else
deviceId = aGsonHandler.fromJson(anItem.getItem().getAsString(), HueDeviceIdentifier.class);
if(deviceId.getHueName() == null || deviceId.getHueName().isEmpty())
deviceId.setHueName(device.getTargetDevice());
@@ -93,12 +105,12 @@ public class HueHome implements Home {
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
validHue = bridgeSettings.isValidHue();
public Home createHome(BridgeSettings bridgeSettings) {
validHue = bridgeSettings.getBridgeSettingsDescriptor().isValidHue();
log.info("Hue passthru Home created." + (validHue ? "" : " No Hue passtrhu systems configured."));
if(validHue) {
hues = new HashMap<String, HueInfo>();
Iterator<NamedIP> theList = bridgeSettings.getHueaddress().getDevices().iterator();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getHueaddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aHue = theList.next();
hues.put(aHue.getName(), new HueInfo(aHue));
@@ -112,10 +124,13 @@ public class HueHome implements Home {
public void closeHome() {
if(!validHue)
return;
if(hues == null)
return;
Iterator<String> keys = hues.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
hues.get(key).closeHue();;
}
hues = null;
}
}

View File

@@ -0,0 +1,49 @@
package com.bwssystems.HABridge.plugins.lifx;
import com.github.besherman.lifx.LFXGroup;
import com.github.besherman.lifx.LFXLight;
public class LifxDevice {
private Object lifxObject;
private String type;
public final static String LIGHT_TYPE = "Light";
public final static String GROUP_TYPE = "Group";
public LifxDevice(Object lifxObject, String type) {
super();
this.lifxObject = lifxObject;
this.type = type;
}
public LifxEntry toEntry() {
LifxEntry anEntry = null;
if(type.equals(LIGHT_TYPE)) {
anEntry = new LifxEntry();
anEntry.setId(((LFXLight)lifxObject).getID());
anEntry.setName(((LFXLight)lifxObject).getLabel());
anEntry.setType(LIGHT_TYPE);
}
if(type.equals(GROUP_TYPE)) {
anEntry = new LifxEntry();
anEntry.setId("na");
anEntry.setName(((LFXGroup)lifxObject).getLabel());
anEntry.setType(GROUP_TYPE);
}
return anEntry;
}
public Object getLifxObject() {
return lifxObject;
}
public void setLifxObject(Object lifxObject) {
this.lifxObject = lifxObject;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,25 @@
package com.bwssystems.HABridge.plugins.lifx;
public class LifxEntry {
private String name;
private String id;
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,257 @@
package com.bwssystems.HABridge.plugins.lifx;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
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.github.besherman.lifx.LFXClient;
import com.github.besherman.lifx.LFXGroup;
import com.github.besherman.lifx.LFXGroupCollection;
import com.github.besherman.lifx.LFXGroupCollectionListener;
import com.github.besherman.lifx.LFXLight;
import com.github.besherman.lifx.LFXLightCollection;
import com.github.besherman.lifx.LFXLightCollectionListener;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class LifxHome implements Home {
private static final Logger log = LoggerFactory.getLogger(LifxHome.class);
private static final float DIM_DIVISOR = (float)254.00;
private Map<String, LifxDevice> lifxMap;
private LFXClient client;
private Boolean validLifx;
private Gson aGsonHandler;
public LifxHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@Override
public Home createHome(BridgeSettings bridgeSettings) {
lifxMap = null;
aGsonHandler = null;
validLifx = bridgeSettings.getBridgeSettingsDescriptor().isValidLifx();
log.info("LifxDevice Home created." + (validLifx ? "" : " No LifxDevices configured."));
if(validLifx) {
try {
log.info("Open Lifx client....");
InetAddress configuredAddress = InetAddress.getByName(bridgeSettings.getBridgeSettingsDescriptor().getUpnpConfigAddress());
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(configuredAddress);
InetAddress bcastInetAddr = null;
if (networkInterface != null) {
for (InterfaceAddress ifaceAddr : networkInterface.getInterfaceAddresses()) {
InetAddress addr = ifaceAddr.getAddress();
if (addr instanceof Inet4Address) {
bcastInetAddr = ifaceAddr.getBroadcast();
break;
}
}
}
if(bcastInetAddr != null) {
lifxMap = new HashMap<String, LifxDevice>();
log.info("Opening LFX Client with broadcast address: " + bcastInetAddr.getHostAddress());
client = new LFXClient(bcastInetAddr.getHostAddress());
client.getLights().addLightCollectionListener(new MyLightListener(lifxMap));
client.getGroups().addGroupCollectionListener(new MyGroupListener(lifxMap));
client.open(false);
aGsonHandler =
new GsonBuilder()
.create();
} else {
log.warn("Could not open LIFX, no bcast addr available, check your upnp config address.");
client = null;
validLifx = false;
return this;
}
} catch (IOException e) {
log.warn("Could not open LIFX, with IO Exception", e);
client = null;
validLifx = false;
return this;
} catch (InterruptedException e) {
log.warn("Could not open LIFX, with Interruprted Exception", e);
client = null;
validLifx = false;
return this;
}
}
return this;
}
public LifxDevice getLifxDevice(String aName) {
if(!validLifx)
return null;
LifxDevice aLifxDevice = null;
if(aName == null || aName.equals("")) {
log.debug("Cannot get LifxDevice for name as it is empty.");
}
else {
aLifxDevice = lifxMap.get(aName);
log.debug("Retrieved a LifxDevice for name: " + aName);
}
return aLifxDevice;
}
@Override
public Object getItems(String type) {
log.debug("consolidating devices for lifx");
if(!validLifx)
return null;
LifxEntry theResponse = null;
Iterator<String> keys = lifxMap.keySet().iterator();
List<LifxEntry> deviceList = new ArrayList<LifxEntry>();
while(keys.hasNext()) {
String key = keys.next();
theResponse = lifxMap.get(key).toEntry();
if(theResponse != null)
deviceList.add(theResponse);
else {
log.warn("Cannot get LifxDevice with name: " + key + ", skipping this Lifx.");
continue;
}
}
return deviceList;
}
private Boolean addLifxLights(LFXLightCollection theDeviceList) {
if(!validLifx)
return false;
Iterator<LFXLight> devices = theDeviceList.iterator();;
while(devices.hasNext()) {
LFXLight theDevice = devices.next();
LifxDevice aNewLifxDevice = new LifxDevice(theDevice, LifxDevice.LIGHT_TYPE);
lifxMap.put(aNewLifxDevice.toEntry().getName(), aNewLifxDevice);
}
return true;
}
private Boolean addLifxGroups(LFXGroupCollection theDeviceList) {
if(!validLifx)
return false;
Iterator<LFXGroup> devices = theDeviceList.iterator();;
while(devices.hasNext()) {
LFXGroup theDevice = devices.next();
LifxDevice aNewLifxDevice = new LifxDevice(theDevice, LifxDevice.GROUP_TYPE);
lifxMap.put(aNewLifxDevice.toEntry().getName(), aNewLifxDevice);
}
return true;
}
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri, Integer targetBriInc, DeviceDescriptor device, String body) {
String theReturn = null;
float aBriValue;
float theValue;
log.debug("executing HUE api request to send message to LifxDevice: " + anItem.getItem().toString());
if(!validLifx) {
log.warn("Should not get here, no LifxDevice clients configured");
theReturn = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"Should not get here, no LifxDevices configured\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
} else {
LifxEntry lifxCommand = null;
if(anItem.getItem().isJsonObject())
lifxCommand = aGsonHandler.fromJson(anItem.getItem(), LifxEntry.class);
else
lifxCommand = aGsonHandler.fromJson(anItem.getItem().getAsString(), LifxEntry.class);
LifxDevice theDevice = getLifxDevice(lifxCommand.getName());
if (theDevice == null) {
log.warn("Should not get here, no LifxDevices available");
theReturn = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"Should not get here, no Lifx clients available\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
} else {
log.debug("calling LifxDevice: " + lifxCommand.getName());
if(theDevice.getType().equals(LifxDevice.LIGHT_TYPE)) {
LFXLight theLight = (LFXLight)theDevice.getLifxObject();
if(body.contains("true"))
theLight.setPower(true);
if(body.contains("false"))
theLight.setPower(false);
if(targetBri != null || targetBriInc != null) {
aBriValue = (float)BrightnessDecode.calculateIntensity(intensity, targetBri, targetBriInc);
theValue = aBriValue/DIM_DIVISOR;
if(theValue > (float)1.0)
theValue = (float)0.99;
theLight.setBrightness(theValue);
}
} else if (theDevice.getType().equals(LifxDevice.GROUP_TYPE)) {
LFXGroup theGroup = (LFXGroup)theDevice.getLifxObject();
if(body.contains("true"))
theGroup.setPower(true);
if(body.contains("false"))
theGroup.setPower(false);
}
}
}
return theReturn;
}
@Override
public void closeHome() {
if(!validLifx)
return;
client.close();
}
private static class MyLightListener implements LFXLightCollectionListener {
private static final Logger log = LoggerFactory.getLogger(MyLightListener.class);
private Map<String, LifxDevice> aLifxMap;
public MyLightListener(Map<String, LifxDevice> theMap) {
aLifxMap = theMap;
}
@Override
public void lightAdded(LFXLight light) {
log.debug("Light added, label: " + light.getLabel() + " and id: " + light.getID());
LifxDevice aNewLifxDevice = new LifxDevice(light, LifxDevice.LIGHT_TYPE);
aLifxMap.put(aNewLifxDevice.toEntry().getName(), aNewLifxDevice);
}
@Override
public void lightRemoved(LFXLight light) {
log.debug("Light removed, label: " + light.getLabel() + " and id: " + light.getID());
aLifxMap.remove(light.getLabel());
}
}
private static class MyGroupListener implements LFXGroupCollectionListener {
private static final Logger log = LoggerFactory.getLogger(MyLightListener.class);
private Map<String, LifxDevice> aLifxMap;
public MyGroupListener(Map<String, LifxDevice> theMap) {
aLifxMap = theMap;
}
@Override
public void groupAdded(LFXGroup group) {
log.debug("Group: " + group.getLabel() + " added: " + group.size());
LifxDevice aNewLifxDevice = new LifxDevice(group, LifxDevice.GROUP_TYPE);
aLifxMap.put(aNewLifxDevice.toEntry().getName(), aNewLifxDevice);
}
@Override
public void groupRemoved(LFXGroup group) {
log.debug("Group: " + group.getLabel() + " removed");
aLifxMap.remove(group.getLabel());
}
}
}

View File

@@ -1,5 +1,6 @@
package com.bwssystems.HABridge.plugins.mqtt;
import org.apache.commons.lang3.StringEscapeUtils;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
@@ -47,7 +48,7 @@ public class MQTTHandler {
}
public void publishMessage(String topic, String content) {
MqttMessage message = new MqttMessage(content.getBytes());
MqttMessage message = new MqttMessage(StringEscapeUtils.unescapeJava(content).getBytes());
message.setQos(qos);
try {
myClient.publish(topic, message);
@@ -68,5 +69,6 @@ public class MQTTHandler {
} catch (MqttException e) {
log.warn("Could not disconnect MQTT client for name: " + myConfig.getName() + " and ip: " + myConfig.getIp());
}
myClient = null;
}
}

View File

@@ -8,13 +8,15 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -24,7 +26,7 @@ public class MQTTHome implements Home {
private Boolean validMqtt;
private Gson aGsonHandler;
public MQTTHome(BridgeSettingsDescriptor bridgeSettings) {
public MQTTHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -41,6 +43,7 @@ public class MQTTHome implements Home {
handlers.get(key).shutdown();
}
}
handlers = null;
}
public MQTTHandler getMQTTHandler(String aName) {
@@ -79,8 +82,16 @@ public class MQTTHome implements Home {
String responseString = null;
log.debug("executing HUE api request to send message to MQTT broker: " + anItem.getItem().toString());
if (validMqtt) {
String mqttObject = BrightnessDecode.calculateReplaceIntensityValue(anItem.getItem().toString(),
String mqttObject = null;
if(anItem.getItem().isJsonObject() || anItem.getItem().isJsonArray()) {
mqttObject = aGsonHandler.toJson(anItem.getItem());
}
else
mqttObject =anItem.getItem().getAsString();
mqttObject = BrightnessDecode.calculateReplaceIntensityValue(mqttObject,
intensity, targetBri, targetBriInc, false);
mqttObject = DeviceDataDecode.replaceDeviceData(mqttObject, device);
mqttObject = TimeDecode.replaceTimeValue(mqttObject);
if (mqttObject.substring(0, 1).equalsIgnoreCase("{"))
mqttObject = "[" + mqttObject + "]";
MQTTMessage[] mqttMessages = aGsonHandler.fromJson(mqttObject, MQTTMessage[].class);
@@ -89,18 +100,16 @@ public class MQTTHome implements Home {
if(mqttMessages[z].getCount() != null && mqttMessages[z].getCount() > 0)
theCount = mqttMessages[z].getCount();
for(int y = 0; y < theCount; y++) {
if( y > 0 || z > 0) {
log.debug("publishing message: " + mqttMessages[y].getClientId() + " - "
+ mqttMessages[y].getTopic() + " - " + mqttMessages[y].getMessage()
+ " - count: " + String.valueOf(z));
log.debug("publishing message: " + mqttMessages[y].getClientId() + " - "
+ mqttMessages[y].getTopic() + " - " + mqttMessages[y].getMessage()
+ " - count: " + String.valueOf(z));
MQTTHandler mqttHandler = getMQTTHandler(mqttMessages[y].getClientId());
if (mqttHandler == null) {
log.warn("Should not get here, no mqtt hanlder available");
} else {
mqttHandler.publishMessage(mqttMessages[y].getTopic(), mqttMessages[y].getMessage());
}
}
MQTTHandler mqttHandler = getMQTTHandler(mqttMessages[y].getClientId());
if (mqttHandler == null) {
log.warn("Should not get here, no mqtt hanlder available");
} else {
mqttHandler.publishMessage(mqttMessages[y].getTopic(), mqttMessages[y].getMessage());
}
}
}
} else {
@@ -114,15 +123,15 @@ public class MQTTHome implements Home {
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
validMqtt = bridgeSettings.isValidMQTT();
public Home createHome(BridgeSettings bridgeSettings) {
validMqtt = bridgeSettings.getBridgeSettingsDescriptor().isValidMQTT();
log.info("MQTT Home created." + (validMqtt ? "" : " No MQTT Clients configured."));
if(validMqtt) {
aGsonHandler =
new GsonBuilder()
.create();
handlers = new HashMap<String, MQTTHandler>();
Iterator<NamedIP> theList = bridgeSettings.getMqttaddress().getDevices().iterator();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getMqttaddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aClientConfig = theList.next();
MQTTHandler aHandler = new MQTTHandler(aClientConfig);

View File

@@ -0,0 +1,67 @@
package com.bwssystems.HABridge.plugins.somfy;
public class SomfyDevice {
private String id;
private String room;
private String category;
private String somfyname;
private String name;
private String deviceUrl;
private String deviceType;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setRoom(String room) {
this.room = room;
}
public String getRoom() {
return room;
}
public void setCategory(String category) {
this.category = category;
}
public String getCategory() {
return category;
}
public void setSomfyname(String somfyname) {
this.somfyname = somfyname;
}
public String getSomfyname() {
return somfyname;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setDeviceUrl(String deviceUrl) {
this.deviceUrl = deviceUrl;
}
public String getDeviceUrl() {
return deviceUrl;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
public String getDeviceType() {
return deviceType;
}
}

View File

@@ -0,0 +1,118 @@
package com.bwssystems.HABridge.plugins.somfy;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.DeviceMapTypes;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
/**
* Support for Somfy Tahoma hub which allows control of IO Homecontrol devices such as Velux windows.
* Currently supports 'turn on' for open window, and 'turn off' for close.
*
* Known issues:
* //TODO - Fix bug on UI where bulk update seems to add the single device twice if 'update' is clicked (this is a general bug with Vera too I think)
* Enhancements:
* //TODO - support 'dimming' for partial window opening.
*
*/
public class SomfyHome implements Home {
private static final Logger log = LoggerFactory.getLogger(SomfyHome.class);
private Map<String, SomfyInfo> somfys;
private Boolean validSomfy;
public SomfyHome(BridgeSettings bridgeSettings) {
createHome(bridgeSettings);
}
public SomfyInfo getSomfyHandler(String somfyName) {
return somfys.get(somfyName);
}
public List<SomfyDevice> getDevices() {
log.debug("consolidating devices for somfy");
Iterator<String> keys = somfys.keySet().iterator();
ArrayList<SomfyDevice> deviceList = new ArrayList<>();
while(keys.hasNext()) {
String key = keys.next();
List<SomfyDevice> devices = somfys.get(key).getSomfyDevices();
deviceList.addAll(devices);
}
return deviceList;
}
@Override
public Object getItems(String type) {
if(validSomfy) {
if(type.equalsIgnoreCase(DeviceMapTypes.SOMFY_DEVICE[DeviceMapTypes.typeIndex]))
return getDevices();
}
return null;
}
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity, Integer targetBri, Integer targetBriInc, DeviceDescriptor device, String body) {
String responseString = null;
if (!validSomfy) {
log.warn("Should not get here, no somfy hub available");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"Should not get here, no somfy hub available\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
} else {
if (anItem.getType() != null && anItem.getType().trim().equalsIgnoreCase(DeviceMapTypes.SOMFY_DEVICE[DeviceMapTypes.typeIndex])) {
log.debug("executing HUE api request to change activity to Somfy: " + anItem.getItem().toString());
String jsonToPost = anItem.getItem().toString();
SomfyInfo somfyHandler = getSomfyHandler(device.getTargetDevice());
if(somfyHandler == null) {
log.warn("Should not get here, no Somfy configured");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"Should not get here, no somfy configured\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
} else {
try {
somfyHandler.execApply(jsonToPost);
} catch (Exception e) {
log.warn("Error posting request to Somfy");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"Error posting request to SomfyTahoma\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
}
}
}
return responseString;
}
@Override
public Home createHome(BridgeSettings bridgeSettings) {
validSomfy = bridgeSettings.getBridgeSettingsDescriptor().isValidSomfy();
log.info("Somfy Home created." + (validSomfy ? "" : " No Somfys configured."));
if(validSomfy) {
somfys = new HashMap<>();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getSomfyAddress().getDevices().iterator();
while (theList.hasNext()) {
NamedIP aSomfy = theList.next();
somfys.put(aSomfy.getName(), new SomfyInfo(aSomfy, aSomfy.getName()));
}
}
return this;
}
@Override
public void closeHome() {
somfys = null;
}
}

View File

@@ -0,0 +1,116 @@
package com.bwssystems.HABridge.plugins.somfy;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.NameValue;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import com.bwssystems.HABridge.plugins.somfy.jsonschema2pojo.getsetup.Device;
import com.bwssystems.HABridge.plugins.somfy.jsonschema2pojo.getsetup.GetSetup;
import com.google.gson.Gson;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class SomfyInfo {
private static final Logger log = LoggerFactory.getLogger(SomfyInfo.class);
private final String somfyName;
private final NamedIP namedIP;
private HTTPHandler httpClient;
private static final String CONNECT_HOST = "https://www.tahomalink.com/";
private static final String BASE_URL = CONNECT_HOST + "enduser-mobile-web/externalAPI/";
private static final String BASE_URL_ENDUSER = CONNECT_HOST + "enduser-mobile-web/enduserAPI/";
public SomfyInfo(NamedIP namedIP, String somfyName) {
super();
this.somfyName = somfyName;
this.namedIP = namedIP;
}
private void initHttpClient() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
if(httpClient==null) {
httpClient = new HTTPHandler();
}
}
public List<SomfyDevice> getSomfyDevices() {
List<SomfyDevice> somfyDevices = new ArrayList<>();
try {
login(namedIP.getUsername(), namedIP.getPassword());
GetSetup setupData = getSetup();
for(Device device : setupData.getSetup().getDevices()) {
somfyDevices.add(mapDeviceToSomfyDevice(device));
}
} catch (Exception e) {
log.error("Could not get Somfy devices", e);
}
return somfyDevices;
}
public void login(String username, String password) throws Exception {
initHttpClient();
NameValue[] httpHeader = getHttpHeaders();
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
nvps.add(new BasicNameValuePair("userId", username));
nvps.add(new BasicNameValuePair("userPassword", password));
log.debug("Making SOMFY http login call");
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(nvps);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
urlEncodedFormEntity.writeTo(bos);
String body = bos.toString();
String response = httpClient.doHttpRequest(BASE_URL + "json/login",HttpPost.METHOD_NAME, "application/x-www-form-urlencoded", body,httpHeader);
log.debug(response);
}
private NameValue[] getHttpHeaders() {
NameValue userAgentHeader = new NameValue();
userAgentHeader.setName("User-Agent");
userAgentHeader.setValue("mine");
return new NameValue[]{userAgentHeader};
}
public GetSetup getSetup() throws IOException {
NameValue[] httpHeader = getHttpHeaders();
log.info("Making SOMFY http setup call");
String response = httpClient.doHttpRequest(BASE_URL + "json/getSetup", HttpGet.METHOD_NAME, "", "", httpHeader );
log.debug(response);
GetSetup setupData = new Gson().fromJson(response, GetSetup.class);
return setupData;
}
public void execApply(String jsonToPost) throws Exception {
login(namedIP.getUsername(), namedIP.getPassword());
log.info("Making SOMFY http exec call");
String response = httpClient.doHttpRequest(BASE_URL_ENDUSER + "exec/apply", HttpPost.METHOD_NAME, "application/json;charset=UTF-8", jsonToPost, getHttpHeaders());
log.info(response);
}
protected SomfyDevice mapDeviceToSomfyDevice(Device device) {
SomfyDevice somfyDevice = new SomfyDevice();
somfyDevice.setId(device.getOid());
somfyDevice.setCategory(device.getUiClass());
somfyDevice.setRoom("");
somfyDevice.setSomfyname(somfyName);
somfyDevice.setName(device.getLabel());
somfyDevice.setDeviceUrl(device.getDeviceURL());
somfyDevice.setDeviceType(device.getWidget());
return somfyDevice;
}
}

View File

@@ -0,0 +1,69 @@
package com.bwssystems.HABridge.plugins.somfy.jsonschema2pojo.getsetup;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Device {
@SerializedName("label")
@Expose
private String label;
@SerializedName("deviceURL")
@Expose
private String deviceURL;
@SerializedName("widget")
@Expose
private String widget;
@SerializedName("oid")
@Expose
private String oid;
@SerializedName("uiClass")
@Expose
private String uiClass;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getDeviceURL() {
return deviceURL;
}
public void setDeviceURL(String deviceURL) {
this.deviceURL = deviceURL;
}
public String getWidget() {
return widget;
}
public void setWidget(String widget) {
this.widget = widget;
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
public String getUiClass() {
return uiClass;
}
public void setUiClass(String uiClass) {
this.uiClass = uiClass;
}
}

View File

@@ -0,0 +1,21 @@
package com.bwssystems.HABridge.plugins.somfy.jsonschema2pojo.getsetup;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class GetSetup {
@SerializedName("setup")
@Expose
private Setup setup;
public Setup getSetup() {
return setup;
}
public void setSetup(Setup setup) {
this.setup = setup;
}
}

View File

@@ -0,0 +1,35 @@
package com.bwssystems.HABridge.plugins.somfy.jsonschema2pojo.getsetup;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Setup {
@SerializedName("id")
@Expose
private String id;
@SerializedName("devices")
@Expose
private List<Device> devices = null;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<Device> getDevices() {
return devices;
}
public void setDevices(List<Device> devices) {
this.devices = devices;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,28 +1,40 @@
package com.bwssystems.HABridge.plugins.tcp;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class TCPHome implements Home {
private static final Logger log = LoggerFactory.getLogger(TCPHome.class);
private byte[] sendData;
private Map<String, Socket> theSockets;
private Gson aGsonHandler;
public TCPHome(BridgeSettingsDescriptor bridgeSettings) {
public TCPHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -30,47 +42,90 @@ public class TCPHome implements Home {
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
Socket dataSendSocket = null;
log.debug("executing HUE api request to TCP: " + anItem.getItem().getAsString());
String intermediate = anItem.getItem().getAsString().substring(anItem.getItem().getAsString().indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
String port = null;
InetAddress IPAddress = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && theUrl.contains("tcp://")) {
if(!theUrl.startsWith("{\"tcpDevice\""))
theUrl = "{\"tcpDevice\":\"" + theUrl + "\"}";
TcpDevice theDevice = aGsonHandler.fromJson(theUrl, TcpDevice.class);
String intermediate = theDevice.getTcpDevice().substring(theDevice.getTcpDevice().indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
String port = null;
InetAddress IPAddress = null;
dataSendSocket = theSockets.get(hostPortion);
if(dataSendSocket == null) {
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
} else
hostAddr = hostPortion;
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
return aGsonHandler.toJson(HueErrorResponse.createResponse("901", null, "Cannot connect, Unknown Host", null, "/lights/" + device.getId(), null).getTheErrors());
}
try {
dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
if(theDevice.isPersistent())
theSockets.put(hostPortion, dataSendSocket);
} catch (Exception e) {
return aGsonHandler.toJson(HueErrorResponse.createResponse("901", null, "Cannot connect, Socket Creation issue", null, "/lights/" + device.getId(), null).getTheErrors());
}
}
theUrlBody = TimeDecode.replaceTimeValue(theUrlBody);
if (theUrlBody.startsWith("0x")) {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true);
theUrlBody = DeviceDataDecode.replaceDeviceData(theUrlBody, device);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
} else {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false);
theUrlBody = DeviceDataDecode.replaceDeviceData(theUrlBody, device);
theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody);
sendData = theUrlBody.getBytes();
}
try {
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
} catch (IOException e) {
log.warn("Could not send data to TCP socket <<<" + e.getMessage() + ">>>, closing socket: " + theUrl);
try {
dataSendSocket.close();
} catch (IOException e1) {
// noop
}
dataSendSocket = null;
if(theDevice.isPersistent())
theSockets.remove(hostPortion);
return aGsonHandler.toJson(HueErrorResponse.createResponse("901", null, "Cannot send data", null, "/lights/" + device.getId(), null).getTheErrors());
}
if(!theDevice.isPersistent()) {
try {
if(dataSendSocket != null)
dataSendSocket.close();
} catch (IOException e1) {
// noop
}
dataSendSocket = null;
}
} else
hostAddr = hostPortion;
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
// noop
}
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 {
Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
dataSendSocket.close();
} catch (Exception e) {
// noop
}
log.warn("Tcp Call to be presented as tcp://<ip_address>:<port>/payload, format of request unknown: " + theUrl);
return null;
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
public Home createHome(BridgeSettings bridgeSettings) {
log.info("TCP Home created.");
theSockets = new HashMap<String, Socket>();
aGsonHandler = new GsonBuilder().create();
return this;
}
@@ -82,8 +137,18 @@ public class TCPHome implements Home {
@Override
public void closeHome() {
// noop
log.debug("Shutting down TCP sockets.");
if(theSockets != null && !theSockets.isEmpty()) {
Iterator<String> keys = theSockets.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
try {
theSockets.get(key).close();
} catch (IOException e) {
// noop
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
package com.bwssystems.HABridge.plugins.tcp;
public class TcpDevice {
private String tcpDevice;
private boolean persistent;
public String getTcpDevice() {
return tcpDevice;
}
public void setTcpDevice(String tcpDevice) {
this.tcpDevice = tcpDevice;
}
public boolean isPersistent() {
return persistent;
}
public void setPersistent(boolean persistent) {
this.persistent = persistent;
}
}

View File

@@ -6,15 +6,18 @@ import java.net.UnknownHostException;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode;
import com.bwssystems.HABridge.util.UDPDatagramSender;
public class UDPHome implements Home {
@@ -22,7 +25,7 @@ public class UDPHome implements Home {
private UDPDatagramSender theUDPDatagramSender;
private byte[] sendData;
public UDPHome(BridgeSettingsDescriptor bridgeSettings, UDPDatagramSender aUDPDatagramSender) {
public UDPHome(BridgeSettings bridgeSettings, UDPDatagramSender aUDPDatagramSender) {
super();
theUDPDatagramSender = aUDPDatagramSender;
createHome(bridgeSettings);
@@ -32,42 +35,52 @@ public class UDPHome implements Home {
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
log.debug("executing HUE api request to UDP: " + anItem.getItem().getAsString());
String intermediate = anItem.getItem().getAsString().substring(anItem.getItem().getAsString().indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
String port = null;
InetAddress IPAddress = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && theUrl.startsWith("udp://")) {
String intermediate = theUrl.substring(theUrl.indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
String port = null;
InetAddress IPAddress = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
} else
hostAddr = hostPortion;
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
log.warn("Udp Call, unknown host, continuing...");
return null;
}
theUrlBody = TimeDecode.replaceTimeValue(theUrlBody);
if (theUrlBody.startsWith("0x")) {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true);
theUrlBody = DeviceDataDecode.replaceDeviceData(theUrlBody, device);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
} else {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false);
theUrlBody = DeviceDataDecode.replaceDeviceData(theUrlBody, device);
theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody);
sendData = theUrlBody.getBytes();
}
try {
theUDPDatagramSender.sendUDPResponse(sendData, IPAddress, Integer.parseInt(port));
} catch (NumberFormatException e) {
log.warn("Udp Call, Number format exception on port, continuing...");
} catch (IOException e) {
log.warn("IO exception on udp call, continuing...");
}
} else
hostAddr = hostPortion;
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
// noop
}
log.warn("Udp Call to be presented as udp://<ip_address>:<port>/payload, format of request unknown: " + theUrl);
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
} catch (IOException e) {
// noop
}
return null;
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
public Home createHome(BridgeSettings bridgeSettings) {
log.info("UDP Home created.");
return this;
}

View File

@@ -9,7 +9,7 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.DeviceMapTypes;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
@@ -25,7 +25,7 @@ public class VeraHome implements Home {
private Map<String, VeraInfo> veras;
private Boolean validVera;
public VeraHome(BridgeSettingsDescriptor bridgeSettings) {
public VeraHome(BridgeSettings bridgeSettings) {
super();
createHome(bridgeSettings);
}
@@ -90,12 +90,12 @@ public class VeraHome implements Home {
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
validVera = bridgeSettings.isValidVera();
public Home createHome(BridgeSettings bridgeSettings) {
validVera = bridgeSettings.getBridgeSettingsDescriptor().isValidVera();
log.info("Vera Home created." + (validVera ? "" : " No Veras configured."));
if(validVera) {
veras = new HashMap<String, VeraInfo>();
Iterator<NamedIP> theList = bridgeSettings.getVeraAddress().getDevices().iterator();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getVeraAddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aVera = theList.next();
veras.put(aVera.getName(), new VeraInfo(aVera));
@@ -106,7 +106,6 @@ public class VeraHome implements Home {
@Override
public void closeHome() {
// TODO Auto-generated method stub
veras = null;
}
}

View File

@@ -1,3 +1,11 @@
[ng\:cloak],
[ng-cloak],
[data-ng-cloak],
[x-ng-cloak],
.ng-cloak,
.x-ng-cloak {
display: none !important;
}
body {
padding-top: 60px;
padding-bottom: 20px;
@@ -8,4 +16,55 @@ body {
}
.sortorder.reverse:after {
content: '\25bc';
}
}
.form-container {
position: relative;
bottom: 0;
display: table-cell;
vertical-align: middle;
}
.form-container form > div {
padding: 0 15px;
}
.form-container form > button {
margin-left: 15px;
}
legend.form-label {
font-size: 24pt;
padding: 0 15px;
}
.form-control.error {
border-color: red;
}
.form-hint {
font-size: 10pt;
line-height: 12pt;
margin: -5px auto 5px;
color: #999;
}
.form-hint.error {
color: #C00;
font-weight: bold;
font-size: 8pt;
}
.password-count {
float: right;
position: relative;
bottom: 24px;
right: 10px;
}
.msg-block {
margin-top:5px;
}
.msg-error {
color:#F00;
font-size:14px;
}

View File

@@ -1,5 +1,5 @@
.scrollableContainer {
max-height: 436px; /* sets max-height value for all standards-compliant browsers */
height: 310px;
position: relative;
padding-top: 35px;
overflow: hidden;
@@ -28,17 +28,17 @@
}
.scrollArea {
_height: expression( this.scrollHeight > 599 ? "600px" : "auto" ); /* sets max-height for IE6 */
max-height: 400px; /* sets max-height value for all standards-compliant browsers */
height: 100%;
overflow-x: auto;
overflow-y: auto;
border: 1px solid #d5d5d5;
/* the implementation of this is still quite buggy; specifically, it doesn't like the
absolutely positioned headers within
-webkit-overflow-scrolling: touch; */
-webkit-overflow-scrolling: auto;
}
.scrollArea table {
overflow-x: hidden;
overflow-x: auto;
overflow-y: auto;
margin-bottom: 0;
width: 100%;
@@ -48,7 +48,7 @@
.scrollArea table th {
padding: 0 !important;
border: none !important;
min-width: 60px;
min-width: 40px;
}
.scrollArea table .th-inner {
overflow: hidden;

View File

@@ -0,0 +1,11 @@
/*!
* @copyright Copyright &copy; Kartik Visweswaran, Krajee.com, 2015
* @package yii2-password
* @version 1.1.3
*
* Password Strength Meter
* Modified and built for Yii Framework 2.0
* Author: Kartik Visweswaran
* Year: 2015
* For more Yii related demos visit http://demos.krajee.com
*/.kv-strength-container{width:100%;margin:0;padding:0;border:0}.kv-strength-container td{vertical-align:middle}.kv-meter-container{width:130px}.kv-meter{text-align:center}.kv-disabled{opacity:.65;cursor:not-allowed}.kv-scorebar-border{background:none repeat scroll 0 0 #333;border:1px solid #333;height:16px;width:100px;margin:0 auto;border-radius:4px}.kv-scorebar{background-image:url(../img/bg_strength_gradient.jpg);background-repeat:no-repeat;background-position:0 0;position:absolute;width:98px;height:14px;z-index:0;border-radius:2px}.kv-score{font-weight:700;font-size:75%;position:absolute;width:98px;z-index:10;border-radius:2px}.kv-score-0,.kv-score-1,.kv-score-5{color:#fff}.kv-score-2,.kv-score-3,.kv-score-4{color:#333}.kv-verdict{width:100%}

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

View File

@@ -13,6 +13,7 @@
<link href="css/ngDialog.min.css" rel="stylesheet">
<link href="css/ngDialog-theme-default.min.css" rel="stylesheet">
<link href="css/scrollable-table.css" rel="stylesheet">
<link href="css/strength-meter.min.css" rel="stylesheet">
<!--[if lt IE 9]>
<script type="text/javascript" src="js/html5shiv.min.js"></script>
@@ -37,13 +38,20 @@
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="http://{{bridge.settings.myechourl}}" target="_blank">My Echo</a></li>
<li><a href="https://github.com/bwssytems/ha-bridge/blob/master/README.md" target="_blank">Help</a></li>
<li class="dropdown">
<a id="dLabel" href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">About <span class="caret"></span></a>
<a id="dLabel1" href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Help <span class="caret"></span></a>
<ul class="dropdown-menu" aria-labelledby="dLabel">
<li><a href="#!/login">Login/Logout</a></li>
<li><a href="https://github.com/bwssytems/ha-bridge/blob/master/README.md" target="_blank">Readme</a></li>
<li><a href="https://github.com/bwssytems/ha-bridge/wiki/HA-Bridge-FAQs" target="_blank">FAQ</a></li>
</ul>
</li>
<li class="dropdown">
<a id="dLabel2" href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">About <span class="caret"></span></a>
<ul class="dropdown-menu" aria-labelledby="dLabel">
<li><a href="http://www.bwssystems.com" target="_blank">Developed by BWS Systems</a></li>
<li><a href="http://www.amazon.com/echo" target="_blank">Amazon Echo</a></li>
<li><a href="">HA Bridge Version {{bridge.habridgeversion}}</a></li>
<li><a href="">HA Bridge Version {{bridge.habridgeversion.version}}</a></li>
<li><center>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick">
@@ -61,7 +69,7 @@
</div>
</nav>
<div ng-view>
<div ng-view prerender-action="authorized()">
</div>
@@ -75,6 +83,10 @@
<script src="js/rzslider.min.js"></script>
<script src="js/ngDialog.min.js"></script>
<script src="js/angular-scrollable-table.min.js"></script>
<script src="js/strength-meter.min.js"></script>
<script src="js/angular-base64.min.js"></script>
<script src="js/angular-resource.min.js"></script>
<script src="js/ngStorage.min.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
!function(){"use strict";angular.module("base64",[]).constant("$base64",function(){function a(a,b){var c=f.indexOf(a.charAt(b));if(-1==c)throw"Cannot decode base64";return c}function b(b){b=""+b;var c,d,f,g=b.length;if(0==g)return b;if(0!=g%4)throw"Cannot decode base64";c=0,b.charAt(g-1)==e&&(c=1,b.charAt(g-2)==e&&(c=2),g-=4);var h=[];for(d=0;g>d;d+=4)f=a(b,d)<<18|a(b,d+1)<<12|a(b,d+2)<<6|a(b,d+3),h.push(String.fromCharCode(f>>16,255&f>>8,255&f));switch(c){case 1:f=a(b,d)<<18|a(b,d+1)<<12|a(b,d+2)<<6,h.push(String.fromCharCode(f>>16,255&f>>8));break;case 2:f=a(b,d)<<18|a(b,d+1)<<12,h.push(String.fromCharCode(f>>16))}return h.join("")}function c(a,b){var c=a.charCodeAt(b);if(c>255)throw"INVALID_CHARACTER_ERR: DOM Exception 5";return c}function d(a){if(1!=arguments.length)throw"SyntaxError: Not enough arguments";var b,d,g=[];a=""+a;var h=a.length-a.length%3;if(0==a.length)return a;for(b=0;h>b;b+=3)d=c(a,b)<<16|c(a,b+1)<<8|c(a,b+2),g.push(f.charAt(d>>18)),g.push(f.charAt(63&d>>12)),g.push(f.charAt(63&d>>6)),g.push(f.charAt(63&d));switch(a.length-h){case 1:d=c(a,b)<<16,g.push(f.charAt(d>>18)+f.charAt(63&d>>12)+e+e);break;case 2:d=c(a,b)<<16|c(a,b+1)<<8,g.push(f.charAt(d>>18)+f.charAt(63&d>>12)+f.charAt(63&d>>6)+e)}return g.join("")}var e="=",f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";return{encode:d,decode:b}}())}();

View File

@@ -0,0 +1,15 @@
/*
AngularJS v1.6.1
(c) 2010-2016 Google, Inc. http://angularjs.org
License: MIT
*/
(function(W,b){'use strict';function K(q,g){g=g||{};b.forEach(g,function(b,h){delete g[h]});for(var h in q)!q.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(g[h]=q[h]);return g}var B=b.$$minErr("$resource"),Q=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;b.module("ngResource",["ng"]).provider("$resource",function(){var q=/^https?:\/\/\[[^\]]*][^/]*/,g=this;this.defaults={stripTrailingSlashes:!0,cancellable:!1,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},
"delete":{method:"DELETE"}}};this.$get=["$http","$log","$q","$timeout",function(h,P,L,M){function C(b,e){this.template=b;this.defaults=p({},g.defaults,e);this.urlParams={}}function x(D,e,u,m){function c(a,d){var c={};d=p({},e,d);t(d,function(d,l){y(d)&&(d=d(a));var f;if(d&&d.charAt&&"@"===d.charAt(0)){f=a;var k=d.substr(1);if(null==k||""===k||"hasOwnProperty"===k||!Q.test("."+k))throw B("badmember",k);for(var k=k.split("."),e=0,g=k.length;e<g&&b.isDefined(f);e++){var h=k[e];f=null!==f?f[h]:void 0}}else f=
d;c[l]=f});return c}function R(a){return a.resource}function l(a){K(a||{},this)}var q=new C(D,m);u=p({},g.defaults.actions,u);l.prototype.toJSON=function(){var a=p({},this);delete a.$promise;delete a.$resolved;return a};t(u,function(a,d){var b=/^(POST|PUT|PATCH)$/i.test(a.method),e=a.timeout,g=N(a.cancellable)?a.cancellable:q.defaults.cancellable;e&&!S(e)&&(P.debug("ngResource:\n Only numeric values are allowed as `timeout`.\n Promises are not supported in $resource, because the same value would be used for multiple requests. If you are looking for a way to cancel requests, you should use the `cancellable` option."),
delete a.timeout,e=null);l[d]=function(f,k,m,D){function u(a){r.catch(E);z.resolve(a)}var G={},v,w,A;switch(arguments.length){case 4:A=D,w=m;case 3:case 2:if(y(k)){if(y(f)){w=f;A=k;break}w=k;A=m}else{G=f;v=k;w=m;break}case 1:y(f)?w=f:b?v=f:G=f;break;case 0:break;default:throw B("badargs",arguments.length);}var F=this instanceof l,n=F?v:a.isArray?[]:new l(v),s={},C=a.interceptor&&a.interceptor.response||R,x=a.interceptor&&a.interceptor.responseError||void 0,H=!!A,I=!!x,z,J;t(a,function(a,d){switch(d){default:s[d]=
T(a);case "params":case "isArray":case "interceptor":case "cancellable":}});!F&&g&&(z=L.defer(),s.timeout=z.promise,e&&(J=M(z.resolve,e)));b&&(s.data=v);q.setUrlParams(s,p({},c(v,a.params||{}),G),a.url);var r=h(s).then(function(f){var c=f.data;if(c){if(O(c)!==!!a.isArray)throw B("badcfg",d,a.isArray?"array":"object",O(c)?"array":"object",s.method,s.url);if(a.isArray)n.length=0,t(c,function(a){"object"===typeof a?n.push(new l(a)):n.push(a)});else{var b=n.$promise;K(c,n);n.$promise=b}}f.resource=n;
return f}),r=r["finally"](function(){n.$resolved=!0;!F&&g&&(n.$cancelRequest=E,M.cancel(J),z=J=s.timeout=null)}),r=r.then(function(a){var d=C(a);(w||E)(d,a.headers,a.status,a.statusText);return d},H||I?function(a){H&&A(a);return I?x(a):L.reject(a)}:void 0);H&&!I&&r.catch(E);return F?r:(n.$promise=r,n.$resolved=!1,g&&(n.$cancelRequest=u),n)};l.prototype["$"+d]=function(a,c,b){y(a)&&(b=c,c=a,a={});a=l[d].call(this,a,this,c,b);return a.$promise||a}});l.bind=function(a){a=p({},e,a);return x(D,a,u,m)};
return l}var E=b.noop,t=b.forEach,p=b.extend,T=b.copy,O=b.isArray,N=b.isDefined,y=b.isFunction,S=b.isNumber,U=b.$$encodeUriQuery,V=b.$$encodeUriSegment;C.prototype={setUrlParams:function(b,e,g){var m=this,c=g||m.template,h,l,p="",a=m.urlParams=Object.create(null);t(c.split(/\W/),function(d){if("hasOwnProperty"===d)throw B("badname");!/^\d+$/.test(d)&&d&&(new RegExp("(^|[^\\\\]):"+d+"(\\W|$)")).test(c)&&(a[d]={isQueryParamValue:(new RegExp("\\?.*=:"+d+"(?:\\W|$)")).test(c)})});c=c.replace(/\\:/g,":");
c=c.replace(q,function(a){p=a;return""});e=e||{};t(m.urlParams,function(a,b){h=e.hasOwnProperty(b)?e[b]:m.defaults[b];N(h)&&null!==h?(l=a.isQueryParamValue?U(h,!0):V(h),c=c.replace(new RegExp(":"+b+"(\\W|$)","g"),function(a,b){return l+b})):c=c.replace(new RegExp("(/?):"+b+"(\\W|$)","g"),function(a,b,d){return"/"===d.charAt(0)?d:b+d})});m.defaults.stripTrailingSlashes&&(c=c.replace(/\/+$/,"")||"/");c=c.replace(/\/\.(?=\w+($|\?))/,".");b.url=p+c.replace(/\/\\\./,"/.");t(e,function(a,c){m.urlParams[c]||
(b.params=b.params||{},b.params[c]=a)})}};return x}]})})(window,window.angular);
//# sourceMappingURL=angular-resource.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
/*! ngstorage 0.3.10 | Copyright (c) 2016 Gias Kay Lee | MIT License */!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["angular"],b):a.hasOwnProperty("angular")?b(a.angular):"object"==typeof exports&&(module.exports=b(require("angular")))}(this,function(a){"use strict";function b(a,b){var c;try{c=a[b]}catch(d){c=!1}if(c){var e="__"+Math.round(1e7*Math.random());try{a[b].setItem(e,e),a[b].removeItem(e,e)}catch(d){c=!1}}return c}function c(c){var d=b(window,c);return function(){var e="ngStorage-";this.setKeyPrefix=function(a){if("string"!=typeof a)throw new TypeError("[ngStorage] - "+c+"Provider.setKeyPrefix() expects a String.");e=a};var f=a.toJson,g=a.fromJson;this.setSerializer=function(a){if("function"!=typeof a)throw new TypeError("[ngStorage] - "+c+"Provider.setSerializer expects a function.");f=a},this.setDeserializer=function(a){if("function"!=typeof a)throw new TypeError("[ngStorage] - "+c+"Provider.setDeserializer expects a function.");g=a},this.supported=function(){return!!d},this.get=function(a){return d&&g(d.getItem(e+a))},this.set=function(a,b){return d&&d.setItem(e+a,f(b))},this.remove=function(a){d&&d.removeItem(e+a)},this.$get=["$rootScope","$window","$log","$timeout","$document",function(d,h,i,j,k){var l,m,n=e.length,o=b(h,c),p=o||(i.warn("This browser does not support Web Storage!"),{setItem:a.noop,getItem:a.noop,removeItem:a.noop}),q={$default:function(b){for(var c in b)a.isDefined(q[c])||(q[c]=a.copy(b[c]));return q.$sync(),q},$reset:function(a){for(var b in q)"$"===b[0]||delete q[b]&&p.removeItem(e+b);return q.$default(a)},$sync:function(){for(var a,b=0,c=p.length;c>b;b++)(a=p.key(b))&&e===a.slice(0,n)&&(q[a.slice(n)]=g(p.getItem(a)))},$apply:function(){var b;if(m=null,!a.equals(q,l)){b=a.copy(l),a.forEach(q,function(c,d){a.isDefined(c)&&"$"!==d[0]&&(p.setItem(e+d,f(c)),delete b[d])});for(var c in b)p.removeItem(e+c);l=a.copy(q)}},$supported:function(){return!!o}};return q.$sync(),l=a.copy(q),d.$watch(function(){m||(m=j(q.$apply,100,!1))}),h.addEventListener&&h.addEventListener("storage",function(b){if(b.key){var c=k[0];c.hasFocus&&c.hasFocus()||e!==b.key.slice(0,n)||(b.newValue?q[b.key.slice(n)]=g(b.newValue):delete q[b.key.slice(n)],l=a.copy(q),d.$apply())}}),h.addEventListener&&h.addEventListener("beforeunload",function(){q.$apply()}),q}]}}return a=a&&a.module?a:window.angular,a.module("ngStorage",[]).provider("$localStorage",c("localStorage")).provider("$sessionStorage",c("sessionStorage"))});

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,139 +1,149 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="active"><a href="#!/">Bridge
Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">Current devices ({{bridge.devices.length}})</h1>
</div>
<div class="panel-body">
<p>
<button class="btn btn-primary" type="submit" ng-click="renumberDevices()">Renumber Devices</button>
</p>
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">ID</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="deviceType">Type</th>
<th sortable-header col="targetDevice">Target</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices">
<td>{{$index+1}}</td>
<td>{{device.id}}</td>
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>
<p>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'on')">Test ON</button>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'dim')">Test Dim</button>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'off')">Test OFF</button>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit/Copy</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">
Bridge Device DB Backup <a ng-click="toggleBk()"><span
class={{imgBkUrl}} aria-hidden="true"></span></a>
</h1>
</div>
<div ng-if="visibleBk" class="panel-body">
<p>Control your backups from this area. Use the default name by hitting backup or specify your own.</p>
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="backup-name">Backup
File Name</label>
<div class="col-xs-8 col-sm-7">
<input id="backup-name" class="form-control" type="text"
ng-model="optionalbackupname" placeholder="Optional">
</div>
<button type="submit" class="btn btn-primary"
ng-click="backupDeviceDb(optionalbackupname)">Backup
Device DB</button>
</div>
</form>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Filename</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="backup in bridge.backups">
<td>{{backup}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="restoreBackup(backup)">Restore</button>
<button class="btn btn-warning" type="submit"
ng-click="deleteBackup(backup)">Delete</button>
</td>
</tr>
</table>
</div>
</div>
<script type="text/ng-template" id="valueDialog">
<div class="ngdialog-message">
<h2>Select value</h2>
<p>
<input type="radio" ng-model="valueType" value="percentage" ng-change="changeScale()"> Percentage
<input type="radio" ng-model="valueType" value="raw" ng-change="changeScale()"> Raw
</p>
<p>
<rzslider rz-slider-model="slider.value" rz-slider-options="slider.options"></rzslider>
</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-primary" ng-click="setValue()">Set</button>
</div>
</script>
<script type="text/ng-template" id="deleteDialog">
<div class="ngdialog-message">
<h2>Device to Delete?</h2>
<p>{{device.name}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteDevice(device)">Delete</button>
</div>
</script>
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="active"><a href="#!/">Bridge
Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div postrender-action="goToRow()">
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">Current devices ({{bridge.devices.length}})</h1>
</div>
<div class="panel-body">
<p>
<button class="btn btn-primary" type="submit" ng-click="renumberDevices()">Renumber Devices</button>
<button ng-if="bridge.securityInfo.useLinkButton" class="btn btn-primary" type="submit" ng-click="pushLinkButton()">Link</button>
</p>
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">ID</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="description">Description</th>
<th sortable-header col="deviceType">Type</th>
<th sortable-header col="targetDevice">Target</th>
<th sortable-header col="inactive">Inactive</th>
<th sortable-header col="noState">No State</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices" row-id="{{device.id}}" ng-class="{info: bridge.viewDevId == device.id}" >
<td>{{$index+1}}</td>
<td>{{device.id}}</td>
<td>{{device.name}}</td>
<td class="cr">{{device.description}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.inactive}}</td>
<td>{{device.noState}}</td>
<td>
<p>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'on')">Test ON</button>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'dim')">Test Dim</button>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'off')">Test OFF</button>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit/Copy</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">
Bridge Device DB Backup <a ng-click="toggleBk()"><span
class={{imgBkUrl}} aria-hidden="true"></span></a>
</h1>
</div>
<div ng-if="visibleBk" class="panel-body">
<p>Control your backups from this area. Use the default name by hitting backup or specify your own.</p>
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="backup-name">Backup
File Name</label>
<div class="col-xs-8 col-sm-7">
<input id="backup-name" class="form-control" type="text"
ng-model="optionalbackupname" placeholder="Optional">
</div>
<button type="submit" class="btn btn-primary"
ng-click="backupDeviceDb(optionalbackupname)">Backup
Device DB</button>
</div>
</form>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Filename</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="backup in bridge.backups">
<td>{{backup}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="restoreBackup(backup)">Restore</button>
<button class="btn btn-warning" type="submit"
ng-click="deleteBackup(backup)">Delete</button>
</td>
</tr>
</table>
</div>
</div>
<script type="text/ng-template" id="valueDialog">
<div class="ngdialog-message">
<h2>Select value</h2>
<p>
<input type="radio" ng-model="valueType" value="percentage" ng-change="changeScale()"> Percentage
<input type="radio" ng-model="valueType" value="raw" ng-change="changeScale()"> Raw
</p>
<p>
<rzslider rz-slider-model="slider.value" rz-slider-options="slider.options"></rzslider>
</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-primary" ng-click="setValue()">Set</button>
</div>
</script>
<script type="text/ng-template" id="deleteDialog">
<div class="ngdialog-message">
<h2>Device to Delete?</h2>
<p>{{device.name}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteDevice(device)">Delete</button>
</div>
</script>

View File

@@ -0,0 +1,143 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a href="#!/domoticzdevices">HAL
Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation" class="active"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Domoticz Device List
({{bridge.domoticzdevices.length}})</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Domoticz Device, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Domoticz Devices' list below will show what
is already setup for your Domoticz.</p>
<p class="text-muted">
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.decimal_percent}">Decimal Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the Domoticz.</p>
<scrollable-table watch="bridge.domoticzdevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">
<span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="type">Type</th>
<th sortable-header col="domoticzname">Domoticz</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="domoticzdevice in bridge.domoticzdevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{domoticzdevice.devicename}}"
ng-checked="bulk.devices.indexOf(domoticzdevice.devicename) > -1"
ng-click="toggleSelection(domoticzdevice.devicename)">
{{domoticzdevice.devicename}}</td>
<td>{{domoticzdevice.devicetype}}</td>
<td>{{domoticzdevice.domoticzname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(domoticzdevice, device_dim_control,false)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Domoticz Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.domoticzdevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="domoticzname">Domoticz</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredDomoticzItems">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -19,6 +19,9 @@
href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a
href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation" class="active"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -31,7 +34,7 @@
fields the bridge uses. Please use care when updating these fields as
you may break the settings used by the bridge to call a specific end
point device.</p>
<p class="text-muted">This area allows you to create any http or
<p class="text-muted">This area allows you to create any http, tcp or
udp call to an endpoint. You can use the default GET or select the
http verb type below and configure a payload for either on, dim or
off methods. For Execution of a
@@ -39,10 +42,10 @@
can use Json notation of array with [{&quot;item&quot;:&quot;the
payload&quot;},{&quot;item&quot;:&quot;another payload&quot;}] to
execute multiple entries. Adding the value replacements
(${intensity..byte},${intensity.percent},${intensity.math(X*1)}) will
(${intensity.byte},${intensity.percent},${intensity.decimal_percent},${intensity.math(X*1)}) will
also work. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command.</p>
<p>When copying, update the name and select the "Add Bridge
<p class="text-muted">When copying, update the name and select the "Add Bridge
Device" Button.</p>
<form name="form">
@@ -62,6 +65,41 @@
<td><input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"></td>
</tr>
<tr>
<td><label>Description</label></td>
<td><input type="text" class="form-control" id="device-description"
ng-model="device.description" placeholder="Device Description"></td>
</tr>
<tr>
<td><label>Comments</label></td>
<td><input type="text" class="form-control" id="device-comments"
ng-model="device.comments" placeholder="Device Comments"></td>
</tr>
<tr>
<td><label>Inactive</label></td>
<td><input type="checkbox"
ng-model="device.inactive" ng-true-value=true
ng-false-value=false> {{device.inactive}}</td>
</tr>
<tr>
<td><label>No State (Do not update state for device)</label></td>
<td><input type="checkbox"
ng-model="device.noState" ng-true-value=true
ng-false-value=false> {{device.noState}}</td>
</tr>
<tr>
<td><label>Off State Resets Bri</label></td>
<td><input type="checkbox"
ng-model="device.offState" ng-true-value=true
ng-false-value=false> {{device.offState}}</td>
</tr>
<tr>
<td><label>Filter Address (comma separated list)</label></td>
<td><input type="text" class="form-control" id="device-requester-addr"
ng-model="device.requesterAddress" placeholder="Only use if you want to restrict this device to a specific caller(s)"></td>
</tr>
<tr>
<td><label>Target</label></td>
@@ -116,6 +154,7 @@
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<div class="col-xs-12 col-md-4">
<th>Type</th>
<th>Target Item</th>
<th>Delay</th>
@@ -126,19 +165,21 @@
<th>Http Headers</th>
<th>Content Type</th>
<th>Manage</th>
</div>
</tr>
</thead>
<tr ng-repeat="onItem in onDevices">
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="onItem.type"></select></td>
<td><textarea rows="1" cols="20" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-target" ng-model="onItem.item" placeholder="The Call"></textarea></td>
<td><textarea rows="1" cols="4" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-delay" ng-model="onItem.delay" placeholder="millis"></textarea></td>
<td><textarea rows="1" cols="2" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-count" ng-model="onItem.count" placeholder="number"></textarea></td>
<td><textarea rows="1" cols="16" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-filterIPs" ng-model="onItem.filterIPs"
placeholder="restrict IPs"></textarea></td>
<td><select name="item-http-verb" id="item-http-verb"
@@ -149,7 +190,7 @@
<option value="PUT">PUT</option>
<option value="POST">POST</option>
</select></td>
<td><textarea rows="1" cols="16" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-httpBody" ng-model="onItem.httpBody"
placeholder="body args"></textarea></td>
<td><textarea rows="1" cols="16" class="form-control"
@@ -175,8 +216,10 @@
</select></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeItemOn(onItem)">Del</button></td>
</div>
</tr>
<tr>
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="newOnItem.type"></select></td>
@@ -226,6 +269,7 @@
</select></td>
<td><button class="btn btn-success" type="submit"
ng-click="addItemOn(newOnItem)">Add</button></td>
</div>
</tr>
</table>
</scrollable-table></td>
@@ -237,6 +281,7 @@
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<div class="col-xs-12 col-md-4">
<th>Type</th>
<th>Target Item</th>
<th>Delay</th>
@@ -247,9 +292,11 @@
<th>Http Headers</th>
<th>Content Type</th>
<th>Manage</th>
</div>
</tr>
</thead>
<tr ng-repeat="dimItem in dimDevices">
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="dimItem.type"></select></td>
@@ -297,8 +344,10 @@
</select></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeItemDim(dimItem)">Del</button></td>
</div>
</tr>
<tr>
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="newDimItem.type"></select></td>
@@ -348,6 +397,7 @@
</select></td>
<td><button class="btn btn-success" type="submit"
ng-click="addItemDim(newDimItem)">Add</button></td>
</div>
</tr>
</table>
</scrollable-table></td>
@@ -359,6 +409,7 @@
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<div class="col-xs-12 col-md-4">
<th>Type</th>
<th>Target Item</th>
<th>Delay</th>
@@ -369,9 +420,11 @@
<th>Http Headers</th>
<th>Content Type</th>
<th>Manage</th>
</div>
</tr>
</thead>
<tr ng-repeat="offItem in offDevices">
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="offItem.type"></select></td>
@@ -419,8 +472,10 @@
</select></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeItemOff(offItem)">Del</button></td>
</div>
</tr>
<tr>
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="newOffItem.type"></select></td>
@@ -470,6 +525,7 @@
</select></td>
<td><button class="btn btn-success" type="submit"
ng-click="addItemOff(newOffItem)">Add</button></td>
</div>
</tr>
</table>
</scrollable-table></td>

View File

@@ -1,175 +1,179 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li role="presentation" class="active"><a href="#!/haldevices">HAL
Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">HAL Device List
({{bridge.haldevices.length}})</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any HAL Device, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured HAL Devices' list below will show what
is already setup for your HAL.</p>
<p>
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p>Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the HAL.</p>
<scrollable-table watch="bridge.haldevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">
<span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="category">Category</th>
<th sortable-header col="halname">HAL</th>
<th>On Button</th>
<th>Off Button</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="haldevice in bridge.haldevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{haldevice.haldevicename}}"
ng-checked="bulk.devices.indexOf(haldevice.haldevicename) > -1"
ng-click="toggleSelection(haldevice.haldevicename)">
{{haldevice.haldevicename}}</td>
<td>{{haldevice.haldevicetype}}</td>
<td>{{haldevice.halname}}</td>
<td>
<select name="button-on" id="button-on" ng-model="button_on">
<option ng-repeat="aButtonOn in haldevice.buttons.DeviceElements"
value="{{aButtonOn}}">{{aButtonOn.DeviceName}}</option>
</select>
</td>
<td>
<select name="button-off" id="button-off" ng-model="button_off">
<option ng-repeat="aButtonOff in haldevice.buttons.DeviceElements"
value="{{aButtonOff}}">{{aButtonOff.DeviceName}}</option>
</select>
</td>
<td>
<button ng-if="haldevice.haldevicetype != 'Home' && haldevice.haldevicetype != 'HVAC' && haldevice.haldevicetype != 'IrData'" class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(haldevice, device_dim_control)">Build Item</button>
<button ng-if="haldevice.haldevicetype == 'Home'" class="btn btn-success" type="submit"
ng-click="buildHALHomeUrls(haldevice)">Build Home/Away</button>
<button ng-if="haldevice.haldevicetype == 'IrData'" class="btn btn-success" type="submit"
ng-click="buildButtonUrls(haldevice, button_on, button_off)">Build
A Button</button>
<ul ng-if="haldevice.haldevicetype == 'HVAC'" class="list-group">
<li class="list-group-item">
<p>
<button class="btn btn-success" type="submit"
ng-click="buildHALHeatUrls(haldevice)">Heat</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALCoolUrls(haldevice)">Cool</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALAutoUrls(haldevice)">Auto</button>
</p>
<p>
<button class="btn btn-success" type="submit"
ng-click="buildHALOffUrls(haldevice)">Off</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALFanUrls(haldevice)">Fan</button>
</p>
</li>
</ul>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured HAL Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.haldevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="halname">HAL</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredHalItems">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li role="presentation" class="active"><a href="#!/haldevices">HAL
Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">HAL Device List
({{bridge.haldevices.length}})</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any HAL Device, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured HAL Devices' list below will show what
is already setup for your HAL.</p>
<p class="text-muted">
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.decimal_percent}">Decimal Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the HAL.</p>
<scrollable-table watch="bridge.haldevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">
<span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="category">Category</th>
<th sortable-header col="halname">HAL</th>
<th>On Button</th>
<th>Off Button</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="haldevice in bridge.haldevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{haldevice.haldevicename}}"
ng-checked="bulk.devices.indexOf(haldevice.haldevicename) > -1"
ng-click="toggleSelection(haldevice.haldevicename)">
{{haldevice.haldevicename}}</td>
<td>{{haldevice.haldevicetype}}</td>
<td>{{haldevice.halname}}</td>
<td>
<select name="button-on" id="button-on" ng-model="button_on">
<option ng-repeat="aButtonOn in haldevice.buttons.DeviceElements"
value="{{aButtonOn}}">{{aButtonOn.DeviceName}}</option>
</select>
</td>
<td>
<select name="button-off" id="button-off" ng-model="button_off">
<option ng-repeat="aButtonOff in haldevice.buttons.DeviceElements"
value="{{aButtonOff}}">{{aButtonOff.DeviceName}}</option>
</select>
</td>
<td>
<button ng-if="haldevice.haldevicetype != 'Home' && haldevice.haldevicetype != 'HVAC' && haldevice.haldevicetype != 'IrData'" class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(haldevice, device_dim_control, false)">Build Item</button>
<button ng-if="haldevice.haldevicetype == 'Home'" class="btn btn-success" type="submit"
ng-click="buildHALHomeUrls(haldevice, false)">Build Home/Away</button>
<button ng-if="haldevice.haldevicetype == 'IrData'" class="btn btn-success" type="submit"
ng-click="buildButtonUrls(haldevice, button_on, button_off, false)">Build
A Button</button>
<ul ng-if="haldevice.haldevicetype == 'HVAC'" class="list-group">
<li class="list-group-item">
<p>
<button class="btn btn-success" type="submit"
ng-click="buildHALHeatUrls(haldevice, false)">Heat</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALCoolUrls(haldevice, false)">Cool</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALAutoUrls(haldevice, false)">Auto</button>
</p>
<p>
<button class="btn btn-success" type="submit"
ng-click="buildHALOffUrls(haldevice, false)">Off</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALFanUrls(haldevice, false)">Fan</button>
</p>
</li>
</ul>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured HAL Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.haldevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="halname">HAL</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredHalItems">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -1,110 +1,113 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li role="presentation" class="active"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li role="presentation"><a href="#!/harmonydevices">Harmony
Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Harmony Activity List</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Harmony Activity, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen.
Then you can modify the name to anything you want that
will be the keyword for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to
finish that selection setup. The 'Already Configured Activities' list
below will show what is already setup for your Harmony Hubs.</p>
<scrollable-table watch="bridge.harmonyactivities">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="label" comparator-fn="comparatorLabel">Name</th>
<th sortable-header col="id" comparator-fn="comparatorNumber">Id</th>
<th sortable-header col="hub" comparator-fn="comparatorHub">Hub</th>
<th>Build Actions</th>
</tr>
</thead>
<tr
ng-repeat="harmonyactivity in bridge.harmonyactivities">
<td>{{$index+1}}</td>
<td>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td>
<td>{{harmonyactivity.hub}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildActivityUrls(harmonyactivity)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Activities <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.harmonyactivities">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Hub</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredHarmonyActivities">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li role="presentation" class="active"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li role="presentation"><a href="#!/harmonydevices">Harmony
Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Harmony Activity List</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Harmony Activity, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen.
Then you can modify the name to anything you want that
will be the keyword for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to
finish that selection setup. The 'Already Configured Activities' list
below will show what is already setup for your Harmony Hubs.</p>
<scrollable-table watch="bridge.harmonyactivities">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="label" comparator-fn="comparatorLabel">Name</th>
<th sortable-header col="id" comparator-fn="comparatorNumber">Id</th>
<th sortable-header col="hub" comparator-fn="comparatorHub">Hub</th>
<th>Build Actions</th>
</tr>
</thead>
<tr
ng-repeat="harmonyactivity in bridge.harmonyactivities">
<td>{{$index+1}}</td>
<td>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td>
<td>{{harmonyactivity.hub}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildActivityUrls(harmonyactivity)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Activities <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.harmonyactivities">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Hub</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredHarmonyActivities">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -1,129 +1,132 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li role="presentation"><a href="#!/harmonyactivities">Harmony
Activities</a></li>
<li role="presentation" class="active"><a href="#!/harmonydevices">Harmony
Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Harmony Device List</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Harmony Device and Buttons, use the
build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then you can modify the name
to anything you want that will be the keyword for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the
'Add Bridge Device' to finish that selection setup. The 'Already
Configured Harmony Buttons' list below will show what is already
setup for your Harmony Hubs.</p>
<scrollable-table watch="bridge.harmonydevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="label" comparator-fn="comparatorLabel">Name</th>
<th sortable-header col="id" comparator-fn="comparatorNumber">Id</th>
<th sortable-header col="hub" comparator-fn="comparatorHub">Hub</th>
<th>On Button</th>
<th>Off Button</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="harmonydevice in bridge.harmonydevices">
<td>{{$index+1}}</td>
<td>{{harmonydevice.device.label}}</td>
<td>{{harmonydevice.device.id}}</td>
<td>{{harmonydevice.hub}}</td>
<td><select name="device-ctrlon" id="device-ctrlon"
ng-model="devicectrlon">
<optgroup ng-repeat="ctrlon in harmonydevice.device.controlGroup"
label="{{ctrlon.name}}">
<option ng-repeat="funcon in ctrlon.function"
value="{{funcon.action}}">{{funcon.label}}</option>
</optgroup>
</select></td>
<td><select name="device-ctrloff" id="device-ctrloff"
ng-model="devicectrloff">
<optgroup ng-repeat="ctrloff in harmonydevice.device.controlGroup"
label="{{ctrloff.name}}">
<option ng-repeat="funcoff in ctrloff.function"
value="{{funcoff.action}}">{{funcoff.label}}</option>
</optgroup>
</select></td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff)">Build
A Button</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Harmony Buttons <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.harmonydevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Hub</th>
<th>Harmony Device-Button On-Button Off</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredHarmonyButtons | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li role="presentation"><a href="#!/harmonyactivities">Harmony
Activities</a></li>
<li role="presentation" class="active"><a href="#!/harmonydevices">Harmony
Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Harmony Device List</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Harmony Device and Buttons, use the
build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then you can modify the name
to anything you want that will be the keyword for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the
'Add Bridge Device' to finish that selection setup. The 'Already
Configured Harmony Buttons' list below will show what is already
setup for your Harmony Hubs.</p>
<scrollable-table watch="bridge.harmonydevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="label" comparator-fn="comparatorLabel">Name</th>
<th sortable-header col="id" comparator-fn="comparatorNumber">Id</th>
<th sortable-header col="hub" comparator-fn="comparatorHub">Hub</th>
<th>On Button</th>
<th>Off Button</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="harmonydevice in bridge.harmonydevices">
<td>{{$index+1}}</td>
<td>{{harmonydevice.device.label}}</td>
<td>{{harmonydevice.device.id}}</td>
<td>{{harmonydevice.hub}}</td>
<td><select name="device-ctrlon" id="device-ctrlon"
ng-model="devicectrlon">
<optgroup ng-repeat="ctrlon in harmonydevice.device.controlGroup"
label="{{ctrlon.name}}">
<option ng-repeat="funcon in ctrlon.function"
value="{{funcon.action}}">{{funcon.label}}</option>
</optgroup>
</select></td>
<td><select name="device-ctrloff" id="device-ctrloff"
ng-model="devicectrloff">
<optgroup ng-repeat="ctrloff in harmonydevice.device.controlGroup"
label="{{ctrloff.name}}">
<option ng-repeat="funcoff in ctrloff.function"
value="{{funcoff.action}}">{{funcoff.label}}</option>
</optgroup>
</select></td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff)">Build
A Button</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Harmony Buttons <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.harmonydevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Hub</th>
<th>Harmony Device-Button On-Button Off</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredHarmonyButtons | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -13,11 +13,13 @@
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation" class="active"><a href="#!/haldevices">HAL
<li ng-if="bridge.showHal" role="presentation"><a href="#!/haldevices">HAL
Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li role="presentation" class="active"><a href="#!/hassdevices">HomeAssistant
Devices</a></li>
<li role="presentation" class="active"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -35,17 +37,18 @@
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured HomeAssistant Devices' list below will show what
is already setup for your HomeAssitant.</p>
<p>
<p class="text-muted">
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
like to be generated, BUT for Home Assistant, the selection should be Pass-thru: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.decimal_percent}">Decimal Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p>Use the check boxes by the names to use the bulk addition
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the HomeAssitant.</p>
@@ -72,26 +75,8 @@
{{hassdevice.deviceState.entity_id}}</td>
<td>{{hassdevice.hassname}}</td>
<td>
<button ng-if="hassdevice.domain != 'climate' && hassdevice.domain != 'sensor' && hassdevice.domain != 'sun'" class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(hassdevice, device_dim_control)">Build Item</button>
<ul ng-if="hassdevice.domain == 'climate'" class="list-group">
<li class="list-group-item">
<p>
<button class="btn btn-success" type="submit"
ng-click="buildhassHeatUrls(hassdevice)">Heat</button>
<button class="btn btn-success" type="submit"
ng-click="buildhassCoolUrls(hassdevice)">Cool</button>
<button class="btn btn-success" type="submit"
ng-click="buildhassAutoUrls(hassdevice)">Auto</button>
</p>
<p>
<button class="btn btn-success" type="submit"
ng-click="buildhassOffUrls(hassdevice)">Off</button>
<button class="btn btn-success" type="submit"
ng-click="buildhassFanUrls(hassdevice)">Fan</button>
</p>
</li>
</ul>
<button ng-if="hassdevice.domain != 'sensor' && hassdevice.domain != 'sun'" class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(hassdevice, device_dim_control, false)">Build Item</button>
</td>
</tr>
</table>

View File

@@ -1,125 +1,128 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li role="presentation" class="active"><a href="#!/huedevices">Hue
Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Hue Device List
({{bridge.huedevices.length}})</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Hue Device, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Hue Devices' list below will show what
is already setup for your Hue.</p>
<p>Use the check boxes by the names to use the bulk addition
feature. Select your items, then click bulk add below. Your items
will be added with on and off or dim and off if selected with the
name of the device from the Hue.</p>
<scrollable-table watch="bridge.huedevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name"><span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="id">Id</th>
<th sortable-header col="huename">Hue</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="huedevice in bridge.huedevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{huedevice.device.uniqueid}}"
ng-checked="bulk.devices.indexOf(huedevice.device.uniqueid) > -1"
ng-click="toggleSelection(huedevice.device.uniqueid)">
{{huedevice.device.name}}</td>
<td>{{huedevice.device.uniqueid}}</td>
<td>{{huedevice.huename}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(huedevice)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices()">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Hue Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.huedevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">hue</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredHueItems">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li role="presentation" class="active"><a href="#!/huedevices">Hue
Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Hue Device List
({{bridge.huedevices.length}})</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Hue Device, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Hue Devices' list below will show what
is already setup for your Hue.</p>
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items, then click bulk add below. Your items
will be added with on, off and dim with the
name of the device from the Hue.</p>
<scrollable-table watch="bridge.huedevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name"><span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="id">Id</th>
<th sortable-header col="huename">Hue</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="huedevice in bridge.huedevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{huedevice.device.uniqueid}}"
ng-checked="bulk.devices.indexOf(huedevice.device.uniqueid) > -1"
ng-click="toggleSelection(huedevice.device.uniqueid)">
{{huedevice.device.name}}</td>
<td>{{huedevice.device.uniqueid}}</td>
<td>{{huedevice.huename}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(huedevice, false)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices()">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Hue Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.huedevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">hue</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredHueItems">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -0,0 +1,123 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li role="presentation" class="active"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">LIFX Devices</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any LIFX Device, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured LIFX Devices' list below will show what
is already setup for your LIFX.</p>
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on, off and dim
with the name of the device from the LIFX device.</p>
<scrollable-table watch="bridge.lifxdevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">
<span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="id">ID</th>
<th sortable-header col="type">Type</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="lifxdev in bridge.lifxdevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{lifxdev.name}}"
ng-checked="bulk.devices.indexOf(lifxdev.name) > -1"
ng-click="toggleSelection(lifxdev.name)">{{lifxdev.name}}</td>
<td>{{lifxdev.id}}</td>
<td>{{lifxdev.type}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(lifxdev, false)">Build
Item</button>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured LIFX Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.lifxdevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Light Name</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredLifxItems | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -0,0 +1,27 @@
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Login</h2>
</div>
<div class="panel-body">
<div class="form-container">
<form name="loginForm" role="form">
<legend class="form-label">Enter Credentials</legend>
<div class="form-group">
<label>User</label> <input id="username" name="username"
class="form-control" type="text" ng-model="username"
placeholder="someone" />
</div>
<div class="form-group">
<label>Password</label> <input id="password" name="password"
class="form-control" type="password" ng-model="password" />
</div>
<div class="form-group">
<button type="button" class="btn btn-success" ng-click="login(username, password)">Submit</button>
<button type="button" class="btn btn-danger" ng-click="logout()">Logout</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -1,87 +1,90 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation" class="active"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">Log Messages</h1>
</div>
<div class="panel-body">
<p>
<button class="btn btn-primary" type="submit" ng-click="updateLogs()">Update
Log</button>
</p>
<scrollable-table watch="bridge.logMsgs">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th sortable-header col="time">Time</th>
<th sortable-header col="level">Level</th>
<th sortable-header col="message">Message</th>
<th sortable-header col="component">Component</th>
</tr>
</thead>
<tr ng-repeat="logMessage in bridge.logMsgs">
<td>{{logMessage.time}}</td>
<td>{{logMessage.level}}</td>
<td>{{logMessage.message}}</td>
<td>{{logMessage.component}}</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">
Logging Configuration <a ng-click="toggle()"><span
class={{imgUrl}} aria-hidden="true"></a>
</h1>
</div>
<div ng-if="visible" class="panel-body">
<p>
<button class="btn btn-primary" type="submit"
ng-click="updateLoggers()">Update Log Levels</button>
Show All Loggers <input type="checkbox" ng-model="bridge.logShowAll"
ng-change="reloadLoggers()" ng-true-value=true ng-false-value=false>
{{bridge.logShowAll}}
</p>
<scrollable-table watch="bridge.loggerInfo">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th sortable-header col="logLevel">Log Level</th>
<th sortable-header col="loggerName">Component</th>
<th>New Log Level</th>
</tr>
</thead>
<tr ng-repeat="logInfo in bridge.loggerInfo">
<td>{{logInfo.logLevel.substr(0,logInfo.logLevel.indexOf("_"))}}</td>
<td>{{logInfo.loggerName}}</td>
<td><select name="new-log-level" id="new-log-level"
ng-change="addToUpdate(logInfo)" ng-model="logInfo.newLogLevel">
<option ng-repeat="alevel in levels" value="{{alevel}}">{{alevel.substr(0,alevel.indexOf("_"))}}</option>
</select></td>
</tr>
</table>
</scrollable-table>
</div>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation" class="active"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">Log Messages</h1>
</div>
<div class="panel-body">
<p>
<button class="btn btn-primary" type="submit" ng-click="updateLogs()">Update
Log</button>
</p>
<scrollable-table watch="bridge.logMsgs">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th sortable-header col="time">Time</th>
<th sortable-header col="level">Level</th>
<th sortable-header col="message">Message</th>
<th sortable-header col="component">Component</th>
</tr>
</thead>
<tr ng-repeat="logMessage in bridge.logMsgs">
<td>{{logMessage.time}}</td>
<td>{{logMessage.level}}</td>
<td>{{logMessage.message}}</td>
<td>{{logMessage.component}}</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">
Logging Configuration <a ng-click="toggle()"><span
class={{imgUrl}} aria-hidden="true"></a>
</h1>
</div>
<div ng-if="visible" class="panel-body">
<p>
<button class="btn btn-primary" type="submit"
ng-click="updateLoggers()">Update Log Levels</button>
Show All Loggers <input type="checkbox" ng-model="bridge.logShowAll"
ng-change="reloadLoggers()" ng-true-value=true ng-false-value=false>
{{bridge.logShowAll}}
</p>
<scrollable-table watch="bridge.loggerInfo">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th sortable-header col="logLevel">Log Level</th>
<th sortable-header col="loggerName">Component</th>
<th>New Log Level</th>
</tr>
</thead>
<tr ng-repeat="logInfo in bridge.loggerInfo">
<td>{{logInfo.logLevel.substr(0,logInfo.logLevel.indexOf("_"))}}</td>
<td>{{logInfo.loggerName}}</td>
<td><select name="new-log-level" id="new-log-level"
ng-change="addToUpdate(logInfo)" ng-model="logInfo.newLogLevel">
<option ng-repeat="alevel in levels" value="{{alevel}}">{{alevel.substr(0,alevel.indexOf("_"))}}</option>
</select></td>
</tr>
</table>
</scrollable-table>
</div>
</div>

View File

@@ -1,115 +1,118 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a href="#!/haldevices">HAL Devices</a></li>
<li role="presentation" class="active"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">MQTT Messages</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any MQTT Broker, use the
build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen.
You can add topic and content in the text areas provided
then selecting the publish generation. Then you can modify the name
to anything you want that will be the keyword for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the
'Add Bridge Device' to finish that selection setup. The 'Already
Configured MQTT Publish messages' list below will show what is already
setup for your MQTT Brokers.</p>
<scrollable-table watch="bridge.mqttbrokers">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">ClientID</th>
<th sortable-header col="ip">IP</th>
<th>Topic</th>
<th>Content</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="mqttbroker in bridge.mqttbrokers">
<td>{{$index+1}}</td>
<td>{{mqttbroker.clientId}}</td>
<td>{{mqttbroker.ip}}</td>
<td>
<textarea rows="2" class="form-control" id="mqtt-topic"
ng-model="mqtttopic" placeholder="The MQTT Topic"></textarea>
</td>
<td>
<textarea rows="2" class="form-control" id="mqtt-content"
ng-model="mqttcontent" placeholder="The MQTT Message Content"></textarea>
</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildMQTTPublish(mqttbroker, mqtttopic, mqttcontent)">Build
publish Message</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured MQTT Messages <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.mqttbrokers">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="nameclientid">Name</th>
<th sortable-header col="targetDevice">ClientID</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredMqttMsgs | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a href="#!/haldevices">HAL Devices</a></li>
<li role="presentation" class="active"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">MQTT Messages</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any MQTT Broker, use the
build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen.
You can add topic and content in the text areas provided
then selecting the publish generation. Then you can modify the name
to anything you want that will be the keyword for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the
'Add Bridge Device' to finish that selection setup. The 'Already
Configured MQTT Publish messages' list below will show what is already
setup for your MQTT Brokers.</p>
<scrollable-table watch="bridge.mqttbrokers">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">ClientID</th>
<th sortable-header col="ip">IP</th>
<th>Topic</th>
<th>Content</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="mqttbroker in bridge.mqttbrokers">
<td>{{$index+1}}</td>
<td>{{mqttbroker.clientId}}</td>
<td>{{mqttbroker.ip}}</td>
<td>
<textarea rows="2" class="form-control" id="mqtt-topic"
ng-model="mqtttopic" placeholder="The MQTT Topic"></textarea>
</td>
<td>
<textarea rows="2" class="form-control" id="mqtt-content"
ng-model="mqttcontent" placeholder="The MQTT Message Content"></textarea>
</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildMQTTPublish(mqttbroker, mqtttopic, mqttcontent)">Build
publish Message</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured MQTT Messages <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.mqttbrokers">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="nameclientid">Name</th>
<th sortable-header col="targetDevice">ClientID</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredMqttMsgs | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -1,131 +1,134 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li role="presentation" class="active"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Nest Items List</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Nest Item, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Nest Items' list below will show what
is already setup for your Nest.</p>
<scrollable-table watch="bridge.nestitems">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="type">Type</th>
<th sortable-header col="location">Location</th>
<th>Build Actions</th>
</tr>
</thead>
<tr
ng-repeat="nestitem in bridge.nestitems | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{nestitem.name}}</td>
<td>{{nestitem.type}}</td>
<td>{{nestitem.location}}</td>
<td>
<ul class="list-group">
<li ng-if="nestitem.type ==='Home' " class="list-group-item">
<button class="btn btn-success" type="submit"
ng-click="buildNestHomeUrls(nestitem)">Home/Away</button>
</li>
<li ng-if="nestitem.type ==='Thermostat' " class="list-group-item">
<p>
<button class="btn btn-success" type="submit"
ng-click="buildNestTempUrls(nestitem)">Temp</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestHeatUrls(nestitem)">Heat</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestCoolUrls(nestitem)">Cool</button>
</p>
<p>
<button class="btn btn-success" type="submit"
ng-click="buildNestRangeUrls(nestitem)">Range</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestOffUrls(nestitem)">Off</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestFanUrls(nestitem)">Fan</button>
</p>
</li>
</ul>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Nest Items <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.nestitems">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id">Location</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices | configuredNestItems | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a
href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li role="presentation" class="active"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Nest Items List</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Nest Item, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Nest Items' list below will show what
is already setup for your Nest.</p>
<scrollable-table watch="bridge.nestitems">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="type">Type</th>
<th sortable-header col="location">Location</th>
<th>Build Actions</th>
</tr>
</thead>
<tr
ng-repeat="nestitem in bridge.nestitems | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{nestitem.name}}</td>
<td>{{nestitem.type}}</td>
<td>{{nestitem.location}}</td>
<td>
<ul class="list-group">
<li ng-if="nestitem.type ==='Home' " class="list-group-item">
<button class="btn btn-success" type="submit"
ng-click="buildNestHomeUrls(nestitem)">Home/Away</button>
</li>
<li ng-if="nestitem.type ==='Thermostat' " class="list-group-item">
<p>
<button class="btn btn-success" type="submit"
ng-click="buildNestTempUrls(nestitem)">Temp</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestHeatUrls(nestitem)">Heat</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestCoolUrls(nestitem)">Cool</button>
</p>
<p>
<button class="btn btn-success" type="submit"
ng-click="buildNestRangeUrls(nestitem)">Range</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestOffUrls(nestitem)">Off</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestFanUrls(nestitem)">Fan</button>
</p>
</li>
</ul>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Nest Items <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.nestitems">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id">Location</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices | configuredNestItems | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -0,0 +1,57 @@
<div class="form-container ngdialog-message" ng-controller="SecurityDialogCtrl">
<form name="securityForm" role="form">
<legend class="form-label">Update Security Settings</legend>
<div class="form-group">
<label>Use Link Button</label>
<input type="checkbox"
ng-model="useLinkButton" ng-true-value=true
ng-false-value=false> {{useLinkButton}}
</div>
<div class="form-group">
<label>Use username/password for HUE Api</label>
<input type="checkbox"
ng-model="secureHueApi" ng-true-value=true
ng-false-value=false> {{secureHueApi}}
</div>
<div class="form-group">
<label>Secure Folder for scripts/executables</label>
<input id="exec-garden" class="form-control"
type="text" ng-model="execGarden"
placeholder="/home/pi/protectedscripts">
</div>
<div class="form-group">
<button type="button" class="btn btn-primary" ng-click="setSecurityInfo()">Update</button>
</div>
<div class="form-group">
<label>Add/Delete User</label>
<input id="new-user" name="new-user" class="form-control"
type="text" ng-model="newUser"
placeholder="someone" nu-check="new-user">
</div>
<div class="form-group">
<button type="button" class="btn btn-danger" ng-click="delUser(newUser)">Delete</button>
</div>
<div ng-if="showPassword" postrender-action="setBlankPassword('password-1')">
<div class="form-group">
<label>Change Password for {{username}}</label>
<input id="password-1" name="password-1" type="password" class="form-control strength" ng-model="password" data-toggle-title="Display Password" />
</div>
<div class="form-group">
<label>Confirm Password</label>
<input id="password-2" name="password-2" class="form-control" type="password" ng-model="password2" pw-check="password-1" />
<div class="msg-block" ng-show="securityForm.$error">
<span class="msg-error" ng-show="securityForm.$error.pwmatch">Passwords don't match.</span>
</div>
</div>
<div ng-if="matched" class="form-group">
<button ng-if="!addingUser" class="btn btn-warning" ng-click="changePassword(password, password2)">Change Password</button>
<button ng-if="addingUser" class="btn btn-success" ng-click="addUser(newUser, password, password2)">Add User</button>
</div>
</div>
<div class="form-group">
<button type="button" class="btn btn-success" ng-click="dismissDialog()">Dismiss</button>
</div>
</form>
</div>

View File

@@ -0,0 +1,143 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li role="presentation"><a href="#!/veradevices">Vera
Devices</a></li>
<li role="presentation"><a href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation" class="active"><a href="#!/somfydevices">Somfy Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Somfy Device List
({{bridge.somfydevices.length}})</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Somfy Device, use the build action buttons
to generate the item addition information into the ha-bridge device
and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Somfy Devices' list below will show
what is already setup for your Somfy.</p>
<!-- TODO - dim support for partial window opening.. --<p>
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p-->
<p>Use the check boxes by the names to use the bulk addition
feature. Select your items, then click
bulk add below. Your items will be added with the name of the device from the Somfy Tahoma.</p>
</div>
<scrollable-table watch="bridge.somfydevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name"><span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">Id</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="somfyname">Somfy</th>
<th>Build Actions</th>
</tr>
</thead>
<tr
ng-repeat="somfydevice in bridge.somfydevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{somfydevice.id}}"
ng-checked="bulk.devices.indexOf(somfydevice.id) > -1"
ng-click="toggleSelection(somfydevice.id)">
{{somfydevice.name}}</td>
<td>{{somfydevice.id}}</td>
<td>{{somfydevice.category}}</td>
<td>{{somfydevice.room}}</td>
<td>{{somfydevice.somfyname}}</td>
<td>
<!--TODO - taken device_dim_control out of here since not yet used-->
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(somfydevice)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Somfy Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Somfy</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices |configuredSomfyDevices">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -18,6 +18,9 @@
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -55,6 +58,8 @@
location.reload();
}
</script>
<button class="btn btn-warning"
type="submit" ng-click="changeSeuritySettings()">Update Security Settings</button>
</p>
<table class="table table-bordered table-striped table-hover">
<thead>
@@ -139,25 +144,30 @@
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>IP</th>
<th>Webhook</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="harmony in bridge.settings.harmonyaddress.devices">
<td>{{harmony.name}}</td>
<td>{{harmony.ip}}</td>
<td>{{harmony.ip}}</td>
<td>{{harmony.webhook}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHarmonytoSettings(harmony.name, harmony.ip)">Del</button></td>
ng-click="removeHarmonytoSettings(harmony.name, harmony.ip, harmony.webhook)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-harmony-name"
class="form-control" type="text" ng-model="newharmonyname"
placeholder="A Harmony"></td>
<td><input id="bridge-settings-next-harmony-ip"
class="form-control" type="text" ng-model="newharmonyip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-harmony-ip"
class="form-control" type="text" ng-model="newharmonyip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-harmony-webhook"
class="form-control" type="text" ng-model="newharmonywebhook"
placeholder="http://hook?a=${activity.label}"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHarmonytoSettings(newharmonyname, newharmonyip)">Add</button></td>
ng-click="addHarmonytoSettings(newharmonyname, newharmonyip, newharmonywebhook)">Add</button></td>
</tr>
</table></td>
</tr>
@@ -276,6 +286,7 @@
<th>IP</th>
<th>Port</th>
<th>Password (opt)</th>
<th>Use SSL</th>
<th>Manage</th>
</tr>
</thead>
@@ -285,6 +296,7 @@
<td>{{hass.port}}</td>
<td ng-if="hass.password">*******</td>
<td ng-if="!hass.password"> </td>
<td>{{hass.secure}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHasstoSettings(hass.name, hass.ip)">Del</button></td>
</tr>
@@ -301,11 +313,99 @@
<td><input id="bridge-settings-next-hass-password"
class="form-control" type="password" ng-model="newhasspassword"
placeholder="Home Assistant password (opt)"></td>
<td><input type="checkbox"
ng-model="newhasssecure" ng-true-value=true
ng-false-value=false></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHasstoSettings(newhassname, newhassip, newhassport, newhasspassword)">Add</button></td>
ng-click="addHasstoSettings(newhassname, newhassip, newhassport, newhasspassword, newhasssecure)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>Domoticz Names and IP Addresses</td>
<td><table
class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Port</th>
<th>Username (opt)</th>
<th>Password (opt)</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="domoticz in bridge.settings.domoticzaddress.devices">
<td>{{domoticz.name}}</td>
<td>{{domoticz.ip}}</td>
<td>{{domoticz.port}}</td>
<td>{{domoticz.username}}</td>
<td ng-if="domoticz.password">*******</td>
<td ng-if="!domoticz.password"> </td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeDomoticztoSettings(domoticz.name, domoticz.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-domoticz-name"
class="form-control" type="text" ng-model="newdomoticzname"
placeholder="A Domoticz"></td>
<td><input id="bridge-settings-next-domoticz-ip"
class="form-control" type="text" ng-model="newdomoticzip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-domoticz-port"
class="form-control" type="text" ng-model="newdomoticzport"
placeholder="8080"></td>
<td><input id="bridge-settings-next-domoticz-username"
class="form-control" type="text" ng-model="newdomoticzusername"
placeholder="Domoticz username"></td>
<td><input id="bridge-settings-next-domoticz-password"
class="form-control" type="password" ng-model="newdomoticzpassword"
placeholder="Domoticz password (opt)"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addDomoticztoSettings(newdomoticzname, newdomoticzip, newdomoticzport, newdomoticzusername, newdomoticzpassword)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>Somfy Names and IP Addresses</td>
<td><table
class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>IP/hostname</th>
<th>Username</th>
<th>Password </th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="somfy in bridge.settings.somfyaddress.devices">
<td>{{somfy.name}}</td>
<td>{{somfy.ip}}</td>
<td>{{somfy.username}}</td>
<td ng-if="somfy.password">*******</td>
<td ng-if="!somfy.password"> </td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeSomfytoSettings(somfy.name, somfy.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-somfy-name"
class="form-control" type="text" ng-model="newsomfyname"
placeholder="A Somfy"></td>
<td><input id="bridge-settings-next-somfy-ip"
class="form-control" type="text" ng-model="newsomfyip"
placeholder="https://www.tahomalink.com"></td>
<td><input id="bridge-settings-next-somfy-username"
class="form-control" type="text" ng-model="newsomfyusername"
placeholder="Somfy username"></td>
<td><input id="bridge-settings-next-somfy-password"
class="form-control" type="password" ng-model="newsomfypassword"
placeholder="Somfy password"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addSomfytoSettings(newsomfyname, newsomfyip, newsomfyusername, newsomfypassword)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>Nest Username</td>
<td><input id="bridge-settings-nestuser" class="form-control"
@@ -324,6 +424,12 @@
ng-model="bridge.settings.farenheit" ng-true-value=true
ng-false-value=false> {{bridge.settings.farenheit}}</td>
</tr>
<tr>
<td>LIFX Support</td>
<td><input type="checkbox"
ng-model="bridge.settings.lifxconfigured" ng-true-value=true
ng-false-value=false> {{bridge.settings.lifxconfigured}}</td>
</tr>
<tr>
<td>Emulate Hue Hub Version</td>
<td><input id="bridge-settings-hubversion" class="form-control"
@@ -369,7 +475,7 @@
<div class="panel-heading">
<h1 class="panel-title">
Bridge Settings Backup <a ng-click="toggle()"><span
class={{imgUrl}} aria-hidden="true"></a>
class={{imgUrl}} aria-hidden="true"></span></a>
</h1>
</div>
<div ng-if="visible" class="panel-body">

View File

@@ -1,140 +1,144 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li role="presentation" class="active"><a href="#!/veradevices">Vera
Devices</a></li>
<li role="presentation"><a href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Vera Device List
({{bridge.veradevices.length}})</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Vera Device, use the build action buttons
to generate the item addition information into the ha-bridge device
and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Vera Devices' list below will show
what is already setup for your Vera.</p>
<p>
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p>Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the Vera.</p>
<scrollable-table watch="bridge.veradevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name"><span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">Id</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Build Actions</th>
</tr>
</thead>
<tr
ng-repeat="veradevice in bridge.veradevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{veradevice.id}}"
ng-checked="bulk.devices.indexOf(veradevice.id) > -1"
ng-click="toggleSelection(veradevice.id)">
{{veradevice.name}}</td>
<td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td>
<td>{{veradevice.room}}</td>
<td>{{veradevice.veraname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(veradevice, device_dim_control)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Vera Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Vera</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredVeraDevices">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li role="presentation" class="active"><a href="#!/veradevices">Vera
Devices</a></li>
<li role="presentation"><a href="#!/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Vera Device List
({{bridge.veradevices.length}})</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Vera Device, use the build action buttons
to generate the item addition information into the ha-bridge device
and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Vera Devices' list below will show
what is already setup for your Vera.</p>
<p class="text-muted">
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.decimal_percent}">Decimal Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the Vera.</p>
<scrollable-table watch="bridge.veradevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name"><span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">Id</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Build Actions</th>
</tr>
</thead>
<tr
ng-repeat="veradevice in bridge.veradevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{veradevice.id}}"
ng-checked="bulk.devices.indexOf(veradevice.id) > -1"
ng-click="toggleSelection(veradevice.id)">
{{veradevice.name}}</td>
<td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td>
<td>{{veradevice.room}}</td>
<td>{{veradevice.veraname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(veradevice, device_dim_control, false)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Vera Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Vera</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredVeraDevices">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -1,110 +1,113 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li role="presentation"><a href="#!/veradevices">Vera Devices</a></li>
<li role="presentation" class="active"><a href="#!/verascenes">Vera
Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Vera Scene List</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Vera Scene, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Vera Scenes' list below will show what
is already setup for your Vera.</p>
<scrollable-table watch="bridge.verascenes">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">Id</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="verascene in bridge.verascenes">
<td>{{$index+1}}</td>
<td>{{verascene.name}}</td>
<td>{{verascene.id}}</td>
<td>{{verascene.room}}</td>
<td>{{verascene.veraname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildSceneUrls(verascene)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Vera Scenes <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.verascenes">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Vera</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredVeraScenes">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#!/">Bridge Devices</a></li>
<li role="presentation"><a href="#!/system">Bridge Control</a></li>
<li role="presentation"><a href="#!/logs">Logs</a></li>
<li role="presentation"><a href="#!/veradevices">Vera Devices</a></li>
<li role="presentation" class="active"><a href="#!/verascenes">Vera
Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a
href="#!/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#!/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#!/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#!/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Vera Scene List</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any Vera Scene, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Vera Scenes' list below will show what
is already setup for your Vera.</p>
<scrollable-table watch="bridge.verascenes">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">Id</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="verascene in bridge.verascenes">
<td>{{$index+1}}</td>
<td>{{verascene.name}}</td>
<td>{{verascene.id}}</td>
<td>{{verascene.room}}</td>
<td>{{verascene.veraname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildSceneUrls(verascene)">Build Item</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured Vera Scenes <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.verascenes">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Vera</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredVeraScenes">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -0,0 +1,21 @@
package com.bwssystems.color.test;
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import com.bwssystems.HABridge.hue.ColorDecode;
public class ConvertCIEColorTestCase {
@Test
public void testColorConversion() {
ArrayList<Double> xy = new ArrayList<Double>(Arrays.asList(new Double(0.3972), new Double(0.4564)));
String colorDecode = ColorDecode.convertCIEtoRGB(xy);
Assert.assertEquals(colorDecode, null);
}
}

View File

@@ -0,0 +1,45 @@
package com.bwssystems.domoticz.test;
import java.util.Iterator;
import com.bwssystems.HABridge.plugins.domoticz.DeviceResult;
import com.bwssystems.HABridge.plugins.domoticz.Devices;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class DomoticzDeviceConstructor {
public final static String DevicesTestData = "{ \"ActTime\" : 1485295582, \"ServerTime\" : \"2017-01-24 16:06:22\", \"Sunrise\" : \"07:11\", \"Sunset\" : \"16:53\", \"result\" : [ { \"AddjMulti\" : 1.0, \"AddjMulti2\" : 1.0, \"AddjValue\" : 0.0, \"AddjValue2\" : 0.0, \"BatteryLevel\" : 255, \"CustomImage\" : 2, \"Data\" : \"On\", \"Description\" : \"\", \"Favorite\" : 1, \"HardwareID\" : 3, \"HardwareName\" : \"MyHue\", \"HardwareType\" : \"Philips Hue Bridge\", \"HardwareTypeVal\" : 38, \"HaveDimmer\" : true, \"HaveGroupCmd\" : false, \"HaveTimeout\" : false, \"ID\" : \"1\", \"Image\" : \"TV\", \"IsSubDevice\" : false, \"LastUpdate\" : \"2017-01-23 17:15:22\", \"Level\" : 0, \"LevelInt\" : 0, \"MaxDimLevel\" : 100, \"Name\" : \"TV\", \"Notifications\" : \"false\", \"PlanID\" : \"0\", \"PlanIDs\" : [ 0 ], \"Protected\" : false, \"ShowNotifications\" : true, \"SignalLevel\" : \"-\", \"Status\" : \"On\", \"StrParam1\" : \"\", \"StrParam2\" : \"\", \"SubType\" : \"RGBW\", \"SwitchType\" : \"On/Off\", \"SwitchTypeVal\" : 0, \"Timers\" : \"false\", \"Type\" : \"Lighting Limitless/Applamp\", \"TypeImg\" : \"lightbulb\", \"Unit\" : 1, \"Used\" : 1, \"UsedByCamera\" : false, \"XOffset\" : \"0\", \"YOffset\" : \"0\", \"idx\" : \"23\" }, { \"AddjMulti\" : 1.0, \"AddjMulti2\" : 1.0, \"AddjValue\" : 0.0, \"AddjValue2\" : 0.0, \"BatteryLevel\" : 255, \"CustomImage\" : 0, \"Data\" : \"On\", \"Description\" : \"\", \"Favorite\" : 1, \"HardwareID\" : 3, \"HardwareName\" : \"MyHue\", \"HardwareType\" : \"Philips Hue Bridge\", \"HardwareTypeVal\" : 38, \"HaveDimmer\" : true, \"HaveGroupCmd\" : false, \"HaveTimeout\" : false, \"ID\" : \"0000000B\", \"Image\" : \"Light\", \"IsSubDevice\" : false, \"LastUpdate\" : \"2017-01-23 16:15:31\", \"Level\" : 0, \"LevelInt\" : 0, \"MaxDimLevel\" : 100, \"Name\" : \"lights\", \"Notifications\" : \"false\", \"PlanID\" : \"0\", \"PlanIDs\" : [ 0 ], \"Protected\" : false, \"ShowNotifications\" : true, \"SignalLevel\" : \"-\", \"Status\" : \"On\", \"StrParam1\" : \"\", \"StrParam2\" : \"\", \"SubType\" : \"RGBW\", \"SwitchType\" : \"On/Off\", \"SwitchTypeVal\" : 0, \"Timers\" : \"false\", \"Type\" : \"Lighting Limitless/Applamp\", \"TypeImg\" : \"lightbulb\", \"Unit\" : 1, \"Used\" : 1, \"UsedByCamera\" : false, \"XOffset\" : \"0\", \"YOffset\" : \"0\", \"idx\" : \"25\" }, { \"AddjMulti\" : 1.0, \"AddjMulti2\" : 1.0, \"AddjValue\" : 0.0, \"AddjValue2\" : 0.0, \"BatteryLevel\" : 255, \"CustomImage\" : 0, \"Data\" : \"Off\", \"Description\" : \"\", \"Favorite\" : 1, \"HardwareID\" : 3, \"HardwareName\" : \"MyHue\", \"HardwareType\" : \"Philips Hue Bridge\", \"HardwareTypeVal\" : 38, \"HaveDimmer\" : true, \"HaveGroupCmd\" : false, \"HaveTimeout\" : false, \"ID\" : \"00000014\", \"Image\" : \"Light\", \"IsSubDevice\" : false, \"LastUpdate\" : \"2017-01-23 11:25:59\", \"Level\" : 0, \"LevelInt\" : 0, \"MaxDimLevel\" : 100, \"Name\" : \"testUDP\", \"Notifications\" : \"false\", \"PlanID\" : \"0\", \"PlanIDs\" : [ 0 ], \"Protected\" : false, \"ShowNotifications\" : true, \"SignalLevel\" : \"-\", \"Status\" : \"Off\", \"StrParam1\" : \"\", \"StrParam2\" : \"\", \"SubType\" : \"RGBW\", \"SwitchType\" : \"Dimmer\", \"SwitchTypeVal\" : 7, \"Timers\" : \"false\", \"Type\" : \"Lighting Limitless/Applamp\", \"TypeImg\" : \"dimmer\", \"Unit\" : 1, \"Used\" : 1, \"UsedByCamera\" : false, \"XOffset\" : \"0\", \"YOffset\" : \"0\", \"idx\" : \"35\" }, { \"AddjMulti\" : 1.0, \"AddjMulti2\" : 1.0, \"AddjValue\" : 0.0, \"AddjValue2\" : 0.0, \"BatteryLevel\" : 255, \"CustomImage\" : 0, \"Data\" : \"Off\", \"Description\" : \"\", \"Favorite\" : 1, \"HardwareID\" : 3, \"HardwareName\" : \"MyHue\", \"HardwareType\" : \"Philips Hue Bridge\", \"HardwareTypeVal\" : 38, \"HaveDimmer\" : true, \"HaveGroupCmd\" : false, \"HaveTimeout\" : false, \"ID\" : \"00000009\", \"Image\" : \"Light\", \"IsSubDevice\" : false, \"LastUpdate\" : \"2017-01-24 09:18:22\", \"Level\" : 94, \"LevelInt\" : 94, \"MaxDimLevel\" : 100, \"Name\" : \"Test Light on CM15 (PL) N1\", \"Notifications\" : \"false\", \"PlanID\" : \"0\", \"PlanIDs\" : [ 0 ], \"Protected\" : false, \"ShowNotifications\" : true, \"SignalLevel\" : \"-\", \"Status\" : \"Off\", \"StrParam1\" : \"\", \"StrParam2\" : \"\", \"SubType\" : \"RGBW\", \"SwitchType\" : \"Dimmer\", \"SwitchTypeVal\" : 7, \"Timers\" : \"false\", \"Type\" : \"Lighting Limitless/Applamp\", \"TypeImg\" : \"dimmer\", \"Unit\" : 1, \"Used\" : 1, \"UsedByCamera\" : false, \"XOffset\" : \"0\", \"YOffset\" : \"0\", \"idx\" : \"44\" } ], \"status\" : \"OK\", \"title\" : \"Devices\" }";
public final static String ScenesTestData = "{ \"ActTime\" : 1485295431, \"AllowWidgetOrdering\" : true, \"ServerTime\" : \"2017-01-24 16:03:51\", \"Sunrise\" : \"07:11\", \"Sunset\" : \"16:53\", \"result\" : [ { \"Description\" : \"\", \"Favorite\" : 0, \"LastUpdate\" : \"2017-01-23 11:06:31\", \"Name\" : \"Watch TV\", \"OffAction\" : \"\", \"OnAction\" : \"\", \"Protected\" : false, \"Status\" : \"On\", \"Timers\" : \"false\", \"Type\" : \"Scene\", \"UsedByCamera\" : false, \"idx\" : \"1\" }, { \"Description\" : \"\", \"Favorite\" : 0, \"LastUpdate\" : \"2017-01-23 11:25:58\", \"Name\" : \"TestScene\", \"OffAction\" : \"\", \"OnAction\" : \"\", \"Protected\" : false, \"Status\" : \"Off\", \"Timers\" : \"false\", \"Type\" : \"Scene\", \"UsedByCamera\" : false, \"idx\" : \"2\" } ], \"status\" : \"OK\", \"title\" : \"Scenes\" }";
public static void main(String[] args){
DomoticzDeviceConstructor aTestService = new DomoticzDeviceConstructor();
if(aTestService.validateStructure())
System.out.println("Test Successful");
}
public Boolean validateStructure() {
Gson aGson;
try {
aGson = new GsonBuilder()
// .registerTypeAdapter(Service.class, new ServiceDeserializer())
// .registerTypeHierarchyAdapter(Field.class, new FieldDeserializer())
.create();
System.out.println("Decode Domoticz Devices Data");
Devices aDeviceContainer = aGson.fromJson(DevicesTestData, Devices.class);
Iterator<DeviceResult> aList = aDeviceContainer.getResult().iterator();
while(aList.hasNext()) {
DeviceResult theResult = aList.next();
System.out.println(" " + theResult.getName() + " - " + theResult.getDescription() + " - " + theResult.getType());
}
System.out.println("Decode Domoticz Sceness Data");
aDeviceContainer = aGson.fromJson(ScenesTestData, Devices.class);
aList = aDeviceContainer.getResult().iterator();
while(aList.hasNext()) {
DeviceResult theResult = aList.next();
System.out.println(" " + theResult.getName() + " - " + theResult.getDescription() + " - " + theResult.getType());
}
} catch (Exception e) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,14 @@
package com.bwssystems.domoticz.test;
import org.junit.Assert;
import org.junit.Test;
public class DomoticzStructureTestCase {
@Test
public void testValidateStructure() {
DomoticzDeviceConstructor aTestService = new DomoticzDeviceConstructor();
Assert.assertEquals(aTestService.validateStructure(), true);
}
}