Compare commits

...

128 Commits

Author SHA1 Message Date
Admin
e5c4e09543 Update readme for version. 2016-11-02 16:33:57 -05:00
Admin
425ac9fb91 Updated upnp response handling to warn on failure and not stop. Added
configuration of echo url for those overseas. Fixed overwriting device
issue when adding a manual device add or after a restart. Added default
IpV4 handling with override for ha-bridge. Fixed Dim URL http body
handling on PUT and POST. Added documentation for Kodi Volume handling.

Fixes #207
Fixes #202
Fixes #201
Fixes #198
Fixes #194
Fixes #193
2016-11-02 16:32:57 -05:00
BWS Systems
32203b65be Merge pull request #189 from bwssytems/postv3.1fixes
Fixes #129 
Fixes #161 
Fixes #166 
Fixes #168 
Fixes #172 
Fixes #173 
Fixes #174 
Fixes #181
2016-10-18 08:42:01 -05:00
Admin
7e9600d2a7 Update release version number. 2016-10-18 08:35:27 -05:00
BWS Systems
dcc470486f Merge pull request #185 from martinbutt/master
Docs fix
2016-10-14 08:17:26 -05:00
Martin Butt
330adbdd4c Docs fix 2016-10-13 15:21:27 -07:00
Admin
23d43b0136 I got some more info from another source about the discovery responses
of a real hue. It sends three responses not just one. Also I have
updated the S/N UUID and the Hue Bridge ID creation as they are
different.
2016-10-10 13:23:29 -05:00
Admin
004276d3ea Changed handling of upnp rsponses from the upnp listener and the
huemulator udp calls to use the same response port.
2016-10-07 16:29:55 -05:00
Admin
e90e0f69ef Updated headers content type to be just "application/json" instead of
"application/json; charset utf-8".
2016-10-04 13:34:47 -05:00
Admin
7163a12384 Updating upnp responses to be more inline with what the hue hub does. To
do so, added the mac address as the id in the bridge id an, serial
number and uuid
2016-10-03 15:07:09 -05:00
Admin
bb65650e53 Added ip check if upnp config address is not available on the current
host. Added checks for content type and body for put and post requests.
2016-09-29 16:21:15 -05:00
Admin
7f7e96465b Switched default web port to be 80. This should resolve some issues with
first time configs and external apps. Changed upnp listener response to
previous style. Set default for link button presses status to true.
2016-09-29 09:25:47 -05:00
Admin
8ff7bc0120 Updated upnp response for M-SEARCH again. Updated devices to have
numbering more in line with how the hue bridge is done. Added special
generation of hue device unique id as the philips api spec shows.  Added
a renumber function to convert id's.
2016-09-28 16:18:53 -05:00
Admin
4da5f65d89 Updatting upnp responses 2016-09-26 17:03:30 -05:00
Admin
faa67827c6 Updated configuration items for hue responses. ADded call thru for hue
devices configured for state info. added dim content body.
2016-09-23 15:46:39 -05:00
Admin
bbce1f4235 Merge remote-tracking branch 'origin/master' into postv3.1fixes 2016-09-22 09:27:02 -05:00
BWS Systems
8575deadf1 Update readne.md for a missed version change
Changed line for download of ha-bridge jar
2016-09-22 09:02:28 -05:00
Admin
ad4015927c Add check for host is down exception on upnp send in listener and not
shutdown only a warning.
2016-09-21 16:33:57 -05:00
BWS Systems
f7df6951b0 Update README.md
Updated version to be the latest in the command examples
2016-09-20 08:29:01 -05:00
Admin
c20d046b30 Updated upnp response to be closer to the hue bridge v2 spec. Updated
handling for is on when bri is 0. Updated exec to check for empty call.
2016-09-15 15:41:59 -05:00
BWS Systems
20dedec8ab Merge pull request #158 from bwssytems/v3-updates
V3.1.0 updates
2016-09-01 11:11:40 -05:00
Admin
580c037b1e updated for unused imports 2016-09-01 11:07:34 -05:00
Admin
77cb064d60 Finished testing fixes and enhancements. 2016-09-01 11:03:11 -05:00
Admin
0e4319ea1d testing for changes: 3.0.0 not storing extended values (2.0.7 does),
update state not always working, Pi3 systemd not starting,
habridge.config created readable by everyone
2016-08-31 16:31:51 -05:00
BWS Systems
c61e623e23 Merge pull request #150 from tylerstraub/patch-1
Update README.md with caveats for using rc.local for starting on raspbian.
2016-08-02 15:45:54 -05:00
Tyler Straub
669483f686 Update README.md
Proposal to add some additional text under the Linux Automation section related to Issue #148. 

see: https://github.com/bwssytems/ha-bridge/issues/148
2016-08-02 12:20:02 -07:00
BWS Systems
5a59747bc9 Merge pull request #141 from bwssytems/hal-integration
Hal integration and other enhancement and fixes
2016-07-19 11:48:49 -05:00
Admin
53ec096c95 Updated version based on enhancement and bug implementations
Fixes #120
Fixes #122
Fixes #123
Fixes #135
Fixes #137
Fixes #138
Fixes #139
2016-07-19 11:42:07 -05:00
Admin
8ccb768391 Updated group 0 handling to be per hue api requirements. 2016-07-15 16:27:53 -05:00
Admin
9d84e2a180 fixed dim addition in bulk add
modified savef button to be active always in bridge settings
2016-07-12 16:26:53 -05:00
Admin
6a5fae583f Updating bridge control to activate save button on list updates. 2016-07-11 16:32:54 -05:00
Admin
8d15d0a0fb Tested api changes 2016-06-09 16:44:11 -05:00
Admin
d4b8b70a83 Added Hue Error Handling objects. Adding whitelsit control. 2016-06-08 16:41:46 -05:00
Admin
a276f97776 Testing api 1.10.0 2016-06-07 16:39:59 -05:00
Admin
9438b25538 Testing upnp response structures. 2016-06-06 16:42:46 -05:00
Admin
6c15ca2c3b Updated the Hue API v 1.11 and tested.
Testing the upnp changes, ongoing.
2016-06-03 16:25:00 -05:00
Admin
39782fa339 Updateing for API V 1.11 and Bridge V2 HUE handling. 2016-06-02 16:51:02 -05:00
Admin
d7e29e2ee5 Finished implementation of HAL calls to the API. Testing next. 2016-05-26 16:06:48 -05:00
Admin
6b4693eaaf Added Home control, Thermostat and other enhancements. 2016-05-25 16:09:29 -05:00
Admin
7f816b03d5 Fixed sizing of scrollable table to be dynamic. Added Appliances,
Theater and Custom devices to HAL interface. Added Select ALL feature
for bulk add screens.
2016-05-24 16:42:05 -05:00
Admin
ed3db4427b Updated view consistency. Fixed HAL bulk add 2016-05-23 16:45:47 -05:00
bwssystems
80ca8c3ca3 updated html pages to reference hal tab 2016-05-21 16:59:03 -05:00
bwssystems
e43473734e Finished draft HAL integration for lights type only 2016-05-21 16:49:12 -05:00
Admin
8a468b8352 First cut at HAL itegration 2016-05-20 16:13:48 -05:00
bwssystems
51ce10cfc7 Fixed build device button in Harmony Device helper tab.
Fixes #116
2016-05-15 12:25:28 -05:00
Admin
b5f7144c9c Update readme with a script construction. 2016-05-11 14:36:58 -05:00
Admin
86a931d383 added exec:// type for loops. updated button maptypeid naming.
Fixes #114
Fixes #115
2016-05-11 12:23:43 -05:00
Admin
3e890721c5 Fixed handling of dim and brigthen requests as they were not setting the
state appropriately for other systems. Fixed handling of data return
from an http call if the entity was empty.

Fixes #102
Fixes #107
2016-05-10 12:29:37 -05:00
Admin
62d1c64a3d Updated entitiy handling in HueMulator http call. 2016-05-06 08:30:37 -05:00
Admin
c025b186cd Updated a comment for the seb server port 2016-05-05 14:13:06 -05:00
Admin
e999c3a969 Update spelling in readme 2016-05-05 10:45:03 -05:00
Admin
351403e611 Fixed success parsing on http to return success. 2016-05-04 14:38:37 -05:00
Admin
c773477a43 Added delete dialog for confirmation. Fixed bug with text in
intensity.byte for vera device tab. Fixed issue with parsing replies for
http requests. Fixed Hue device bulk add. recommit


Fixes #100
Fixes #102
Fixes #104
Fixes #105
2016-05-04 11:45:51 -05:00
BWS Systems
5d1f0ce3b6 update versions in unit file 2016-05-02 15:41:01 -05:00
Admin
7e0fd6c21b Updated the register with hue function to not send a user name when
linking as this has been deprecated by Philips.

Fixes #99
2016-04-29 16:12:52 -05:00
BWS Systems
3bf52f5da0 Updated Readme
Added comment for Hue proxy and using the link button.
2016-04-29 12:54:08 -05:00
BWS Systems
bd856d8f9e Fixed spelling 2016-04-29 12:44:30 -05:00
Admin
73b2be752e Issue with http/https handling trying to token out line. Updated Readme
Fixes #96
Fixes #98
2016-04-29 12:27:15 -05:00
Admin
dda7a7a34a Merge remote-tracking branch 'origin/add-color-args' 2016-04-29 11:26:06 -05:00
Admin
f238e05533 Fixed bug where device state object was dropped from hue api device
response object. This caused devices to appear offline and invalid
response interpretation by the echo.

Fixes #93
2016-04-28 12:12:49 -05:00
Admin
aaaebd0c05 Merged add-color-args branch to master.
Fixes #72
Fixes #81
Fixes #82
Fixes #85
Fixes #87
Fixes #88
Fixes #89
Fixes #90
2016-04-27 14:42:52 -05:00
Admin
9a1924422e Updated Readme 2016-04-27 13:07:49 -05:00
Admin
e446c618ce MAde the generate buttons consistent in the UI. Updated the Readme. 2016-04-27 12:40:54 -05:00
Admin
60d35acff9 update readme for formatting 2016-04-27 09:11:11 -05:00
Admin
c9adab53a9 Updated selections in device add or editor and updated loggin for upnp
listener and updated the readme
2016-04-27 09:08:41 -05:00
Admin
72b6b2027b Finished updating UPNP handling with ports and errors and testing for
new features.

Updateding README
2016-04-26 16:49:26 -05:00
Admin
60239bad82 Finished testing and refactoring for udp/tcp and http 2016-04-26 10:12:36 -05:00
Admin
aecd589308 Finished implementation of headers and tested. Added calls for TCP
request with UDP and also added the value replacement for dimming, this
needs to be tested.
2016-04-25 16:48:59 -05:00
Admin
ee45cee8e3 Fixed error handling in for loops 2016-04-22 14:00:47 -05:00
Admin
21e5dfb338 added multiple command execution. Added commandline Exec. NEeds testing 2016-04-22 13:59:12 -05:00
Admin
b73a4cd666 Finished testing and updating HUE passthru handling 2016-04-22 10:58:54 -05:00
Admin
05418fdda1 Updated passthru after more testing 2016-04-21 15:41:06 -05:00
Admin
a717fd7c68 Add new classes. 2016-04-20 16:44:39 -05:00
Admin
8408d7350e More testing on HUE passthru. 2016-04-20 16:44:02 -05:00
Admin
3ba8f56db2 Completed Hue passthru 2016-04-18 16:41:15 -05:00
Admin
7a0946e3b7 Continue adding HUE pass thru. 2016-04-15 16:30:34 -05:00
Admin
50c9369d71 Added config override for server port. Updated Readme. Continued
enhancements to pass color to a real hue.
2016-04-14 17:12:06 -05:00
Admin
6c2a34f507 Updated code for collor passing. 2016-04-04 16:36:05 -05:00
Admin
ee2c105040 Started implementation 2016-04-01 16:33:55 -05:00
Admin
3ac83912f3 Fixed success return validation on calls and implemented validation
catching logic for URL issues.

Fixes #73
Fixes #75
2016-03-29 16:24:12 -05:00
Admin
e62fcf7765 Adding URL formatting for various characters and updating test for
success return.
2016-03-28 16:36:21 -05:00
Admin
9c3d95f177 Finished implementing and testing state change and bug fixes from
testing

Fixes #44
Fixes #60
Fixes #61
Fixes #63
Fixes #66
Fixes #69
2016-03-21 16:37:13 -05:00
Admin
926a7f50dc Finished adding dim versus on, utf-8 and track state and return on api
calls.
2016-03-18 16:38:18 -05:00
Admin
ad820a68c9 Updated C/F setting. Started to implement UTF-8 2016-03-16 17:13:03 -05:00
Admin
73f0f766f7 Added more logging and updated nest controller version to fix issues
with range temp setting in nest.
2016-03-09 16:45:21 -06:00
Admin
113ce0ca59 Updated logging with new nest module. 2016-03-08 16:40:32 -06:00
bwssystems
a5860417c1 Update null numberoflogmessages 2016-02-27 12:56:42 -06:00
bwssystems
48af3d84a2 Finished configurable logging view and control.
Fixes #51
Fixes #52
2016-02-27 12:48:19 -06:00
Admin
8ce0483e54 Adding log level control 2016-02-26 16:38:41 -06:00
Admin
1a2024d92b Tables in all views updated to scrollable/sortable. Refactored
controllers for views. debugging logging message return for messages
with invalid text for json parsing.
2016-02-25 16:42:35 -06:00
Admin
8198919a27 playing with scrollable table for log 2016-02-24 16:46:52 -06:00
Admin
614734a2aa Adding logs window. 2016-02-23 16:52:00 -06:00
Admin
58fccb1fa7 fixed about issue. Looking into log view and changed log impl to logback
within slf4j.
2016-02-22 16:38:57 -06:00
Admin
77d3084b01 Finished slider for testint items with percentage items. Many refactors
and fixes.

Fixes #43
Fixes #46
Fixes #47
Fixes #48
2016-02-19 16:34:04 -06:00
Admin
922bb54143 add js and css dialog files 2016-02-18 16:40:25 -06:00
Admin
8586bbd965 Found slider and popup dialog. 2016-02-18 16:39:41 -06:00
Admin
d64a028f30 removed slider.js and .css changing to a nicer slider 2016-02-18 08:49:49 -06:00
Admin
fff30d17d6 Finished updating settings editor and reinit control. Starting number
slider for input.
2016-02-17 16:41:34 -06:00
Admin
13fa5dea73 update ui components. 2016-02-16 16:53:01 -06:00
Admin
1d6a4c1432 Updated UI for configuration and added shutdown of harmony and nest
connections.
2016-02-16 16:52:12 -06:00
bwssystems
9cb275230e Updating editing for Settings UI. 2016-02-13 17:02:51 -06:00
Admin
c97ab2cd38 Fixed bulk add race conditions. added save capability into UI. UI
settings layout update.
2016-02-12 15:51:38 -06:00
Admin
7b45ca9438 added class backup handler to git 2016-02-11 16:52:08 -06:00
Admin
e6da9950d6 Added config settings page and refactored backup handling and bridge
settings and cotntrol. Next is to add the editing to the config screen.
2016-02-11 16:51:11 -06:00
Admin
20328b15d8 Added config file handling for the next step in config screens. 2016-02-10 16:47:58 -06:00
Admin
2ff73e5672 Added reinitialize and stop commands in prep for building the config
file handling.
2016-02-09 16:41:05 -06:00
Admin
cca9a6be78 Finished button sleep time addition. Updated documentation for
button.sleep and corrected the device DB line and updated ip list
directives for vera.address and harmony.address.

Fixes #39
Fixes #40
Fixes #42
2016-02-08 16:21:34 -06:00
Admin
5d5b68209a Updated test notifcation to a toast, added config button sleep time. 2016-02-05 16:10:47 -06:00
Admin
d2e906caa3 Fixed variable mis name in apps.js for olddevicename 2016-02-03 14:32:33 -06:00
Admin
a1708f2a88 Vera device and scene creation testing and added Copy capability to add
an already configured device with a different name.

Fixes #32
Fixes #38
2016-02-02 12:04:48 -06:00
Admin
49c3b85894 Fixed the vera device and scene build. 2016-02-01 16:34:04 -06:00
Admin
f9f5a3a878 Updated code in the html and javascript to select the coorect name for
the button press command. Fixed the calculation of celsius conversion
for setting nest temperatures.

Fixes #33
Fixes #34
2016-01-29 13:49:34 -06:00
Admin
2565183ee9 Finished implementation for multiple vera support. Tweaks to the UI.
Fixes #19
Fixes #21
Fixes #23
Fixes #26
Fixes #28
Fixes #30
Fixes #31
2016-01-28 16:39:20 -06:00
Admin
a6bb1ae3aa Fixed the harmony button to be a label and not the name. Implementation
of multi batch add completed.
2016-01-27 16:54:23 -06:00
Admin
4bc91be88b Added backup and restore for device db functionality. Testing multi
button press for harmony.
2016-01-26 16:47:16 -06:00
Admin
315fd31270 implemented multi button build for devices. more testing needed. fixed
tab display on first view.
2016-01-25 16:35:22 -06:00
Admin
09787fe08d Fixed undefined name error in nest setup and celsius conversion for the
nest thermostat.
2016-01-22 12:20:24 -06:00
Admin
37d346f558 Finished Nest implementation and default ip selection to not be
127.0.0.1

Fixes #12
Fixes #20
2016-01-21 16:44:24 -06:00
Admin
ac59398aa0 Testing nest functionality 2016-01-20 16:46:21 -06:00
Admin
87073435fc Updated code for IP address selection on startup fi none given. 2016-01-13 14:54:35 -06:00
Admin
8687f3482a Continuation of nest implementation 2016-01-12 16:34:05 -06:00
Admin
32a5f26ddd Added new object 2016-01-11 16:45:49 -06:00
Admin
c28f07d628 Continuation of nest implementation. 2016-01-11 16:45:02 -06:00
Admin
d3cc961dfb Initial updates for nest control 2016-01-08 16:14:42 -06:00
Admin
1b3d826f28 Fixed Readme format errors 2015-12-29 10:18:51 -06:00
Admin
c8f4d89a45 Update Readme for clarity on upnp.config.address usage. Also, updated
the 'ask alexa' section with more details.
2015-12-29 10:14:26 -06:00
Admin
2b335d6b9b Updated upnp listener logging. Fixed issue with HUE emulation for
configuration. HUE mobile app now connects.
2015-12-17 16:13:46 -06:00
Admin
b27bb5eef8 Updating hue emulation repsonses using hue android app. fixed issues in
returning the hue config and for requesting the mac address.
2015-12-16 16:41:08 -06:00
92 changed files with 10115 additions and 2277 deletions

468
README.md

File diff suppressed because one or more lines are too long

160
kodivolume.md Normal file
View File

@@ -0,0 +1,160 @@
# 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 doesn<73>t include **<EFBFBD>volume<EFBFBD>** as it will cause conflicts with the Echo<68>s built in volume controls. A device name of **<EFBFBD>cody sound<6E>** 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 don<6F>t 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 don<6F>t 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}
```
#### Encoding the JSON part
You should now be able to control the volume of Kodi using the structured URL you built above in a browser. If you can<61>t get it to work in a browser then you won<6F>t be able to get it to work in HA Bridge.
In order for HA Bridge to send the JSON request you need to encode the URL. This means we take this:
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
And turn it into this:
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request=%7B%22jsonrpc%22%3A%222.0%22%2C%22method%22%3A%22Application.SetVolume%22%2C%22params%22%3A%7B%22volume%22%3A100%7D%2C%22id%22%3A1%7D
```
Note that we are only encoding the JSON, the URL part we leave as is.
Using the online tool [www.url-encode-decode.com](http://www.url-encode-decode.com/), copy the JSON part (shown in bold below) and paste it into the left hand field.
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request=**{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}**
Click the **Encode url** button.
Copy the output from the right hand box and paste it on a new line in your text file.
```
%7B%22jsonrpc%22%3A%222.0%22%2C%22method%22%3A%22Application.SetVolume%22%2C%22params%22%3A%7B%22volume%22%3A100%7D%2C%22id%22%3A1%7D
```
#### Join the URL and JSON
Finally you want to insert the URL part before the request (shown in bold below)
**http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request=**%7B%22jsonrpc%22%3A%222.0%22%2C%22method%22%3A%22Application.SetVolume%22%2C%22params%22%3A%7B%22volume%22%3A100%7D%2C%22id%22%3A1%7D
### HA Bridge
Paste the final URL in **On URL** field. Just do the one for now, add the device and in the Bridge Control tab click Save.
Test the button in the Bridge Devices tab and hopefully it should turn the volume up in Kodi.
Go ahead and repeat the steps for the **Off URL**. All the same steps but change 100% to 0%. If you want to use the previous URL you can, just find the 100% in the encoded URL.
`<EFBFBD>%22%3A%7B%22volume%22%3A`**100**`%7D%2C%22id%22%3A1%7D`
and change it to 0
`<EFBFBD>%22%3A%7B%22volume%22%3A`**0**`%7D%2C%22id%22%3A1%7D`
#### Dim JSON
The Dim URL uses the `${intensity.percent}` to take the given number from your voice command and pass it to Kodi.
Here is the JSON to <20>Dim<69> the volume
``` json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":"${intensity.percent}""},"id":1}
```
You don<6F>t need to encode the `${intensity.percent}` part. This means you can simply replace the number value (100/0) with `${intensity.percent}` as shown below.
`<EFBFBD>%22%3A%7B%22volume%22%3A`**${intensity.percent}**`%7D%2C%22id%22%3A1%7D`
### Controlling the Device
You can use the commands as listed in the [README](https://github.com/bwssytems/ha-bridge#ask-alexa)
<EFBFBD>Set Cody Sound to 50 percent<6E>
<EFBFBD>Cody Sound to 70 percent<6E>
Remembering that <20>Turn on Cody Sound<6E> will set the volume to 100%, and <20>Turn off Cody Sound<6E> will mute.

64
pom.xml
View File

@@ -5,11 +5,11 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>1.2.2</version>
<version>3.2.1</version>
<packaging>jar</packaging>
<name>HA Bridge</name>
<description>Emulates a Philips Hue bridge to allow the Amazon Echo to hook up to other HA systems, i.e. Vera or Harmony Hub, using lightweight frameworks</description>
<description>Emulates a Philips Hue bridge to allow the Amazon Echo to hook up to other HA systems, i.e. Vera or Harmony Hub or Nest, using lightweight frameworks</description>
<properties>
<java.version>1.8</java.version>
@@ -29,42 +29,68 @@
<groupId>com.github.bwssytems</groupId>
<artifactId>harmony-java-client</artifactId>
<version>1.0.8</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.bwssytems</groupId>
<artifactId>nest-controller</artifactId>
<version>1.0.9</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.3</version>
<exclusions>
<exclusion>
<artifactId>slf4j-simple</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.6</version>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.5</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.cedarsoftware</groupId>
<artifactId>json-io</artifactId>
<version>4.1.6</version>
</dependency>
<dependency>
<groupId>net.java.dev.eval</groupId>
<artifactId>eval</artifactId>
@@ -128,7 +154,7 @@
<artifact>*:*</artifact>
</filter>
<filter>
<artifact>org.slf4j:*</artifact>
<artifact>org.slf4j:slf4j-api</artifact>
<includes>
<include>**</include>
</includes>

View File

@@ -0,0 +1,25 @@
package com.bwssystems.HABridge;
public class BridgeControlDescriptor {
private boolean reinit;
private boolean stop;
public BridgeControlDescriptor() {
super();
this.reinit = false;
this.stop = false;
}
public boolean isReinit() {
return reinit;
}
public void setReinit(boolean reinit) {
this.reinit = reinit;
}
public boolean isStop() {
return stop;
}
public void setStop(boolean stop) {
this.stop = stop;
}
}

View File

@@ -1,106 +1,283 @@
package com.bwssystems.HABridge;
import java.util.List;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
public class BridgeSettings {
private String upnpconfigaddress;
private String serverport;
private String upnpresponseport;
private String upnpdevicedb;
private String veraaddress;
private IpList harmonyaddress;
private String harmonyuser;
private String harmonypwd;
private Integer upnpresponsedevices;
private boolean upnpstrict;
private boolean traceupnp;
private boolean devmode;
import org.apache.http.conn.util.InetAddressUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.util.BackupHandler;
import com.bwssystems.util.JsonTransformer;
import com.google.gson.Gson;
public class BridgeSettings extends BackupHandler {
private static final Logger log = LoggerFactory.getLogger(BridgeSettings.class);
private BridgeSettingsDescriptor theBridgeSettings;
private BridgeControlDescriptor bridgeControl;
public String getUpnpConfigAddress() {
return upnpconfigaddress;
public BridgeSettings() {
super();
bridgeControl = new BridgeControlDescriptor();
theBridgeSettings = new BridgeSettingsDescriptor();
String ipV6Stack = System.getProperty("ipV6Stack");
if(ipV6Stack == null || !ipV6Stack.equalsIgnoreCase("true")) {
System.setProperty("java.net.preferIPv4Stack" , "true");
}
}
public void setUpnpConfigAddress(String upnpConfigAddress) {
this.upnpconfigaddress = upnpConfigAddress;
public BridgeControlDescriptor getBridgeControl() {
return bridgeControl;
}
public String getServerPort() {
return serverport;
public BridgeSettingsDescriptor getBridgeSettingsDescriptor() {
return theBridgeSettings;
}
public void setServerPort(String serverPort) {
this.serverport = serverPort;
public void buildSettings() {
String addressString = null;
String theVeraAddress = null;
String theHarmonyAddress = null;
String configFileProperty = System.getProperty("config.file");
if(configFileProperty == null) {
Path filePath = Paths.get(Configuration.CONFIG_FILE);
if(Files.exists(filePath) && Files.isReadable(filePath))
configFileProperty = Configuration.CONFIG_FILE;
}
String serverPortOverride = System.getProperty("server.port");
if(configFileProperty != null)
{
log.info("reading from config file: " + configFileProperty);
theBridgeSettings.setConfigfile(configFileProperty);
_loadConfig();
}
else
{
log.info("reading from system properties");
theBridgeSettings.setNumberoflogmessages(Configuration.NUMBER_OF_LOG_MESSAGES);
theBridgeSettings.setFarenheit(true);
theBridgeSettings.setConfigfile(Configuration.CONFIG_FILE);
theBridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DEFAULT_WEB_PORT));
theBridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address"));
theBridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db"));
theBridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT));
theVeraAddress = System.getProperty("vera.address");
IpList theVeraList = null;
if(theVeraAddress != null) {
try {
theVeraList = new Gson().fromJson(theVeraAddress, IpList.class);
} catch (Exception e) {
try {
theVeraList = new Gson().fromJson("{devices:[{name:default,ip:" + theVeraAddress + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse vera.address, not set with message: " + e.getMessage(), e);
theVeraList = null;
}
}
}
theBridgeSettings.setVeraAddress(theVeraList);
theHarmonyAddress = System.getProperty("harmony.address");
IpList theHarmonyList = null;
if(theHarmonyAddress != null) {
try {
theHarmonyList = new Gson().fromJson(theHarmonyAddress, IpList.class);
} catch (Exception e) {
try {
theHarmonyList = new Gson().fromJson("{devices:[{name:default,ip:" + theHarmonyAddress + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse harmony.address, not set with message: " + e.getMessage(), e);
theHarmonyList = null;
}
}
}
theBridgeSettings.setHarmonyAddress(theHarmonyList);
theBridgeSettings.setHarmonyUser(System.getProperty("harmony.user"));
theBridgeSettings.setHarmonyPwd(System.getProperty("harmony.pwd"));
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)));
theBridgeSettings.setNestuser(System.getProperty("nest.user"));
theBridgeSettings.setNestpwd(System.getProperty("nest.pwd"));
}
if(theBridgeSettings.getUpnpConfigAddress() == null || theBridgeSettings.getUpnpConfigAddress().equals("")) {
addressString = checkIpAddress(null, true);
if(addressString != null) {
theBridgeSettings.setUpnpConfigAddress(addressString);
log.info("Adding " + addressString + " as our default upnp config address.");
}
else
log.error("Cannot get ip address of this host.");
}
else {
addressString = checkIpAddress(theBridgeSettings.getUpnpConfigAddress(), false);
if(addressString == null)
log.warn("The upnp config address, " + theBridgeSettings.getUpnpConfigAddress() + ", does not match any known IP's on this host.");
}
if(theBridgeSettings.getUpnpResponsePort() == null)
theBridgeSettings.setUpnpResponsePort(Configuration.UPNP_RESPONSE_PORT);
if(theBridgeSettings.getServerPort() == null)
theBridgeSettings.setServerPort(Configuration.DEFAULT_WEB_PORT);
if(theBridgeSettings.getUpnpDeviceDb() == null)
theBridgeSettings.setUpnpDeviceDb(Configuration.DEVICE_DB_DIRECTORY);
if(theBridgeSettings.getNumberoflogmessages() == null)
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)
theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP));
theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera());
theBridgeSettings.setHarmonyconfigured(theBridgeSettings.isValidHarmony());
theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest());
theBridgeSettings.setHueconfigured(theBridgeSettings.isValidHue());
theBridgeSettings.setHalconfigured(theBridgeSettings.isValidHal());
if(serverPortOverride != null)
theBridgeSettings.setServerPort(serverPortOverride);
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");
}
public String getUpnpResponsePort() {
return upnpresponseport;
public void loadConfig() {
if(theBridgeSettings.getConfigfile() != null)
_loadConfig();
}
public void setUpnpResponsePort(String upnpResponsePort) {
this.upnpresponseport = upnpResponsePort;
private void _loadConfig() {
Path configPath = Paths.get(theBridgeSettings.getConfigfile());
_loadConfig(configPath);
}
private void _loadConfig(Path aPath) {
String jsonContent = configReader(aPath);
if(jsonContent == null)
return;
try {
theBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class);
} catch (Exception e) {
log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed.");
theBridgeSettings = new BridgeSettingsDescriptor();
theBridgeSettings.setConfigfile(aPath.toString());
}
}
public void save(BridgeSettingsDescriptor newBridgeSettings) {
log.debug("Save HA Bridge settings.");
Path configPath = Paths.get(theBridgeSettings.getConfigfile());
JsonTransformer aRenderer = new JsonTransformer();
String jsonValue = aRenderer.render(newBridgeSettings);
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);
return;
}
if(Files.notExists(filePath.getParent())) {
try {
Files.createDirectories(filePath.getParent());
} catch (IOException e) {
log.error("Error creating the directory: " + filePath + " message: " + e.getMessage(), e);
}
}
try {
Path target = null;
if(Files.exists(filePath)) {
target = FileSystems.getDefault().getPath(filePath.getParent().toString(), "habridge.config.old");
Files.move(filePath, target);
}
Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE);
// set attributes to be for user only
// using PosixFilePermission to set file permissions
Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>();
// add owners permission
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
try {
Files.setPosixFilePermissions(filePath, perms);
} catch(UnsupportedOperationException e) {
log.info("Cannot set permissions for config file on this system as it is not supported. Continuing");
}
if(target != null)
Files.delete(target);
} catch (IOException e) {
log.error("Error writing the file: " + filePath + " message: " + e.getMessage(), e);
}
}
public String getUpnpDeviceDb() {
return upnpdevicedb;
private String configReader(Path filePath) {
String content = null;
if(Files.notExists(filePath) || !Files.isReadable(filePath)){
log.warn("Error reading the file: " + filePath + " - Does not exist or is not readable. continuing...");
return null;
}
try {
content = new String(Files.readAllBytes(filePath));
} catch (IOException e) {
log.error("Error reading the file: " + filePath + " message: " + e.getMessage(), e);
}
return content;
}
public void setUpnpDeviceDb(String upnpDeviceDb) {
this.upnpdevicedb = upnpDeviceDb;
}
public String getVeraAddress() {
return veraaddress;
}
public void setVeraAddress(String veraAddress) {
this.veraaddress = veraAddress;
}
public IpList getHarmonyAddress() {
return harmonyaddress;
}
public void setHarmonyAddress(IpList harmonyaddress) {
this.harmonyaddress = harmonyaddress;
}
public String getHarmonyUser() {
return harmonyuser;
}
public void setHarmonyUser(String harmonyuser) {
this.harmonyuser = harmonyuser;
}
public String getHarmonyPwd() {
return harmonypwd;
}
public void setHarmonyPwd(String harmonypwd) {
this.harmonypwd = harmonypwd;
}
public Integer getUpnpResponseDevices() {
return upnpresponsedevices;
}
public void setUpnpResponseDevices(Integer upnpresponsedevices) {
this.upnpresponsedevices = upnpresponsedevices;
}
public boolean isUpnpStrict() {
return upnpstrict;
}
public void setUpnpStrict(boolean upnpStrict) {
this.upnpstrict = upnpStrict;
}
public boolean isTraceupnp() {
return traceupnp;
}
public void setTraceupnp(boolean traceupnp) {
this.traceupnp = traceupnp;
}
public boolean isDevMode() {
return devmode;
}
public void setDevMode(boolean devmode) {
this.devmode = devmode;
}
public Boolean isValidVera() {
if(this.veraaddress.contains(Configuration.DEFAULT_VERA_ADDRESS))
return false;
return true;
}
public Boolean isValidHarmony() {
List<NamedIP> devicesList = this.harmonyaddress.getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_HARMONY_ADDRESS))
return false;
if(this.harmonypwd == null || this.harmonypwd == "")
return false;
if(this.harmonyuser == null || this.harmonyuser == "")
return false;
return true;
private String checkIpAddress(String ipAddress, boolean checkForLocalhost) {
Enumeration<NetworkInterface> ifs = null;
try {
ifs = NetworkInterface.getNetworkInterfaces();
} catch(SocketException e) {
log.error("checkIpAddress cannot get ip address of this host, Exiting with message: " + e.getMessage(), e);
return null;
}
String addressString = null;
InetAddress address = null;
while (ifs.hasMoreElements() && addressString == null) {
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses();
String name = xface.getName();
int IPsPerNic = 0;
while (addrs.hasMoreElements() && IPsPerNic == 0) {
address = addrs.nextElement();
if (InetAddressUtils.isIPv4Address(address.getHostAddress())) {
log.debug(name + " ... has IPV4 addr " + address);
if(checkForLocalhost && (!name.equalsIgnoreCase(Configuration.LOOP_BACK_INTERFACE) || !address.getHostAddress().equalsIgnoreCase(Configuration.LOOP_BACK_ADDRESS))) {
IPsPerNic++;
addressString = address.getHostAddress();
log.debug("checkIpAddress found " + addressString + " from interface " + name);
}
else if(ipAddress != null && ipAddress.equalsIgnoreCase(address.getHostAddress())){
addressString = ipAddress;
IPsPerNic++;
}
}
}
}
return addressString;
}
}

View File

@@ -0,0 +1,264 @@
package com.bwssystems.HABridge;
import java.util.List;
import java.util.Map;
import com.bwssystems.HABridge.api.hue.WhitelistEntry;
public class BridgeSettingsDescriptor {
private String upnpconfigaddress;
private Integer serverport;
private Integer upnpresponseport;
private String upnpdevicedb;
private IpList veraaddress;
private IpList harmonyaddress;
private String harmonyuser;
private String harmonypwd;
private Integer buttonsleep;
private boolean upnpstrict;
private boolean traceupnp;
private String nestuser;
private String nestpwd;
private boolean veraconfigured;
private boolean harmonyconfigured;
private boolean nestconfigured;
private boolean farenheit;
private String configfile;
private Integer numberoflogmessages;
private IpList hueaddress;
private boolean hueconfigured;
private IpList haladdress;
private String haltoken;
private boolean halconfigured;
private Map<String, WhitelistEntry> whitelist;
private boolean settingsChanged;
private String myechourl;
public BridgeSettingsDescriptor() {
super();
this.upnpstrict = true;
this.traceupnp = false;
this.nestconfigured = false;
this.veraconfigured = false;
this.harmonyconfigured = false;
this.hueconfigured = false;
this.halconfigured = false;
this.farenheit = true;
this.whitelist = null;
this.settingsChanged = false;
this.myechourl = "echo.amazon.com/#cards";
}
public String getUpnpConfigAddress() {
return upnpconfigaddress;
}
public void setUpnpConfigAddress(String upnpConfigAddress) {
this.upnpconfigaddress = upnpConfigAddress;
}
public Integer getServerPort() {
return serverport;
}
public void setServerPort(Integer serverPort) {
this.serverport = serverPort;
}
public void setServerPort(String serverPort) {
this.serverport = Integer.valueOf(serverPort);
}
public Integer getUpnpResponsePort() {
return upnpresponseport;
}
public void setUpnpResponsePort(Integer upnpResponsePort) {
this.upnpresponseport = upnpResponsePort;
}
public void setUpnpResponsePort(String upnpResponsePort) {
this.upnpresponseport = Integer.valueOf(upnpResponsePort);
}
public String getUpnpDeviceDb() {
return upnpdevicedb;
}
public void setUpnpDeviceDb(String upnpDeviceDb) {
this.upnpdevicedb = upnpDeviceDb;
}
public IpList getVeraAddress() {
return veraaddress;
}
public void setVeraAddress(IpList veraAddress) {
this.veraaddress = veraAddress;
}
public IpList getHarmonyAddress() {
return harmonyaddress;
}
public void setHarmonyAddress(IpList harmonyaddress) {
this.harmonyaddress = harmonyaddress;
}
public String getHarmonyUser() {
return harmonyuser;
}
public void setHarmonyUser(String harmonyuser) {
this.harmonyuser = harmonyuser;
}
public String getHarmonyPwd() {
return harmonypwd;
}
public void setHarmonyPwd(String harmonypwd) {
this.harmonypwd = harmonypwd;
}
public boolean isUpnpStrict() {
return upnpstrict;
}
public void setUpnpStrict(boolean upnpStrict) {
this.upnpstrict = upnpStrict;
}
public boolean isTraceupnp() {
return traceupnp;
}
public void setTraceupnp(boolean traceupnp) {
this.traceupnp = traceupnp;
}
public String getNestuser() {
return nestuser;
}
public void setNestuser(String nestuser) {
this.nestuser = nestuser;
}
public String getNestpwd() {
return nestpwd;
}
public void setNestpwd(String nestpwd) {
this.nestpwd = nestpwd;
}
public boolean isVeraconfigured() {
return veraconfigured;
}
public void setVeraconfigured(boolean veraconfigured) {
this.veraconfigured = veraconfigured;
}
public boolean isHarmonyconfigured() {
return harmonyconfigured;
}
public void setHarmonyconfigured(boolean harmonyconfigured) {
this.harmonyconfigured = harmonyconfigured;
}
public boolean isNestConfigured() {
return nestconfigured;
}
public void setNestConfigured(boolean isNestConfigured) {
this.nestconfigured = isNestConfigured;
}
public Integer getButtonsleep() {
return buttonsleep;
}
public void setButtonsleep(Integer buttonsleep) {
this.buttonsleep = buttonsleep;
}
public String getConfigfile() {
return configfile;
}
public void setConfigfile(String configfile) {
this.configfile = configfile;
}
public Integer getNumberoflogmessages() {
return numberoflogmessages;
}
public void setNumberoflogmessages(Integer numberoflogmessages) {
this.numberoflogmessages = numberoflogmessages;
}
public boolean isFarenheit() {
return farenheit;
}
public void setFarenheit(boolean farenheit) {
this.farenheit = farenheit;
}
public IpList getHueaddress() {
return hueaddress;
}
public void setHueaddress(IpList hueaddress) {
this.hueaddress = hueaddress;
}
public boolean isHueconfigured() {
return hueconfigured;
}
public void setHueconfigured(boolean hueconfigured) {
this.hueconfigured = hueconfigured;
}
public IpList getHaladdress() {
return haladdress;
}
public void setHaladdress(IpList haladdress) {
this.haladdress = haladdress;
}
public String getHaltoken() {
return haltoken;
}
public void setHaltoken(String haltoken) {
this.haltoken = haltoken;
}
public boolean isHalconfigured() {
return halconfigured;
}
public void setHalconfigured(boolean halconfigured) {
this.halconfigured = halconfigured;
}
public Map<String, WhitelistEntry> getWhitelist() {
return whitelist;
}
public void setWhitelist(Map<String, WhitelistEntry> whitelist) {
this.whitelist = whitelist;
}
public boolean isSettingsChanged() {
return settingsChanged;
}
public void setSettingsChanged(boolean settingsChanged) {
this.settingsChanged = settingsChanged;
}
public String getMyechourl() {
return myechourl;
}
public void setMyechourl(String myechourl) {
this.myechourl = myechourl;
}
public Boolean isValidVera() {
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getVeraAddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
return true;
}
public Boolean isValidHarmony() {
if(this.getHarmonyAddress() == null || this.getHarmonyAddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getHarmonyAddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
if(this.getHarmonyPwd() == null || this.getHarmonyPwd().equals(""))
return false;
if(this.getHarmonyUser() == null || this.getHarmonyUser().equals(""))
return false;
return true;
}
public Boolean isValidNest() {
if(this.getNestpwd() == null || this.getNestpwd().equals(""))
return false;
if(this.getNestuser() == null || this.getNestuser().equals(""))
return false;
return true;
}
public Boolean isValidHue() {
if(this.getHueaddress() == null || this.getHueaddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getHueaddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
return true;
}
public Boolean isValidHal() {
if(this.getHaladdress() == null || this.getHaladdress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getHaladdress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
if(this.getHaltoken() == null || this.getHaltoken().equals(""))
return false;
return true;
}
}

View File

@@ -3,11 +3,13 @@ package com.bwssystems.HABridge;
public class Configuration {
public final static String DEVICE_DB_DIRECTORY = "data/device.db";
public final static String UPNP_RESPONSE_PORT = "50000";
public final static String UPNP_RESPONSE_DEVICES = "30";
public final static String DEFAULT_VERA_ADDRESS = "1.1.1.1";
public final static String DEFAULT_HARMONY_ADDRESS = "1.1.1.1";
public final static String DEFAULT_HARMONY_ADDRESS_LIST = "{devices:[{name:default,ip:1.1.1.1}]}";
public final static String DEFAULT_HARMONY_USER = "";
public final static String DEFAULT_HARMONY_PWD = "";
public final static String DFAULT_WEB_PORT = "8080";
public final static String DEFAULT_ADDRESS = "1.1.1.1";
public final static String LOOP_BACK_ADDRESS = "127.0.0.1";
public final static String LOOP_BACK_INTERFACE = "lo";
public final static String DEFAULT_WEB_PORT = "80";
public final static String DEFAULT_BUTTON_SLEEP = "100";
public static final int UPNP_DISCOVERY_PORT = 1900;
public static final String UPNP_MULTICAST_ADDRESS = "239.255.255.250";
public static final String CONFIG_FILE = "data/habridge.config";
public static final int NUMBER_OF_LOG_MESSAGES = 512;
}

View File

@@ -2,9 +2,6 @@ package com.bwssystems.HABridge;
import static spark.Spark.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,8 +9,11 @@ import com.bwssystems.HABridge.devicemanagmeent.*;
import com.bwssystems.HABridge.hue.HueMulator;
import com.bwssystems.HABridge.upnp.UpnpListener;
import com.bwssystems.HABridge.upnp.UpnpSettingsResource;
import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.hal.HalHome;
import com.bwssystems.harmony.HarmonyHome;
import com.google.gson.Gson;
import com.bwssystems.hue.HueHome;
import com.bwssystems.util.UDPDatagramSender;
public class HABridge {
@@ -36,73 +36,77 @@ public class HABridge {
Logger log = LoggerFactory.getLogger(HABridge.class);
DeviceResource theResources;
HarmonyHome harmonyHome;
NestHome nestHome;
HueHome hueHome;
HalHome halHome;
HueMulator theHueMulator;
UDPDatagramSender udpSender;
UpnpSettingsResource theSettingResponder;
UpnpListener theUpnpListener;
InetAddress address;
String addressString;
SystemControl theSystem;
BridgeSettings bridgeSettings;
Version theVersion;
theVersion = new Version();
log.info("HA Bridge (v" + theVersion.getVersion() + ") starting setup....");
//get ip address for upnp requests
try {
address = InetAddress.getLocalHost();
addressString = address.getHostAddress();
} catch (UnknownHostException e) {
log.error("Cannot get ip address of this host, Exiting with message: " + e.getMessage(), e);
return;
}
log.info("HA Bridge (v" + theVersion.getVersion() + ") starting....");
bridgeSettings = new BridgeSettings();
bridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DFAULT_WEB_PORT));
bridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address", addressString));
bridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db", Configuration.DEVICE_DB_DIRECTORY));
bridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT));
bridgeSettings.setVeraAddress(System.getProperty("vera.address", Configuration.DEFAULT_VERA_ADDRESS));
IpList theHarmonyList;
try {
theHarmonyList = new Gson().fromJson(System.getProperty("harmony.address", Configuration.DEFAULT_HARMONY_ADDRESS_LIST), IpList.class);
} catch (Exception e) {
try {
theHarmonyList = new Gson().fromJson("{devices:[{name:default,ip:" + System.getProperty("harmony.address", Configuration.DEFAULT_HARMONY_ADDRESS) + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse harmony.address, Exiting with message: " + e.getMessage(), e);
return;
}
while(!bridgeSettings.getBridgeControl().isStop()) {
bridgeSettings.buildSettings();
log.info("HA Bridge initializing....");
// sparkjava config directive to set ip address for the web server to listen on
// ipAddress("0.0.0.0"); // not used
// 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");
// setup system control api first
theSystem = new SystemControl(bridgeSettings, theVersion);
theSystem.setupServer();
//setup the harmony connection if available
harmonyHome = new HarmonyHome(bridgeSettings.getBridgeSettingsDescriptor());
//setup the nest connection if available
nestHome = new NestHome(bridgeSettings.getBridgeSettingsDescriptor());
//setup the hue passtrhu configuration if available
hueHome = new HueHome(bridgeSettings.getBridgeSettingsDescriptor());
//setup the hal configuration if available
halHome = new HalHome(bridgeSettings.getBridgeSettingsDescriptor());
// setup the class to handle the resource setup rest api
theResources = new DeviceResource(bridgeSettings.getBridgeSettingsDescriptor(), harmonyHome, nestHome, hueHome, halHome);
// setup the class to handle the upnp response rest api
theSettingResponder = new UpnpSettingsResource(bridgeSettings.getBridgeSettingsDescriptor());
theSettingResponder.setupServer();
// setup the UDP Datagram socket to be used by the HueMulator and the upnpListener
udpSender = UDPDatagramSender.createUDPDatagramSender(bridgeSettings.getBridgeSettingsDescriptor().getUpnpResponsePort());
if(udpSender == null) {
bridgeSettings.getBridgeControl().setStop(true);
}
else {
// setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), harmonyHome, nestHome, hueHome, udpSender);
theHueMulator.setupServer();
// wait for the sparkjava initialization of the rest api classes to be complete
awaitInitialization();
// start the upnp ssdp discovery listener
theUpnpListener = new UpnpListener(bridgeSettings.getBridgeSettingsDescriptor(), bridgeSettings.getBridgeControl(), udpSender);
if(theUpnpListener.startListening())
log.info("HA Bridge (v" + theVersion.getVersion() + ") reinitialization requessted....");
else
bridgeSettings.getBridgeControl().setStop(true);
if(bridgeSettings.getBridgeSettingsDescriptor().isSettingsChanged())
bridgeSettings.save(bridgeSettings.getBridgeSettingsDescriptor());
}
bridgeSettings.getBridgeControl().setReinit(false);
stop();
nestHome.closeTheNest();
nestHome = null;
harmonyHome.shutdownHarmonyHubs();
harmonyHome = null;
udpSender.closeResponseSocket();
}
bridgeSettings.setHarmonyAddress(theHarmonyList);
bridgeSettings.setHarmonyUser(System.getProperty("harmony.user", Configuration.DEFAULT_HARMONY_USER));
bridgeSettings.setHarmonyPwd(System.getProperty("harmony.pwd", Configuration.DEFAULT_HARMONY_PWD));
bridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true")));
bridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
bridgeSettings.setDevMode(Boolean.parseBoolean(System.getProperty("dev.mode", "false")));
bridgeSettings.setUpnpResponseDevices(Integer.parseInt(System.getProperty("upnp.response.devices", Configuration.UPNP_RESPONSE_DEVICES)));
// sparkjava config directive to set ip address for the web server to listen on
// ipAddress("0.0.0.0"); // not used
// sparkjava config directive to set port for the web server to listen on
port(Integer.valueOf(bridgeSettings.getServerPort()));
// sparkjava config directive to set html static file location for Jetty
staticFileLocation("/public");
//setup the harmony connection if available
harmonyHome = new HarmonyHome(bridgeSettings);
// setup the class to handle the resource setup rest api
theResources = new DeviceResource(bridgeSettings, theVersion, harmonyHome);
// setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(bridgeSettings, theResources.getDeviceRepository(), harmonyHome);
theHueMulator.setupServer();
// setup the class to handle the upnp response rest api
theSettingResponder = new UpnpSettingsResource(bridgeSettings);
theSettingResponder.setupServer();
// wait for the sparkjava initialization of the rest api classes to be complete
awaitInitialization();
// start the upnp ssdp discovery listener
theUpnpListener = new UpnpListener(bridgeSettings);
theUpnpListener.startListening();
log.info("HA Bridge (v" + theVersion.getVersion() + ") exiting....");
System.exit(0);
}
}

View File

@@ -0,0 +1,283 @@
package com.bwssystems.HABridge;
import static spark.Spark.get;
import static spark.Spark.options;
import static spark.Spark.post;
import static spark.Spark.put;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.dao.BackupFilename;
import com.bwssystems.logservices.LoggerInfo;
import com.bwssystems.logservices.LoggingForm;
import com.bwssystems.logservices.LoggingManager;
import com.bwssystems.util.TextStringFormatter;
import com.bwssystems.util.JsonTransformer;
import com.google.gson.Gson;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.read.CyclicBufferAppender;
public class SystemControl {
private static final Logger log = LoggerFactory.getLogger(SystemControl.class);
public static final String CYCLIC_BUFFER_APPENDER_NAME = "CYCLIC";
private LoggerContext lc;
private static final String SYSTEM_CONTEXT = "/system";
private BridgeSettings bridgeSettings;
private Version version;
private CyclicBufferAppender<ILoggingEvent> cyclicBufferAppender;
private DateFormat dateFormat;
private LoggingManager theLogServiceMgr;
public SystemControl(BridgeSettings theBridgeSettings, Version theVersion) {
this.bridgeSettings = theBridgeSettings;
this.version = theVersion;
this.lc = (LoggerContext) LoggerFactory.getILoggerFactory();
this.dateFormat = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss.SSS");
reacquireCBA();
theLogServiceMgr = new LoggingManager();
theLogServiceMgr.init();
}
// This function sets up the sparkjava rest calls for the hue api
public void setupServer() {
log.info("System control service started....");
// http://ip_address:port/system/habridge/version gets the version of this bridge instance
get (SYSTEM_CONTEXT + "/habridge/version", "application/json", (request, response) -> {
log.debug("Get HA Bridge version: v" + version.getVersion());
response.status(HttpStatus.SC_OK);
return "{\"version\":\"" + version.getVersion() + "\"}";
});
// http://ip_address:port/system/logmsgs gets the log messages for the bridge
get (SYSTEM_CONTEXT + "/logmsgs", "application/json", (request, response) -> {
log.debug("Get logmsgs.");
response.status(HttpStatus.SC_OK);
String logMsgs;
int count = -1;
if(cyclicBufferAppender == null)
reacquireCBA();
if (cyclicBufferAppender != null) {
count = cyclicBufferAppender.getLength();
}
logMsgs = "[";
if (count == -1) {
logMsgs = logMsgs + "{\"message\":\"Failed to locate CyclicBuffer\"}";
} else if (count == 0) {
logMsgs = logMsgs + "{\"message\":\"No logging events to display\"}";
} else {
LoggingEvent le;
for (int i = 0; i < count; i++) {
le = (LoggingEvent) cyclicBufferAppender.get(i);
logMsgs = logMsgs + ( i > 0?",{":"{") + "\"time\":\"" + dateFormat.format(le.getTimeStamp()) + "\",\"level\":\"" + le.getLevel().levelStr + "\",\"component\":\"" + le.getLoggerName() + "\",\"message\":\"" + TextStringFormatter.forJSON(le.getFormattedMessage()) + "\"}";
}
}
logMsgs = logMsgs + "]";
response.status(200);
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) -> {
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);
return theLogServiceMgr.getConfiguredLoggers();
}, new JsonTransformer());
// http://ip_address:port/system/logmgmt/update CORS request
options(SYSTEM_CONTEXT + "/logmgmt/update", "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; charset=utf-8");
return "";
});
// http://ip_address:port/system/logmgmt/update which changes logging parameters for the process
put(SYSTEM_CONTEXT + "/logmgmt/update", "application/json", (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();
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) -> {
log.debug("bridge settings requested from " + request.ip());
response.status(200);
return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer());
// http://ip_address:port/system/settings CORS request
options(SYSTEM_CONTEXT + "/settings", "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; charset=utf-8");
return "";
});
// http://ip_address:port/system/settings which returns the bridge configuration settings
put(SYSTEM_CONTEXT + "/settings", "application/json", (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);
return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer());
// http://ip_address:port/system/control/reinit CORS request
options(SYSTEM_CONTEXT + "/control/reinit", "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; charset=utf-8");
return "";
});
// http://ip_address:port/system/control/reinit sets the parameter reinit the server
put(SYSTEM_CONTEXT + "/control/reinit", "application/json", (request, response) -> {
return reinit();
});
// http://ip_address:port/system/control/stop CORS request
options(SYSTEM_CONTEXT + "/control/stop", "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; charset=utf-8");
return "";
});
// http://ip_address:port/system/control/stop sets the parameter stop the server
put(SYSTEM_CONTEXT + "/control/stop", "application/json", (request, response) -> {
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) -> {
log.debug("Get backup filenames");
response.status(HttpStatus.SC_OK);
return bridgeSettings.getBackups();
}, new JsonTransformer());
// http://ip_address:port/system/backup/create CORS request
options(SYSTEM_CONTEXT + "/backup/create", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
put (SYSTEM_CONTEXT + "/backup/create", "application/json", (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()));
return returnFilename;
}, new JsonTransformer());
// http://ip_address:port/system/backup/delete CORS request
options(SYSTEM_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (SYSTEM_CONTEXT + "/backup/delete", "application/json", (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;
}, new JsonTransformer());
// http://ip_address:port/system/backup/restore CORS request
options(SYSTEM_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (SYSTEM_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
log.debug("Restore backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null) {
bridgeSettings.restoreBackup(aFilename.getFilename());
bridgeSettings.loadConfig();
}
else
log.warn("No filename given for restore backup.");
return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer());
}
void reacquireCBA() {
cyclicBufferAppender = (CyclicBufferAppender<ILoggingEvent>) lc.getLogger(
Logger.ROOT_LOGGER_NAME).getAppender(CYCLIC_BUFFER_APPENDER_NAME);
cyclicBufferAppender.setMaxSize(bridgeSettings.getBridgeSettingsDescriptor().getNumberoflogmessages());
}
protected void pingListener() {
try {
byte[] buf = new byte[256];
String testData = "M-SEARCH * HTTP/1.1\nHOST: " + Configuration.UPNP_MULTICAST_ADDRESS + ":" + Configuration.UPNP_DISCOVERY_PORT + "ST: urn:schemas-upnp-org:device:CloudProxy:1\nMAN: \"ssdp:discover\"\nMX: 3";
buf = testData.getBytes();
MulticastSocket socket = new MulticastSocket(Configuration.UPNP_DISCOVERY_PORT);
InetAddress group = InetAddress.getByName(Configuration.UPNP_MULTICAST_ADDRESS);
DatagramPacket packet;
packet = new DatagramPacket(buf, buf.length, group, Configuration.UPNP_DISCOVERY_PORT);
socket.send(packet);
socket.close();
}
catch (IOException e) {
log.warn("Error pinging listener. " + e.getMessage());
}
}
public String reinit() {
bridgeSettings.getBridgeControl().setReinit(true);
pingListener();
return "{\"control\":\"reiniting\"}";
}
public String stop() {
bridgeSettings.getBridgeControl().setStop(true);
pingListener();
return "{\"control\":\"stopping\"}";
}
}

View File

@@ -0,0 +1,13 @@
package com.bwssystems.HABridge.api;
public class CallItem {
private String item;
public String getItem() {
return item;
}
public void setItem(String anitem) {
item = anitem;
}
}

View File

@@ -0,0 +1,18 @@
package com.bwssystems.HABridge.api;
public class NameValue {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,13 @@
package com.bwssystems.HABridge.api;
public class SuccessUserResponse {
private UserCreateResponse success;
public UserCreateResponse getSuccess() {
return success;
}
public void setSuccess(UserCreateResponse success) {
this.success = success;
}
}

View File

@@ -0,0 +1,13 @@
package com.bwssystems.HABridge.api;
public class UserCreateResponse {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@@ -1,5 +1,7 @@
package com.bwssystems.HABridge.api.hue;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
/**
* Created by arm on 4/14/15.
*/
@@ -9,6 +11,7 @@ public class DeviceResponse {
private String name;
private String modelid;
private String manufacturername;
private String luminaireuniqueid;
private String uniqueid;
private String swversion;
@@ -68,22 +71,25 @@ public class DeviceResponse {
this.swversion = swversion;
}
public static DeviceResponse createResponse(String name, String id){
DeviceState deviceState = new DeviceState();
DeviceResponse response = new DeviceResponse();
response.setState(deviceState);
deviceState.setOn(false);
deviceState.setReachable(true);
deviceState.setEffect("none");
deviceState.setAlert("none");
deviceState.setBri(254);
public String getLuminaireuniqueid() {
return luminaireuniqueid;
}
response.setName(name);
response.setUniqueid(id);
public void setLuminaireuniqueid(String luminaireuniqueid) {
this.luminaireuniqueid = luminaireuniqueid;
}
public static DeviceResponse createResponse(DeviceDescriptor device){
DeviceResponse response = new DeviceResponse();
response.setState(device.getDeviceState());
response.setName(device.getName());
response.setUniqueid(device.getUniqueid());
response.setManufacturername("Philips");
response.setType("Dimmable light");
response.setModelid("LWB004");
response.setSwversion("66012040");
response.setLuminaireuniqueid(null);
return response;
}

View File

@@ -1,15 +1,23 @@
package com.bwssystems.HABridge.api.hue;
// import java.util.ArrayList;
import java.util.List;
/**
* Created by arm on 4/14/15.
*/
public class DeviceState {
private boolean on;
private int bri = 255;
private int bri;
private int hue;
private int sat;
private String effect;
private int ct;
private String alert;
private String colormode;
private boolean reachable;
private List<Double> xy;
// private int transitiontime;
public boolean isOn() {
return on;
@@ -27,6 +35,22 @@ public class DeviceState {
this.bri = bri;
}
public int getHue() {
return hue;
}
public void setHue(int hue) {
this.hue = hue;
}
public int getSat() {
return sat;
}
public void setSat(int sat) {
this.sat = sat;
}
public String getEffect() {
return effect;
}
@@ -35,6 +59,14 @@ public class DeviceState {
this.effect = effect;
}
public int getCt() {
return ct;
}
public void setCt(int ct) {
this.ct = ct;
}
public String getAlert() {
return alert;
}
@@ -43,6 +75,14 @@ public class DeviceState {
this.alert = alert;
}
public String getColormode() {
return colormode;
}
public void setColormode(String colormode) {
this.colormode = colormode;
}
public boolean isReachable() {
return reachable;
}
@@ -51,6 +91,39 @@ public class DeviceState {
this.reachable = reachable;
}
public List<Double> getXy() {
return xy;
}
public void setXy(List<Double> xy) {
this.xy = xy;
}
// public int getTransitiontime() {
// return transitiontime;
// }
// public void setTransitiontime(int transitiontime) {
// this.transitiontime = transitiontime;
// }
public static DeviceState createDeviceState() {
DeviceState newDeviceState = new DeviceState();
newDeviceState.fillIn();
// newDeviceState.setColormode("none");
// ArrayList<Double> doubleArray = new ArrayList<Double>();
// doubleArray.add(new Double(0));
// doubleArray.add(new Double(0));
// newDeviceState.setXy(doubleArray);
return newDeviceState;
}
public void fillIn() {
if(this.getAlert() == null)
this.setAlert("none");
if(this.getEffect() == null)
this.setEffect("none");
this.setReachable(true);
}
@Override
public String toString() {
return "DeviceState{" +

View File

@@ -0,0 +1,18 @@
package com.bwssystems.HABridge.api.hue;
public class DeviceTypes {
private Boolean bridge;
private String[] lights;
public Boolean getBridge() {
return bridge;
}
public void setBridge(Boolean bridge) {
this.bridge = bridge;
}
public String[] getLights() {
return lights;
}
public void setLights(String[] lights) {
this.lights = lights;
}
}

View File

@@ -0,0 +1,44 @@
package com.bwssystems.HABridge.api.hue;
import java.util.List;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.dao.DeviceRepository;
public class GroupResponse {
private DeviceState action;
private String[] lights;
private String name;
public DeviceState getAction() {
return action;
}
public void setAction(DeviceState action) {
this.action = action;
}
public String[] getLights() {
return lights;
}
public void setLights(String[] lights) {
this.lights = lights;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static GroupResponse createGroupResponse(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("Lightset 0");
theResponse.setLights(theList);
return theResponse;
}
}

View File

@@ -4,19 +4,26 @@ import java.util.HashMap;
import java.util.Map;
import com.bwssystems.HABridge.api.hue.DeviceResponse;
import com.google.gson.JsonObject;
/**
* Created by arm on 4/14/15.
*/
public class HueApiResponse {
private Map<String, DeviceResponse> lights;
private Map<String, String> scenes;
private Map<String, String> groups;
private Map<String, JsonObject> scenes;
private Map<String, JsonObject> groups;
private Map<String, JsonObject> schedules;
private Map<String, JsonObject> sensors;
private Map<String, JsonObject> rules;
private HueConfig config;
public HueApiResponse(String name, String ipaddress, String devicetype, String userid) {
public HueApiResponse(String name, String ipaddress, Map<String, WhitelistEntry> awhitelist) {
super();
this.setConfig(HueConfig.createConfig(name, ipaddress, devicetype, userid));
this.setConfig(HueConfig.createConfig(name, ipaddress, awhitelist));
this.setRules(new HashMap<>());
this.setSensors(new HashMap<>());
this.setSchedules(new HashMap<>());
this.setGroups(new HashMap<>());
this.setScenes(new HashMap<>());
}
@@ -29,22 +36,46 @@ public class HueApiResponse {
this.lights = lights;
}
public Map<String, String> getScenes() {
public Map<String, JsonObject> getScenes() {
return scenes;
}
public void setScenes(Map<String, String> scenes) {
public void setScenes(Map<String, JsonObject> scenes) {
this.scenes = scenes;
}
public Map<String, String> getGroups() {
public Map<String, JsonObject> getGroups() {
return groups;
}
public void setGroups(Map<String, String> groups) {
public void setGroups(Map<String, JsonObject> groups) {
this.groups = groups;
}
public Map<String, JsonObject> getSchedules() {
return schedules;
}
public void setSchedules(Map<String, JsonObject> schedules) {
this.schedules = schedules;
}
public Map<String, JsonObject> getSensors() {
return sensors;
}
public void setSensors(Map<String, JsonObject> sensors) {
this.sensors = sensors;
}
public Map<String, JsonObject> getRules() {
return rules;
}
public void setRules(Map<String, JsonObject> rules) {
this.rules = rules;
}
public HueConfig getConfig() {
return config;
}

View File

@@ -6,7 +6,6 @@ import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
@@ -29,19 +28,23 @@ public class HueConfig
private String localtime;
private String timezone;
private String zigbeechannel;
private String modelid;
private String bridgeid;
private Boolean factorynew;
private String replacesbridgeid;
private Map<String, WhitelistEntry> whitelist;
public static HueConfig createConfig(String name, String ipaddress, String devicetype, String userid) {
public static HueConfig createConfig(String name, String ipaddress, Map<String, WhitelistEntry> awhitelist) {
HueConfig aConfig = new HueConfig();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormatGmt.setTimeZone(TimeZone.getTimeZone("UTC"));
aConfig.setMac(HueConfig.getMacAddress(ipaddress));
aConfig.setApiversion("1.4.0");
aConfig.setApiversion("1.15.0");
aConfig.setPortalservices(false);
aConfig.setGateway(ipaddress);
aConfig.setSwversion("01005215");
aConfig.setLinkbutton(false);
aConfig.setSwversion("01035934");
aConfig.setLinkbutton(true);
aConfig.setIpaddress(ipaddress);
aConfig.setProxyport(0);
aConfig.setSwupdate(Swupdate.createSwupdate());
@@ -53,8 +56,10 @@ public class HueConfig
aConfig.setLocaltime(dateFormat.format(new Date()));
aConfig.setTimezone(TimeZone.getDefault().getID());
aConfig.setZigbeechannel("6");
Map<String, WhitelistEntry> awhitelist = new HashMap<>();
awhitelist.put(userid, WhitelistEntry.createEntry(devicetype));
aConfig.setBridgeid(HuePublicConfig.createConfig(name, ipaddress).getHueBridgeIdFromMac());
aConfig.setModelid("BSB002");
aConfig.setFactorynew(false);
aConfig.setReplacesbridgeid(null);
aConfig.setWhitelist(awhitelist);
return aConfig;
@@ -84,6 +89,10 @@ public class HueConfig
sb.append("00:00:88:00:bb:ee");
} catch (Exception e){
sb.append("00:00:88:00:bb:ee");
}
return sb.toString();
@@ -231,4 +240,36 @@ public class HueConfig
public void setZigbeechannel(String zigbeechannel) {
this.zigbeechannel = zigbeechannel;
}
public String getModelid() {
return modelid;
}
public void setModelid(String modelid) {
this.modelid = modelid;
}
public String getBridgeid() {
return bridgeid;
}
public void setBridgeid(String bridgeid) {
this.bridgeid = bridgeid;
}
public Boolean getFactorynew() {
return factorynew;
}
public void setFactorynew(Boolean factorynew) {
this.factorynew = factorynew;
}
public String getReplacesbridgeid() {
return replacesbridgeid;
}
public void setReplacesbridgeid(String replacesbridgeid) {
this.replacesbridgeid = replacesbridgeid;
}
}

View File

@@ -0,0 +1,19 @@
package com.bwssystems.HABridge.api.hue;
public class HueError {
private HueErrorDetails error;
public HueError(HueErrorDetails error) {
super();
this.error = error;
}
public HueErrorDetails getError() {
return error;
}
public void setError(HueErrorDetails error) {
this.error = error;
}
}

View File

@@ -0,0 +1,56 @@
package com.bwssystems.HABridge.api.hue;
public class HueErrorDetails {
private String type;
private String address;
private String description;
private String method_name;
private String resource_name;
private String value;
public HueErrorDetails(String type, String address, String description, String method_name, String resource_name,
String value) {
super();
this.type = type;
this.address = address;
this.description = description;
this.method_name = method_name;
this.resource_name = resource_name;
this.value = value;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getMethod_name() {
return method_name;
}
public void setMethod_name(String method_name) {
this.method_name = method_name;
}
public String getResource_name() {
return resource_name;
}
public void setResource_name(String resource_name) {
this.resource_name = resource_name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,27 @@
package com.bwssystems.HABridge.api.hue;
import java.util.ArrayList;
public class HueErrorResponse {
private ArrayList<HueError> theErrors;
public HueErrorResponse() {
super();
theErrors = new ArrayList<HueError>();
}
public void addError(HueError anError) {
theErrors.add(anError);
}
public HueError[] getTheErrors() {
HueError theList[] = new HueError[theErrors.size()];
theList = theErrors.toArray(theList);
return theList;
}
public void setTheErrors(ArrayList<HueError> theErrors) {
this.theErrors = theErrors;
}
}

View File

@@ -0,0 +1,151 @@
package com.bwssystems.HABridge.api.hue;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
public class HuePublicConfig
{
private String name;
private String apiversion;
private String swversion;
private String mac;
private String bridgeid;
private String replacesbridgeid;
private Boolean factorynew;
private String modelid;
public static HuePublicConfig createConfig(String name, String ipaddress) {
HuePublicConfig aConfig = new HuePublicConfig();
aConfig.setMac(HuePublicConfig.getMacAddress(ipaddress));
aConfig.setApiversion("1.15.0");
aConfig.setSwversion("01035934");
aConfig.setName(name);
aConfig.setBridgeid(aConfig.getHueBridgeIdFromMac());
aConfig.setModelid("BSB002");
aConfig.setFactorynew(false);
aConfig.setReplacesbridgeid(null);
return aConfig;
}
private static String getMacAddress(String addr)
{
InetAddress ip;
StringBuilder sb = new StringBuilder();
try {
ip = InetAddress.getByName(addr);
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
byte[] mac = network.getHardwareAddress();
for (int i = 0; i < mac.length; i++) {
sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? ":" : ""));
}
} catch (UnknownHostException e) {
sb.append("00:00:88:00:bb:ee");
} catch (SocketException e){
sb.append("00:00:88:00:bb:ee");
} catch (Exception e){
sb.append("00:00:88:00:bb:ee");
}
return sb.toString();
}
public String getSNUUIDFromMac()
{
StringTokenizer st = new StringTokenizer(this.getMac(), ":");
String bridgeUUID = "";
while(st.hasMoreTokens()) {
bridgeUUID = bridgeUUID + st.nextToken();
}
bridgeUUID = bridgeUUID.toLowerCase();
return bridgeUUID.toLowerCase();
}
protected String getHueBridgeIdFromMac()
{
String cleanMac = this.getSNUUIDFromMac();
String bridgeId = cleanMac.substring(0, 6) + "FFFE" + cleanMac.substring(6);
return bridgeId.toUpperCase();
}
public String getMac() {
return mac;
}
public void setMac(String mac) {
this.mac = mac;
}
public String getSwversion() {
return swversion;
}
public void setSwversion(String swversion) {
this.swversion = swversion;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getApiversion() {
return apiversion;
}
public void setApiversion(String apiversion) {
this.apiversion = apiversion;
}
public String getModelid() {
return modelid;
}
public void setModelid(String modelid) {
this.modelid = modelid;
}
public String getBridgeid() {
return bridgeid;
}
public void setBridgeid(String bridgeid) {
this.bridgeid = bridgeid;
}
public Boolean getFactorynew() {
return factorynew;
}
public void setFactorynew(Boolean factorynew) {
this.factorynew = factorynew;
}
public String getReplacesbridgeid() {
return replacesbridgeid;
}
public void setReplacesbridgeid(String replacesbridgeid) {
this.replacesbridgeid = replacesbridgeid;
}
}

View File

@@ -0,0 +1,136 @@
package com.bwssystems.HABridge.api.hue;
// import java.util.ArrayList;
import java.util.List;
/**
* Created by arm on 4/14/15.
*/
public class StateChangeBody {
private boolean on;
private int bri;
private int hue;
private int sat;
private String effect;
private int ct;
private String alert;
private List<Double> xy;
private int transitiontime;
private int bri_inc;
private int hue_inc;
private int sat_inc;
private List<Double> xy_inc;
private int ct_inc;
public boolean isOn() {
return on;
}
public void setOn(boolean on) {
this.on = on;
}
public int getBri() {
return bri;
}
public void setBri(int bri) {
this.bri = bri;
}
public int getHue() {
return hue;
}
public void setHue(int hue) {
this.hue = hue;
}
public int getSat() {
return sat;
}
public void setSat(int sat) {
this.sat = sat;
}
public String getEffect() {
return effect;
}
public void setEffect(String effect) {
this.effect = effect;
}
public int getCt() {
return ct;
}
public void setCt(int ct) {
this.ct = ct;
}
public String getAlert() {
return alert;
}
public void setAlert(String alert) {
this.alert = alert;
}
public List<Double> getXy() {
return xy;
}
public void setXy(List<Double> xy) {
this.xy = xy;
}
public int getTransitiontime() {
return transitiontime;
}
public void setTransitiontime(int transitiontime) {
this.transitiontime = transitiontime;
}
public int getBri_inc() {
return bri_inc;
}
public void setBri_inc(int bri_inc) {
this.bri_inc = bri_inc;
}
public int getHue_inc() {
return hue_inc;
}
public void setHue_inc(int hue_inc) {
this.hue_inc = hue_inc;
}
public int getSat_inc() {
return sat_inc;
}
public void setSat_inc(int sat_inc) {
this.sat_inc = sat_inc;
}
public List<Double> getXy_inc() {
return xy_inc;
}
public void setXy_inc(List<Double> xy_inc) {
this.xy_inc = xy_inc;
}
public int getCt_inc() {
return ct_inc;
}
public void setCt_inc(int ct_inc) {
this.ct_inc = ct_inc;
}
}

View File

@@ -3,20 +3,36 @@ package com.bwssystems.HABridge.api.hue;
public class Swupdate
{
private Integer updatestate;
private Boolean checkforupdate;
private DeviceTypes devicetypes;
private String text;
private Boolean notify;
private Integer updatestate;
private String url;
public static Swupdate createSwupdate() {
Swupdate aSwupdate = new Swupdate();
aSwupdate.setUpdatestate(0);
aSwupdate.setCheckforupdate(false);
aSwupdate.setDevicetypes(new DeviceTypes());
aSwupdate.setNotify(false);
aSwupdate.setText("");
aSwupdate.setUpdatestate(0);
aSwupdate.setUrl("");
return aSwupdate;
}
public Boolean getCheckforupdate() {
return checkforupdate;
}
public void setCheckforupdate(Boolean checkforupdate) {
this.checkforupdate = checkforupdate;
}
public DeviceTypes getDevicetypes() {
return devicetypes;
}
public void setDevicetypes(DeviceTypes devicetypes) {
this.devicetypes = devicetypes;
}
public String getText() {
return text;
}

View File

@@ -0,0 +1,13 @@
package com.bwssystems.HABridge.dao;
public class BackupFilename {
private String filename;
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
}

View File

@@ -1,21 +1,64 @@
package com.bwssystems.HABridge.dao;
import com.bwssystems.HABridge.api.hue.DeviceState;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/*
* Object to handle the device configuration
*/
public class DeviceDescriptor{
private String id;
@SerializedName("id")
@Expose
private String id;
@SerializedName("uniqueid")
@Expose
private String uniqueid;
@SerializedName("name")
@Expose
private String name;
@SerializedName("mapId")
@Expose
private String mapId;
@SerializedName("mapType")
@Expose
private String mapType;
@SerializedName("deviceType")
@Expose
private String deviceType;
@SerializedName("targetDevice")
@Expose
private String targetDevice;
@SerializedName("offUrl")
@Expose
private String offUrl;
@SerializedName("dimUrl")
@Expose
private String dimUrl;
@SerializedName("onUrl")
@Expose
private String onUrl;
@SerializedName("headers")
@Expose
private String headers;
@SerializedName("httpVerb")
@Expose
private String httpVerb;
@SerializedName("contentType")
@Expose
private String contentType;
@SerializedName("contentBody")
@Expose
private String contentBody;
@SerializedName("contentBodyOff")
@Expose
private String contentBodyOff;
@SerializedName("contentBodyDim")
@Expose
private String contentBodyDim;
private DeviceState deviceState;
public String getName() {
return name;
}
@@ -64,7 +107,15 @@ public class DeviceDescriptor{
this.offUrl = offUrl;
}
public String getOnUrl() {
public String getDimUrl() {
return dimUrl;
}
public void setDimUrl(String dimUrl) {
this.dimUrl = dimUrl;
}
public String getOnUrl() {
return onUrl;
}
@@ -80,6 +131,22 @@ public class DeviceDescriptor{
this.id = id;
}
public String getUniqueid() {
return uniqueid;
}
public void setUniqueid(String uniqueid) {
this.uniqueid = uniqueid;
}
public String getHeaders() {
return headers;
}
public void setHeaders(String headers) {
this.headers = headers;
}
public String getHttpVerb() {
return httpVerb;
}
@@ -111,6 +178,23 @@ public class DeviceDescriptor{
public void setContentBodyOff(String contentBodyOff) {
this.contentBodyOff = contentBodyOff;
}
public String getContentBodyDim() {
return contentBodyDim;
}
public void setContentBodyDim(String contentBodyDim) {
this.contentBodyDim = contentBodyDim;
}
public DeviceState getDeviceState() {
if(deviceState == null)
deviceState = DeviceState.createDeviceState();
return deviceState;
}
public void setDeviceState(DeviceState deviceState) {
this.deviceState = deviceState;
}
}

View File

@@ -2,7 +2,7 @@ package com.bwssystems.HABridge.dao;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -10,44 +10,64 @@ import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import javax.xml.bind.DatatypeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.google.gson.stream.JsonReader;
import com.bwssystems.util.BackupHandler;
import com.bwssystems.util.JsonTransformer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.List;
import java.util.ListIterator;
/*
* This is an in memory list to manage the configured devices and saves the list as a JSON string to a file for later
* loading.
*/
public class DeviceRepository {
Map<String, DeviceDescriptor> devices;
Path repositoryPath;
final Random random = new Random();
final Logger log = LoggerFactory.getLogger(DeviceRepository.class);
public class DeviceRepository extends BackupHandler {
private Map<String, DeviceDescriptor> devices;
private Path repositoryPath;
private Gson gson;
private Integer nextId;
private Logger log = LoggerFactory.getLogger(DeviceRepository.class);
public DeviceRepository(String deviceDb) {
super();
gson =
new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
repositoryPath = null;
repositoryPath = Paths.get(deviceDb);
String jsonContent = repositoryReader(repositoryPath);
setupParams(repositoryPath, ".bk", "device.db-");
nextId = 0;
_loadRepository(repositoryPath);
}
public void loadRepository() {
if(repositoryPath != null)
_loadRepository(repositoryPath);
}
private void _loadRepository(Path aPath){
String jsonContent = repositoryReader(aPath);
devices = new HashMap<String, DeviceDescriptor>();
if(jsonContent != null)
{
List<DeviceDescriptor> list = readJsonStream(jsonContent);
ListIterator<DeviceDescriptor> theIterator = list.listIterator();
DeviceDescriptor theDevice = null;
while (theIterator.hasNext()) {
theDevice = theIterator.next();
put(theDevice.getId(), theDevice);
DeviceDescriptor list[] = gson.fromJson(jsonContent, DeviceDescriptor[].class);
for(int i = 0; i < list.length; i++) {
put(list[i].getId(), list[i]);
if(Integer.decode(list[i].getId()) > nextId) {
nextId = Integer.decode(list[i].getId());
}
}
}
}
}
}
public List<DeviceDescriptor> findAll() {
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>(devices.values());
@@ -67,16 +87,50 @@ public class DeviceRepository {
devices.put(id, aDescriptor);
}
public void save(DeviceDescriptor aDescriptor) {
if(aDescriptor.getId() != null)
devices.remove(aDescriptor.getId());
else
aDescriptor.setId(String.valueOf(random.nextInt(Integer.MAX_VALUE)));
put(aDescriptor.getId(), aDescriptor);
JsonTransformer aRenderer = new JsonTransformer();
String jsonValue = aRenderer.render(findAll());
public void save(DeviceDescriptor[] descriptors) {
String theNames = "";
for(int i = 0; i < descriptors.length; i++) {
if(descriptors[i].getId() != null && descriptors[i].getId().length() > 0)
devices.remove(descriptors[i].getId());
else {
nextId++;
descriptors[i].setId(String.valueOf(nextId));
}
if(descriptors[i].getUniqueid() == null || descriptors[i].getUniqueid().length() == 0) {
BigInteger bigInt = BigInteger.valueOf(Integer.decode(descriptors[i].getId()));
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
descriptors[i].setUniqueid("00:17:88:5E:D3:" + hexValue + "-" + hexValue);
}
put(descriptors[i].getId(), descriptors[i]);
theNames = theNames + " " + descriptors[i].getName() + ", ";
}
String jsonValue = gson.toJson(findAll());
repositoryWriter(jsonValue, repositoryPath);
log.debug("Save device(s): " + theNames);
}
public void renumber() {
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>(devices.values());
Iterator<DeviceDescriptor> deviceIterator = list.iterator();
Map<String, DeviceDescriptor> newdevices = new HashMap<String, DeviceDescriptor>();;
nextId = 0;
log.debug("Renumber devices.");
while(deviceIterator.hasNext()) {
nextId++;
DeviceDescriptor theDevice = deviceIterator.next();
theDevice.setId(String.valueOf(nextId));
BigInteger bigInt = BigInteger.valueOf(nextId);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
theDevice.setUniqueid("00:17:88:5E:D3:" + hexValue + "-" + hexValue);
newdevices.put(theDevice.getId(), theDevice);
}
devices = newdevices;
String jsonValue = gson.toJson(findAll());
repositoryWriter(jsonValue, repositoryPath);
log.debug("Save device: " + aDescriptor.getName());
}
public String delete(DeviceDescriptor aDescriptor) {
@@ -137,82 +191,4 @@ public class DeviceRepository {
return content;
}
private List<DeviceDescriptor> readJsonStream(String context) {
JsonReader reader = new JsonReader(new StringReader(context));
List<DeviceDescriptor> theDescriptors = null;
try {
theDescriptors = readDescriptorArray(reader);
} catch (IOException e) {
log.error("Error reading json array: " + context + " message: " + e.getMessage(), e);
} finally {
try {
reader.close();
} catch (IOException e) {
log.error("Error closing json reader: " + context + " message: " + e.getMessage(), e);
}
}
return theDescriptors;
}
public List<DeviceDescriptor> readDescriptorArray(JsonReader reader) throws IOException {
List<DeviceDescriptor> descriptors = new ArrayList<DeviceDescriptor>();
reader.beginArray();
while (reader.hasNext()) {
descriptors.add(readDescriptor(reader));
}
reader.endArray();
return descriptors;
}
public DeviceDescriptor readDescriptor(JsonReader reader) throws IOException {
DeviceDescriptor deviceEntry = new DeviceDescriptor();
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals("id")) {
deviceEntry.setId(reader.nextString());
log.debug("Read a Device - device json id: " + deviceEntry.getId());
} else if (name.equals("name")) {
deviceEntry.setName(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getName());
} else if (name.equals("mapType")) {
deviceEntry.setMapType(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getMapType());
} else if (name.equals("mapId")) {
deviceEntry.setMapId(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getMapId());
} else if (name.equals("deviceType")) {
deviceEntry.setDeviceType(reader.nextString());
log.debug("Read a Device - device json type:" + deviceEntry.getDeviceType());
} else if (name.equals("targetDevice")) {
deviceEntry.setTargetDevice(reader.nextString());
log.debug("Read a Device - device json type:" + deviceEntry.getTargetDevice());
} else if (name.equals("offUrl")) {
deviceEntry.setOffUrl(reader.nextString());
log.debug("Read a Device - device json off URL:" + deviceEntry.getOffUrl());
} else if (name.equals("onUrl")) {
deviceEntry.setOnUrl(reader.nextString());
log.debug("Read a Device - device json on URL:" + deviceEntry.getOnUrl());
} else if (name.equals("httpVerb")) {
deviceEntry.setHttpVerb(reader.nextString());
log.debug("Read a Device - device json httpVerb:" + deviceEntry.getHttpVerb());
} else if (name.equals("contentType")) {
deviceEntry.setContentType(reader.nextString());
log.debug("Read a Device - device json contentType:" + deviceEntry.getContentType());
} else if (name.equals("contentBody")) {
deviceEntry.setContentBody(reader.nextString());
log.debug("Read a Device - device json contentBody:" + deviceEntry.getContentBody());
} else if (name.equals("contentBodyOff")) {
deviceEntry.setContentBodyOff(reader.nextString());
log.debug("Read a Device - device json contentBodyOff:" + deviceEntry.getContentBodyOff());
} else {
reader.skipValue();
}
}
reader.endObject();
return deviceEntry;
}
}
}

View File

@@ -0,0 +1,18 @@
package com.bwssystems.HABridge.dao;
public class ErrorMessage {
private String message;
public ErrorMessage(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -8,6 +8,7 @@ import static spark.Spark.delete;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -15,14 +16,19 @@ import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.Version;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
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.NestBridge.NestHome;
import com.bwssystems.hal.HalHome;
import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.luupRequests.Sdata;
import com.bwssystems.vera.VeraInfo;
import com.bwssystems.hue.HueHome;
import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Scene;
import com.bwssystems.util.JsonTransformer;
import com.bwssystems.vera.VeraHome;
import com.google.gson.Gson;
/**
@@ -31,22 +37,43 @@ import com.google.gson.Gson;
public class DeviceResource {
private static final String API_CONTEXT = "/api/devices";
private static final Logger log = LoggerFactory.getLogger(DeviceResource.class);
private DeviceRepository deviceRepository;
private VeraInfo veraInfo;
private Version version;
private VeraHome veraHome;
private HarmonyHome myHarmonyHome;
private NestHome nestHome;
private HueHome hueHome;
private HalHome halHome;
private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post"));
public DeviceResource(BridgeSettings theSettings, Version theVersion, HarmonyHome theHarmonyHome) {
public DeviceResource(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome, HalHome aHalHome) {
this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
this.veraInfo = new VeraInfo(theSettings.getVeraAddress(), theSettings.isValidVera());
if(theSettings.isValidVera())
this.veraHome = new VeraHome(theSettings);
else
this.veraHome = null;
if(theSettings.isValidHarmony())
this.myHarmonyHome = theHarmonyHome;
else
this.myHarmonyHome = null;
this.version = theVersion;
setupEndpoints();
if(theSettings.isValidNest())
this.nestHome = aNestHome;
else
this.nestHome = null;
if(theSettings.isValidHue())
this.hueHome = aHueHome;
else
this.hueHome = null;
if(theSettings.isValidHal())
this.halHome = aHalHome;
else
this.halHome = null;
setupEndpoints();
}
public DeviceRepository getDeviceRepository() {
@@ -65,24 +92,31 @@ public class DeviceResource {
return "";
});
post(API_CONTEXT, "application/json", (request, response) -> {
log.debug("Create a Device - request body: " + request.body());
DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class);
if(device.getContentBody() != null ) {
if (device.getContentType() == null || device.getHttpVerb() == null || !supportedVerbs.contains(device.getHttpVerb().toLowerCase())) {
device = null;
response.status(HttpStatus.SC_BAD_REQUEST);
log.debug("Bad http verb in create a Device: " + request.body());
return device;
}
}
log.debug("Create a Device(s) - request body: " + request.body());
DeviceDescriptor devices[];
if(request.body().substring(0,1).equalsIgnoreCase("[") == true) {
devices = new Gson().fromJson(request.body(), DeviceDescriptor[].class);
}
else {
devices = new Gson().fromJson("[" + request.body() + "]", DeviceDescriptor[].class);
}
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() + " ");
}
}
}
deviceRepository.save(device);
log.debug("Created a Device: " + request.body());
deviceRepository.save(devices);
log.debug("Created a Device(s): " + request.body());
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.status(HttpStatus.SC_CREATED);
return device;
return devices;
}, new JsonTransformer());
// http://ip_address:port/api/devices/:id CORS request
@@ -97,32 +131,24 @@ public class DeviceResource {
put (API_CONTEXT + "/:id", "application/json", (request, response) -> {
log.debug("Edit a Device - request body: " + request.body());
DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class);
DeviceDescriptor deviceEntry = deviceRepository.findOne(request.params(":id"));
if(deviceEntry == null){
log.debug("Could not save an edited Device Id: " + request.params(":id"));
if(deviceRepository.findOne(request.params(":id")) == null){
log.debug("Could not save an edited device, Device Id not found: " + request.params(":id"));
response.status(HttpStatus.SC_BAD_REQUEST);
return new ErrorMessage("Could not save an edited device, Device Id not found: " + request.params(":id") + " ");
}
else
{
log.debug("Saving an edited Device: " + deviceEntry.getName());
log.debug("Saving an edited Device: " + device.getName());
deviceEntry.setName(device.getName());
if (device.getDeviceType() != null)
deviceEntry.setDeviceType(device.getDeviceType());
deviceEntry.setMapId(device.getMapId());
deviceEntry.setMapType(device.getMapType());
deviceEntry.setTargetDevice(device.getTargetDevice());
deviceEntry.setOnUrl(device.getOnUrl());
deviceEntry.setOffUrl(device.getOffUrl());
deviceEntry.setHttpVerb(device.getHttpVerb());
deviceEntry.setContentType(device.getContentType());
deviceEntry.setContentBody(device.getContentBody());
deviceEntry.setContentBodyOff(device.getContentBodyOff());
device.setDeviceType(device.getDeviceType());
deviceRepository.save(deviceEntry);
DeviceDescriptor[] theDevices = new DeviceDescriptor[1];
theDevices[0] = device;
deviceRepository.save(theDevices);
response.status(HttpStatus.SC_OK);
}
return deviceEntry;
return device;
}, new JsonTransformer());
get (API_CONTEXT, "application/json", (request, response) -> {
@@ -138,8 +164,10 @@ public class DeviceResource {
get (API_CONTEXT + "/:id", "application/json", (request, response) -> {
log.debug("Get a device");
DeviceDescriptor descriptor = deviceRepository.findOne(request.params(":id"));
if(descriptor == null)
if(descriptor == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("Could not find, id: " + request.params(":id") + " ");
}
else
response.status(HttpStatus.SC_OK);
return descriptor;
@@ -149,8 +177,10 @@ public class DeviceResource {
String anId = request.params(":id");
log.debug("Delete a device: " + anId);
DeviceDescriptor deleted = deviceRepository.findOne(anId);
if(deleted == null)
if(deleted == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("Could not delete, id: " + anId + " not found. ");
}
else
{
deviceRepository.delete(deleted);
@@ -159,40 +189,43 @@ public class DeviceResource {
return null;
}, new JsonTransformer());
get (API_CONTEXT + "/habridge/version", "application/json", (request, response) -> {
log.debug("Get HA Bridge version: v" + version.getVersion());
response.status(HttpStatus.SC_OK);
return "{\"version\":\"" + version.getVersion() + "\"}";
});
get (API_CONTEXT + "/vera/devices", "application/json", (request, response) -> {
log.debug("Get vera devices");
Sdata sData = veraInfo.getSdata();
if(sData == null){
if(veraHome == null){
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Vera is not available.");
}
response.status(HttpStatus.SC_OK);
return sData.getDevices();
List<Device> theDevices = veraHome.getDevices();
if(theDevices == null) {
response.status(HttpStatus.SC_SERVICE_UNAVAILABLE);
return new ErrorMessage("A Vera request failed to get devices. Check your Vera IP addresses.");
}
else
response.status(HttpStatus.SC_OK);
return theDevices;
}, new JsonTransformer());
get (API_CONTEXT + "/vera/scenes", "application/json", (request, response) -> {
log.debug("Get vera scenes");
Sdata sData = veraInfo.getSdata();
if(sData == null){
if(veraHome == null){
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Vera is not available.");
}
response.status(HttpStatus.SC_OK);
return sData.getScenes();
List<Scene> theScenes = veraHome.getScenes();
if(theScenes == null) {
response.status(HttpStatus.SC_SERVICE_UNAVAILABLE);
return new ErrorMessage("A Vera is not available and failed to get scenes. Check your Vera IP addresses.");
}
else
response.status(HttpStatus.SC_OK);
return theScenes;
}, new JsonTransformer());
get (API_CONTEXT + "/harmony/activities", "application/json", (request, response) -> {
log.debug("Get harmony activities");
if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Harmony is not available.");
}
response.status(HttpStatus.SC_OK);
return myHarmonyHome.getActivities();
@@ -202,7 +235,7 @@ public class DeviceResource {
log.debug("Get harmony current activity");
if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Harmony is not available.");
}
response.status(HttpStatus.SC_OK);
return myHarmonyHome.getCurrentActivities();
@@ -212,11 +245,118 @@ public class DeviceResource {
log.debug("Get harmony devices");
if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Harmony is not available.");
}
response.status(HttpStatus.SC_OK);
return myHarmonyHome.getDevices();
}, new JsonTransformer());
get (API_CONTEXT + "/nest/items", "application/json", (request, response) -> {
log.debug("Get nest items");
if(nestHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("A Nest is not available.");
}
response.status(HttpStatus.SC_OK);
return nestHome.getItems();
}, new JsonTransformer());
get (API_CONTEXT + "/hue/devices", "application/json", (request, response) -> {
log.debug("Get hue items");
if(hueHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("A Hue is not available.");
}
response.status(HttpStatus.SC_OK);
return hueHome.getDevices();
}, new JsonTransformer());
get (API_CONTEXT + "/hal/devices", "application/json", (request, response) -> {
log.debug("Get hal items");
if(halHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("A Hal is not available.");
}
response.status(HttpStatus.SC_OK);
return halHome.getDevices();
}, new JsonTransformer());
// http://ip_address:port/api/devices/exec/renumber CORS request
options(API_CONTEXT + "/exec/renumber", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (API_CONTEXT + "/exec/renumber", "application/json", (request, response) -> {
log.debug("Renumber devices.");
deviceRepository.renumber();
return null;
}, new JsonTransformer());
get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> {
log.debug("Get backup filenames");
response.status(HttpStatus.SC_OK);
return deviceRepository.getBackups();
}, new JsonTransformer());
// http://ip_address:port/api/devices/backup/create CORS request
options(API_CONTEXT + "/backup/create", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
put (API_CONTEXT + "/backup/create", "application/json", (request, response) -> {
log.debug("Create backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
BackupFilename returnFilename = new BackupFilename();
returnFilename.setFilename(deviceRepository.backup(aFilename.getFilename()));
return returnFilename;
}, new JsonTransformer());
// http://ip_address:port/api/devices/backup/delete CORS request
options(API_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (API_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
log.debug("Delete backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null)
deviceRepository.deleteBackup(aFilename.getFilename());
else
log.warn("No filename given for delete backup.");
return null;
}, new JsonTransformer());
// http://ip_address:port/api/devices/backup/restore CORS request
options(API_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (API_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
log.debug("Restore backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null) {
deviceRepository.restoreBackup(aFilename.getFilename());
deviceRepository.loadRepository();
}
else
log.warn("No filename given for restore backup.");
return null;
}, new JsonTransformer());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,11 @@ package com.bwssystems.HABridge.upnp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeControlDescriptor;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.Configuration;
import com.bwssystems.HABridge.api.hue.HuePublicConfig;
import com.bwssystems.util.UDPDatagramSender;
import java.io.IOException;
import java.net.*;
@@ -14,79 +18,141 @@ import org.apache.http.conn.util.*;
public class UpnpListener {
private Logger log = LoggerFactory.getLogger(UpnpListener.class);
private static final int UPNP_DISCOVERY_PORT = 1900;
private static final String UPNP_MULTICAST_ADDRESS = "239.255.255.250";
private int upnpResponsePort;
private UDPDatagramSender theUDPDatagramSender;
private int httpServerPort;
private String responseAddress;
private boolean strict;
private boolean traceupnp;
public UpnpListener(BridgeSettings theSettings) {
private BridgeControlDescriptor bridgeControl;
private String responseTemplate1 = "HTTP/1.1 200 OK\r\n" +
"HOST: %s:%s\r\n" +
"CACHE-CONTROL: max-age=100\r\n" +
"EXT:\r\n" +
"LOCATION: http://%s:%s/description.xml\r\n" +
"SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.15.0\r\n" +
"hue-bridgeid: %s\r\n" +
"ST: upnp:rootdevice\r\n" +
"USN: uuid:2f402f80-da50-11e1-9b23-%s::upnp:rootdevice\r\n\r\n";
private String responseTemplate2 = "HTTP/1.1 200 OK\r\n" +
"HOST: %s:%s\r\n" +
"CACHE-CONTROL: max-age=100\r\n" +
"EXT:\r\n" +
"LOCATION: http://%s:%s/description.xml\r\n" +
"SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.15.0\r\n" +
"hue-bridgeid: %s\r\n" +
"ST: uuid:2f402f80-da50-11e1-9b23-%s\r\n" +
"USN: uuid:2f402f80-da50-11e1-9b23-%s\r\n\r\n";
private String responseTemplate3 = "HTTP/1.1 200 OK\r\n" +
"HOST: %s:%s\r\n" +
"CACHE-CONTROL: max-age=100\r\n" +
"EXT:\r\n" +
"LOCATION: http://%s:%s/description.xml\r\n" +
"SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.15.0\r\n" +
"hue-bridgeid: %s\r\n" +
"ST: urn:schemas-upnp-org:device:basic:1\r\n" +
"USN: uuid:2f402f80-da50-11e1-9b23-%s\r\n\r\n";
public UpnpListener(BridgeSettingsDescriptor theSettings, BridgeControlDescriptor theControl, UDPDatagramSender aUdpDatagramSender) {
super();
upnpResponsePort = Integer.valueOf(theSettings.getUpnpResponsePort());
theUDPDatagramSender = aUdpDatagramSender;
httpServerPort = Integer.valueOf(theSettings.getServerPort());
responseAddress = theSettings.getUpnpConfigAddress();
strict = theSettings.isUpnpStrict();
traceupnp = theSettings.isTraceupnp();
bridgeControl = theControl;
}
public void startListening(){
@SuppressWarnings("resource")
public boolean startListening(){
log.info("UPNP Discovery Listener starting....");
MulticastSocket upnpMulticastSocket = null;
Enumeration<NetworkInterface> ifs = null;
try (DatagramSocket responseSocket = new DatagramSocket(upnpResponsePort);
MulticastSocket upnpMulticastSocket = new MulticastSocket(UPNP_DISCOVERY_PORT);) {
InetSocketAddress socketAddress = new InetSocketAddress(UPNP_MULTICAST_ADDRESS, UPNP_DISCOVERY_PORT);
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
try {
upnpMulticastSocket = new MulticastSocket(Configuration.UPNP_DISCOVERY_PORT);
} catch(IOException e){
log.error("Upnp Discovery Port is in use, or restricted by admin (try running with sudo or admin privs): " + Configuration.UPNP_DISCOVERY_PORT + " with message: " + e.getMessage());
return false;
}
InetSocketAddress socketAddress = new InetSocketAddress(Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT);
try {
ifs = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
log.error("Could not get network interfaces for this machine: " + e.getMessage());
return false;
}
while (ifs.hasMoreElements()) {
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses();
String name = xface.getName();
int IPsPerNic = 0;
while (ifs.hasMoreElements()) {
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses();
String name = xface.getName();
int IPsPerNic = 0;
while (addrs.hasMoreElements()) {
InetAddress addr = addrs.nextElement();
if(traceupnp)
log.info("Traceupnp: " + name + " ... has addr " + addr);
else
log.debug(name + " ... has addr " + addr);
if (InetAddressUtils.isIPv4Address(addr.getHostAddress())) {
IPsPerNic++;
}
while (addrs.hasMoreElements()) {
InetAddress addr = addrs.nextElement();
if (traceupnp)
log.info("Traceupnp: " + name + " ... has addr " + addr);
else
log.debug(name + " ... has addr " + addr);
if (InetAddressUtils.isIPv4Address(addr.getHostAddress())) {
IPsPerNic++;
}
log.debug("Checking " + name + " to our interface set");
if (IPsPerNic > 0) {
}
log.debug("Checking " + name + " to our interface set");
if (IPsPerNic > 0) {
try {
upnpMulticastSocket.joinGroup(socketAddress, xface);
if(traceupnp)
if (traceupnp)
log.info("Traceupnp: Adding " + name + " to our interface set");
else
log.debug("Adding " + name + " to our interface set");
} catch (IOException e) {
log.warn("Multicast join failed for: " + socketAddress.getHostName() + " to interface: "
+ xface.getName() + " with message: " + e.getMessage());
}
}
log.info("UPNP Discovery Listener running and ready....");
while(true){ //trigger shutdown here
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
upnpMulticastSocket.receive(packet);
if(isSSDPDiscovery(packet)){
sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort());
}
}
} catch (IOException e) {
log.error("UpnpListener encountered an error opening sockets. Shutting down", e);
}
log.info("UPNP Discovery Listener Stopped");
log.info("UPNP Discovery Listener running and ready....");
boolean loopControl = true;
boolean error = false;
while (loopControl) { // trigger shutdown here
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
try {
upnpMulticastSocket.receive(packet);
if (isSSDPDiscovery(packet)) {
try {
sendUpnpResponse(packet.getAddress(), packet.getPort());
} catch (IOException e) {
log.warn("UpnpListener encountered an error sending upnp response packet. IP: " + packet.getAddress().getHostAddress() + " with message: " + e.getMessage());
log.debug("UpnpListener send upnp exception: ", e);
}
}
} catch (IOException e) {
log.error("UpnpListener encountered an error reading socket. Shutting down", e);
error = true;
}
if (error || bridgeControl.isReinit() || bridgeControl.isStop()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// noop
}
loopControl = false;
}
}
upnpMulticastSocket.close();
if (bridgeControl.isReinit())
log.info("UPNP Discovery Listener - ended, restart found");
if (bridgeControl.isStop())
log.info("UPNP Discovery Listener - ended, stop found");
if (!bridgeControl.isStop() && !bridgeControl.isReinit()) {
log.info("UPNP Discovery Listener - ended, error found");
return false;
}
return bridgeControl.isReinit();
}
/**
@@ -94,50 +160,70 @@ public class UpnpListener {
*/
protected boolean isSSDPDiscovery(DatagramPacket packet){
//Only respond to discover request for strict upnp form
String packetString = new String(packet.getData());
String packetString = new String(packet.getData(), 0, packet.getLength());
if(packetString != null && packetString.startsWith("M-SEARCH * HTTP/1.1") && packetString.contains("\"ssdp:discover\"")){
if(traceupnp) {
log.info("Traceupnp: SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
log.info("Traceupnp: isSSDPDiscovery found message to be an M-SEARCH message.");
}
else {
log.debug("Got SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
log.debug("Found message to be an M-SEARCH message.");
}
log.debug("isSSDPDiscovery Found message to be an M-SEARCH message.");
log.debug("isSSDPDiscovery Got SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
if(strict && (packetString.contains("ST: urn:schemas-upnp-org:device:basic:1") || packetString.contains("ST: upnp:rootdevice") || packetString.contains("ST: ssdp:all")))
{
if(traceupnp)
if(traceupnp) {
log.info("Traceupnp: isSSDPDiscovery found message to be an M-SEARCH message.");
log.info("Traceupnp: isSSDPDiscovery found message to be valid under strict rules - strict: " + strict);
log.info("Traceupnp: SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
}
else
log.debug("isSSDPDiscovery found message to be valid under strict rules - strict: " + strict);
return true;
}
else if (!strict)
{
if(traceupnp)
if(traceupnp) {
log.info("Traceupnp: isSSDPDiscovery found message to be an M-SEARCH message.");
log.info("Traceupnp: isSSDPDiscovery found message to be valid under loose rules - strict: " + strict);
log.info("Traceupnp: SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
}
else
log.debug("isSSDPDiscovery found message to be valid under loose rules - strict: " + strict);
return true;
}
}
if(traceupnp)
log.info("Traceupnp: isSSDPDiscovery found message to not be valid - strict: " + strict);
else {
// log.debug("isSSDPDiscovery found message to not be valid - strict: " + strict);
// log.debug("SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
}
return false;
}
String discoveryTemplate = "HTTP/1.1 200 OK\r\n" +
"CACHE-CONTROL: max-age=86400\r\n" +
"EXT:\r\n" +
"LOCATION: http://%s:%s/description.xml\r\n" +
"SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/0.1\r\n" +
"ST: urn:schemas-upnp-org:device:basic:1\r\n" +
"USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1\r\n\r\n";
protected void sendUpnpResponse(DatagramSocket socket, InetAddress requester, int sourcePort) throws IOException {
protected void sendUpnpResponse(InetAddress requester, int sourcePort) throws IOException {
String discoveryResponse = null;
discoveryResponse = String.format(discoveryTemplate, responseAddress, httpServerPort);
if(traceupnp)
log.info("Traceupnp: sendUpnpResponse: " + discoveryResponse);
String bridgeId = null;
String bridgeSNUUID = null;
HuePublicConfig aHueConfig = HuePublicConfig.createConfig("temp", responseAddress);
bridgeId = aHueConfig.getBridgeid();
bridgeSNUUID = aHueConfig.getSNUUIDFromMac();
discoveryResponse = String.format(responseTemplate1, Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT, responseAddress, httpServerPort, bridgeId, bridgeSNUUID);
if(traceupnp) {
log.info("Traceupnp: sendUpnpResponse discovery responseTemplate1 is <<<" + discoveryResponse + ">>>");
}
else
log.debug("sendUpnpResponse: " + discoveryResponse);
DatagramPacket response = new DatagramPacket(discoveryResponse.getBytes(), discoveryResponse.length(), requester, sourcePort);
socket.send(response);
log.debug("sendUpnpResponse discovery responseTemplate1 is <<<" + discoveryResponse + ">>>");
theUDPDatagramSender.sendUDPResponse(discoveryResponse, requester, sourcePort);
discoveryResponse = String.format(responseTemplate2, Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT, responseAddress, httpServerPort, bridgeId, bridgeSNUUID, bridgeSNUUID);
if(traceupnp) {
log.info("Traceupnp: sendUpnpResponse discovery responseTemplate2 is <<<" + discoveryResponse + ">>>");
}
else
log.debug("sendUpnpResponse discovery responseTemplate2 is <<<" + discoveryResponse + ">>>");
theUDPDatagramSender.sendUDPResponse(discoveryResponse, requester, sourcePort);
discoveryResponse = String.format(responseTemplate3, Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT, responseAddress, httpServerPort, bridgeId, bridgeSNUUID);
if(traceupnp) {
log.info("Traceupnp: sendUpnpResponse discovery responseTemplate3 is <<<" + discoveryResponse + ">>>");
}
else
log.debug("sendUpnpResponse discovery responseTemplate3 is <<<" + discoveryResponse + ">>>");
theUDPDatagramSender.sendUDPResponse(discoveryResponse, requester, sourcePort);
}
}

View File

@@ -3,8 +3,8 @@ package com.bwssystems.HABridge.upnp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.api.hue.HuePublicConfig;
import static spark.Spark.get;
@@ -12,47 +12,60 @@ import static spark.Spark.get;
*
*/
public class UpnpSettingsResource {
private static final String UPNP_CONTEXT = "/upnp";
private Logger log = LoggerFactory.getLogger(UpnpSettingsResource.class);
private BridgeSettings theSettings;
private BridgeSettingsDescriptor theSettings;
private String hueTemplate = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
+ "<specVersion>\n" + "<major>1</major>\n" + "<minor>0</minor>\n" + "</specVersion>\n"
+ "<URLBase>http://%s:%s/</URLBase>\n" + // hostname string
"<device>\n" + "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>\n"
+ "<friendlyName>HA-Bridge (%s)</friendlyName>\n"
+ "<manufacturer>Royal Philips Electronics</manufacturer>\n"
+ "<manufacturerURL>http://www.bwssystems.com</manufacturerURL>\n"
+ "<modelDescription>Hue Emulator for HA bridge</modelDescription>\n"
+ "<modelName>Philips hue bridge 2012</modelName>\n" + "<modelNumber>929000226503</modelNumber>\n"
+ "<modelURL>http://www.bwssystems.com/apps.html</modelURL>\n"
+ "<serialNumber>0017880ae670</serialNumber>\n"
+ "<UDN>uuid:88f6698f-2c83-4393-bd03-cd54a9f8595</UDN>\n" + "<serviceList>\n" + "<service>\n"
+ "<serviceType>(null)</serviceType>\n" + "<serviceId>(null)</serviceId>\n"
+ "<controlURL>(null)</controlURL>\n" + "<eventSubURL>(null)</eventSubURL>\n"
+ "<SCPDURL>(null)</SCPDURL>\n" + "</service>\n" + "</serviceList>\n"
+ "<presentationURL>index.html</presentationURL>\n" + "<iconList>\n" + "<icon>\n"
+ "<mimetype>image/png</mimetype>\n" + "<height>48</height>\n" + "<width>48</width>\n"
+ "<depth>24</depth>\n" + "<url>hue_logo_0.png</url>\n" + "</icon>\n" + "<icon>\n"
+ "<mimetype>image/png</mimetype>\n" + "<height>120</height>\n" + "<width>120</width>\n"
+ "<depth>24</depth>\n" + "<url>hue_logo_3.png</url>\n" + "</icon>\n" + "</iconList>\n" + "</device>\n"
private String hueTemplate = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
+ "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
+ "<specVersion>\n"
+ "<major>1</major>\n"
+ "<minor>0</minor>\n"
+ "</specVersion>\n"
+ "<URLBase>http://%s:%s/</URLBase>\n"
+ "<device>\n"
+ "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>\n"
+ "<friendlyName>Philips hue (%s)</friendlyName>\n"
+ "<manufacturer>Royal Philips Electronics</manufacturer>\n"
+ "<manufacturerURL>http://www.philips.com</manufacturerURL>\n"
+ "<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>\n"
+ "<modelName>Philips hue bridge 2015</modelName>\n"
+ "<modelNumber>BSB002</modelNumber>\n"
+ "<modelURL>http://www.meethue.com</modelURL>\n"
+ "<serialNumber>%s</serialNumber>\n"
+ "<UDN>uuid:2f402f80-da50-11e1-9b23-%s</UDN>\n"
+ "<serviceList>\n"
+ "<service>\n"
+ "<serviceType>(null)</serviceType>\n"
+ "<serviceId>(null)</serviceId>\n"
+ "<controlURL>(null)</controlURL>\n"
+ "<eventSubURL>(null)</eventSubURL>\n"
+ "<SCPDURL>(null)</SCPDURL>\n"
+ "</service>\n"
+ "</serviceList>\n"
+ "<presentationURL>index.html</presentationURL>\n"
+ "<iconList>\n"
+ "<icon>\n"
+ "<mimetype>image/png</mimetype>\n"
+ "<height>48</height>\n"
+ "<width>48</width>\n"
+ "<depth>24</depth>\n"
+ "<url>hue_logo_0.png</url>\n"
+ "</icon>\n"
+ "<icon>\n"
+ "<mimetype>image/png</mimetype>\n"
+ "<height>120</height>\n"
+ "<width>120</width>\n"
+ "<depth>24</depth>\n"
+ "<url>hue_logo_3.png</url>\n"
+ "</icon>\n"
+ "</iconList>\n"
+ "</device>\n"
+ "</root>\n";
public UpnpSettingsResource(BridgeSettings theBridgeSettings) {
public UpnpSettingsResource(BridgeSettingsDescriptor theBridgeSettings) {
super();
this.theSettings = new BridgeSettings();
this.theSettings.setDevMode(theBridgeSettings.isDevMode());
this.theSettings.setHarmonyAddress(theBridgeSettings.getHarmonyAddress());
this.theSettings.setServerPort(theBridgeSettings.getServerPort());
this.theSettings.setTraceupnp(theBridgeSettings.isTraceupnp());
this.theSettings.setUpnpConfigAddress(theBridgeSettings.getUpnpConfigAddress());
this.theSettings.setUpnpDeviceDb(theBridgeSettings.getUpnpDeviceDb());
this.theSettings.setUpnpResponseDevices(theBridgeSettings.getUpnpResponseDevices());
this.theSettings.setUpnpResponsePort(theBridgeSettings.getUpnpResponsePort());
this.theSettings.setUpnpStrict(theBridgeSettings.isUpnpStrict());
this.theSettings.setVeraAddress(theBridgeSettings.getVeraAddress());
this.theSettings = theBridgeSettings;
}
public void setupServer() {
@@ -60,16 +73,17 @@ public class UpnpSettingsResource {
// http://ip_adress:port/description.xml which returns the xml configuration for the hue emulator
get("/description.xml", "application/xml; charset=utf-8", (request, response) -> {
if(theSettings.isTraceupnp())
log.info("Traceupnp: upnp device settings requested: " + request.params(":id") + " from " + request.ip() + ":" + request.port());
log.info("Traceupnp: upnp device settings requested: " + " from " + request.ip() + ":" + request.port());
else
log.debug("upnp device settings requested: " + request.params(":id") + " from " + request.ip() + ":" + request.port());
log.debug("upnp device settings requested: " + " from " + request.ip() + ":" + request.port());
String portNumber = Integer.toString(request.port());
String filledTemplate = null;
filledTemplate = String.format(hueTemplate, theSettings.getUpnpConfigAddress(), portNumber, theSettings.getUpnpConfigAddress());
String bridgeIdMac = HuePublicConfig.createConfig("temp", theSettings.getUpnpConfigAddress()).getSNUUIDFromMac();
filledTemplate = String.format(hueTemplate, theSettings.getUpnpConfigAddress(), portNumber, theSettings.getUpnpConfigAddress(), bridgeIdMac, bridgeIdMac);
if(theSettings.isTraceupnp())
log.info("Traceupnp: upnp device settings response: " + filledTemplate);
log.info("Traceupnp: upnp device settings template filled with address: " + theSettings.getUpnpConfigAddress() + " and port: " + portNumber);
else
log.debug("upnp device settings response: " + filledTemplate);
log.debug("Traceupnp: upnp device settings template filled with address: " + theSettings.getUpnpConfigAddress() + " and port: " + portNumber);
// response.header("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
// response.header("Pragma", "no-cache");
// response.header("Expires", "Mon, 1 Aug 2011 09:00:00 GMT");
@@ -85,14 +99,17 @@ public class UpnpSettingsResource {
return filledTemplate;
} );
// http://ip_address:port/upnp/settings which returns the bridge configuration settings
get(UPNP_CONTEXT + "/settings", "application/json", (request, response) -> {
log.debug("bridge settings requested from " + request.ip());
response.status(200);
return theSettings;
}, new JsonTransformer());
// http://ip_adress:port/favicon.ico
get("/favicon.ico", "application/xml; charset=utf-8", (request, response) -> {
return "";
} );
// http://ip_adress:port/hue_logo_0.png
get("/hue_logo_0.png", "application/xml; charset=utf-8", (request, response) -> {
return "";
} );
// http://ip_adress:port/hue_logo_3.png
get("/hue_logo_3.png", "application/xml; charset=utf-8", (request, response) -> {
return "";
} );
}
}

View File

@@ -0,0 +1,110 @@
package com.bwssystems.NestBridge;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.nest.controller.Home;
import com.bwssystems.nest.controller.Nest;
import com.bwssystems.nest.controller.NestSession;
import com.bwssystems.nest.controller.Thermostat;
import com.bwssystems.nest.protocol.error.LoginException;
import com.bwssystems.nest.protocol.status.WhereDetail;
import com.bwssystems.nest.protocol.status.WhereItem;
public class NestHome {
private static final Logger log = LoggerFactory.getLogger(NestHome.class);
private NestSession theSession;
private Nest theNest;
private ArrayList<NestItem> nestItems;
public NestHome(BridgeSettingsDescriptor bridgeSettings) {
theSession = null;
theNest = null;
nestItems = null;
if(!bridgeSettings.isValidNest()) {
log.debug("not a valid nest");
return;
}
try {
theSession = new NestSession(bridgeSettings.getNestuser(), bridgeSettings.getNestpwd());
theNest = new Nest(theSession);
} catch (LoginException e) {
log.error("Caught Login Exception, exiting....");
theSession = null;
}
}
public List<NestItem> getItems() {
if(theNest == null)
return null;
if(nestItems == null) {
nestItems = new ArrayList<NestItem>();
Set<String> homeNames = theNest.getHomeNames();
Home aHome = null;
NestItem anItem = null;
for(String name : homeNames) {
aHome = theNest.getHome(name);
anItem = new NestItem();
anItem.setId(name);
anItem.setName(aHome.getDetail().getName());
anItem.setType("Home");
anItem.setLocation(aHome.getDetail().getLocation());
nestItems.add(anItem);
}
Thermostat thermo = null;
Set<String> thermoNames = theNest.getThermostatNames();
for(String name : thermoNames) {
thermo = theNest.getThermostat(name);
anItem = new NestItem();
anItem.setId(name);
anItem.setType("Thermostat");
String where = null;
String homeName= null;
Boolean found = false;
for(String aHomeName : homeNames) {
WhereDetail aDetail = theNest.getWhere(aHomeName);
ListIterator<WhereItem> anIterator = aDetail.getWheres().listIterator();
while(anIterator.hasNext()) {
WhereItem aWhereItem = (WhereItem) anIterator.next();
if(aWhereItem.getWhereId().equals(thermo.getDeviceDetail().getWhereId())) {
where = aWhereItem.getName();
homeName = theNest.getHome(aHomeName).getDetail().getName();
found = true;
break;
}
}
if(found)
break;
}
anItem.setName(where + "(" + name.substring(name.length() - 4) + ")");
anItem.setLocation(where + " - " + homeName);
nestItems.add(anItem);
}
}
return nestItems;
}
public Nest getTheNest() {
return theNest;
}
public void closeTheNest() {
if(theSession != null) {
theNest.endNestSession();
theNest = null;
theSession = null;
nestItems = null;
}
}
}

View File

@@ -0,0 +1,33 @@
package com.bwssystems.NestBridge;
public class NestInstruction {
private String name;
private Boolean away;
private String control;
private String temp;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getAway() {
return away;
}
public void setAway(Boolean away) {
this.away = away;
}
public String getControl() {
return control;
}
public void setControl(String control) {
this.control = control;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
}

View File

@@ -0,0 +1,41 @@
package com.bwssystems.NestBridge;
import java.io.UnsupportedEncodingException;
public class NestItem {
private String name;
private String id;
private String type;
private String location;
public String getName() {
return name;
}
public void setName(String name) {
byte ptext[];
String theLabel = new String(name);
try {
ptext = theLabel.getBytes("ISO-8859-1");
this.name = new String(ptext, "UTF-8");
} catch (UnsupportedEncodingException e) {
this.name = theLabel;
}
}
public String getId() {
return id;
}
public void setId(String anid) {
id = anid;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}

View File

@@ -0,0 +1,17 @@
package com.bwssystems.hal;
import java.util.List;
import com.google.gson.annotations.SerializedName;
public class DeviceElements {
@SerializedName(value="DeviceElements", alternate={"SceneElements", "GroupElements", "HVACElements", "MacroElements", "IrElements", "IrButtons"})
private List<DeviceName> DeviceElements;
public List<DeviceName> getDeviceElements() {
return DeviceElements;
}
public void setDeviceElements(List<DeviceName> deviceElements) {
DeviceElements = deviceElements;
}
}

View File

@@ -0,0 +1,17 @@
package com.bwssystems.hal;
import com.google.gson.annotations.SerializedName;
public class DeviceName {
@SerializedName(value="DeviceName", alternate={"SceneName", "GroupName", "HVACName", "MacroName", "IrName", "IrButton"})
private String DeviceName;
public String getDeviceName() {
return DeviceName;
}
public void setDeviceName(String deviceName) {
DeviceName = deviceName;
}
}

View File

@@ -0,0 +1,39 @@
package com.bwssystems.hal;
public class HalDevice {
private String haldevicetype;
private String haldevicename;
private String haladdress;
private String halname;
private DeviceElements buttons;
public String getHaldevicetype() {
return haldevicetype;
}
public void setHaldevicetype(String haldevicetype) {
this.haldevicetype = haldevicetype;
}
public String getHaldevicename() {
return haldevicename;
}
public void setHaldevicename(String haldevicename) {
this.haldevicename = haldevicename;
}
public String getHaladdress() {
return haladdress;
}
public void setHaladdress(String haladdress) {
this.haladdress = haladdress;
}
public String getHalname() {
return halname;
}
public void setHalname(String halname) {
this.halname = halname;
}
public DeviceElements getButtons() {
return buttons;
}
public void setButtons(DeviceElements buttons) {
this.buttons = buttons;
}
}

View File

@@ -0,0 +1,113 @@
package com.bwssystems.hal;
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.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.NamedIP;
public class HalHome {
private static final Logger log = LoggerFactory.getLogger(HalHome.class);
private Map<String, HalInfo> hals;
public HalHome(BridgeSettingsDescriptor bridgeSettings) {
super();
hals = new HashMap<String, HalInfo>();
if(!bridgeSettings.isValidHal())
return;
Iterator<NamedIP> theList = bridgeSettings.getHaladdress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aHal = theList.next();
try {
hals.put(aHal.getName(), new HalInfo(aHal, bridgeSettings.getHaltoken()));
} catch (Exception e) {
log.error("Cannot get harmony client (" + aHal.getName() + ") setup, Exiting with message: " + e.getMessage(), e);
return;
}
}
}
public List<HalDevice> getDevices() {
log.debug("consolidating devices for hues");
List<HalDevice> theResponse = null;
Iterator<String> keys = hals.keySet().iterator();
List<HalDevice> deviceList = new ArrayList<HalDevice>();
while(keys.hasNext()) {
String key = keys.next();
theResponse = hals.get(key).getLights();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else {
log.warn("Cannot get lights for Hal with name: " + key + ", skipping this HAL.");
continue;
}
theResponse = hals.get(key).getAppliances();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get appliances for Hal with name: " + key);
theResponse = hals.get(key).getTheatre();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get theatre for Hal with name: " + key);
theResponse = hals.get(key).getCustom();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get custom for Hal with name: " + key);
theResponse = hals.get(key).getHVAC();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get HVAC for Hal with name: " + key);
theResponse = hals.get(key).getHome(key);
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get Homes for Hal with name: " + key);
theResponse = hals.get(key).getGroups();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get Groups for Hal with name: " + key);
theResponse = hals.get(key).getMacros();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get Macros for Hal with name: " + key);
theResponse = hals.get(key).getScenes();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get Scenes for Hal with name: " + key);
theResponse = hals.get(key).getButtons();
if(theResponse != null)
addHalDevices(deviceList, theResponse, key);
else
log.warn("Cannot get Buttons for Hal with name: " + key);
}
return deviceList;
}
private Boolean addHalDevices(List<HalDevice> theDeviceList, List<HalDevice> theSourceList, String theKey) {
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);
}
return true;
}
}

View File

@@ -0,0 +1,206 @@
package com.bwssystems.hal;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.util.TextStringFormatter;
import com.google.gson.Gson;
public class HalInfo {
private static final Logger log = LoggerFactory.getLogger(HalInfo.class);
private static final String DEVICE_REQUEST = "/DeviceData!DeviceCmd=GetNames!DeviceType=";
private static final String HVAC_REQUEST = "/HVACData!HVACCmd=GetNames";
private static final String GROUP_REQUEST = "/GroupData!GroupCmd=GetNames";
private static final String MACRO_REQUEST = "/MacroData!MacroCmd=GetNames";
private static final String SCENE_REQUEST = "/SceneData!SceneCmd=GetNames";
private static final String IRDATA_REQUEST = "/IrData!IRCmd=GetNames";
private static final String IRBUTTON_REQUEST = "/IrData!IRCmd=GetButtons!IrDevice=";
private static final String TOKEN_REQUEST = "?Token=";
private static final String LIGHT_REQUEST = "Light";
private static final String APPL_REQUEST = "Appl";
// private static final String VIDEO_REQUEST = "Video";
private static final String THEATRE_REQUEST = "Theatre";
private static final String CUSTOM_REQUEST = "Custom";
private static final String HVAC_TYPE = "HVAC";
private static final String HOME_TYPE = "Home";
private static final String GROUP_TYPE = "Group";
private static final String MACRO_TYPE = "Macro";
private static final String SCENE_TYPE = "Scene";
private static final String IRDATA_TYPE = "IrData";
private HttpClient httpClient;
private NamedIP halAddress;
private String theToken;
public HalInfo(NamedIP addressName, String aGivenToken) {
super();
httpClient = HttpClients.createDefault();
halAddress = addressName;
theToken = aGivenToken;
}
public List<HalDevice> getLights() {
return getHalDevices(DEVICE_REQUEST + LIGHT_REQUEST + TOKEN_REQUEST, LIGHT_REQUEST);
}
public List<HalDevice> getAppliances() {
return getHalDevices(DEVICE_REQUEST + APPL_REQUEST + TOKEN_REQUEST, APPL_REQUEST);
}
public List<HalDevice> getTheatre() {
return getHalDevices(DEVICE_REQUEST + THEATRE_REQUEST + TOKEN_REQUEST, THEATRE_REQUEST);
}
public List<HalDevice> getCustom() {
return getHalDevices(DEVICE_REQUEST + CUSTOM_REQUEST + TOKEN_REQUEST, CUSTOM_REQUEST);
}
public List<HalDevice> getHVAC() {
return getHalDevices(HVAC_REQUEST + TOKEN_REQUEST, HVAC_TYPE);
}
public List<HalDevice> getGroups() {
return getHalDevices(GROUP_REQUEST + TOKEN_REQUEST, GROUP_TYPE);
}
public List<HalDevice> getMacros() {
return getHalDevices(MACRO_REQUEST + TOKEN_REQUEST, MACRO_TYPE);
}
public List<HalDevice> getScenes() {
return getHalDevices(SCENE_REQUEST + TOKEN_REQUEST, SCENE_TYPE);
}
public List<HalDevice> getButtons() {
List<HalDevice> irDataDevices = getHalDevices(IRDATA_REQUEST + TOKEN_REQUEST, IRDATA_TYPE);
return getDeviceButtons(irDataDevices);
}
public List<HalDevice> getHome(String theDeviceName) {
List<HalDevice> deviceList = null;
deviceList = new ArrayList<HalDevice>();
HalDevice aNewHalDevice = new HalDevice();
aNewHalDevice.setHaldevicetype(HOME_TYPE);
aNewHalDevice.setHaldevicename(theDeviceName);
deviceList.add(aNewHalDevice);
return deviceList;
}
private List<HalDevice> getHalDevices(String apiType, String deviceType) {
DeviceElements theHalApiResponse = null;
List<HalDevice> deviceList = null;
String theUrl = null;
String theData;
theUrl = "http://" + halAddress.getIp() + apiType + theToken;
theData = doHttpGETRequest(theUrl);
if(theData != null) {
log.debug("GET " + deviceType + " HalApiResponse - data: " + theData);
theHalApiResponse = new Gson().fromJson(theData, DeviceElements.class);
if(theHalApiResponse.getDeviceElements() == null) {
StatusDescription theStatus = new Gson().fromJson(theData, StatusDescription.class);
if(theStatus.getStatus() == null) {
log.warn("Cannot get an devices for type " + deviceType + " for hal " + halAddress.getName() + " as response is not parsable.");
}
else {
log.warn("Cannot get an devices for type " + deviceType + " for hal " + halAddress.getName() + ". Status: " + theStatus.getStatus() + ", with description: " + theStatus.getDescription());
}
return deviceList;
}
deviceList = new ArrayList<HalDevice>();
Iterator<DeviceName> theDeviceNames = theHalApiResponse.getDeviceElements().iterator();
while(theDeviceNames.hasNext()) {
DeviceName theDevice = theDeviceNames.next();
HalDevice aNewHalDevice = new HalDevice();
aNewHalDevice.setHaldevicetype(deviceType);
aNewHalDevice.setHaldevicename(theDevice.getDeviceName());
deviceList.add(aNewHalDevice);
}
}
else {
log.warn("Get Hal device types " + deviceType + " for " + halAddress.getName() + " - returned null, no data.");
}
return deviceList;
}
private List<HalDevice> getDeviceButtons(List<HalDevice> theIrDevices) {
DeviceElements theHalApiResponse = null;
List<HalDevice> deviceList = null;
String theUrl = null;
String theData;
if(theIrDevices == null)
return null;
Iterator<HalDevice> theHalDevices = theIrDevices.iterator();
deviceList = new ArrayList<HalDevice>();
while (theHalDevices.hasNext()) {
HalDevice theHalDevice = theHalDevices.next();
theUrl = "http://" + halAddress.getIp() + IRBUTTON_REQUEST + TextStringFormatter.forQuerySpaceUrl(theHalDevice.getHaldevicename()) + TOKEN_REQUEST + theToken;
theData = doHttpGETRequest(theUrl);
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) {
StatusDescription theStatus = new Gson().fromJson(theData, StatusDescription.class);
if (theStatus.getStatus() == null) {
log.warn("Cannot get buttons for IR Device " + theHalDevice.getHaldevicename() + " for hal "
+ halAddress.getName() + " as response is not parsable.");
} else {
log.warn("Cannot get buttons for IR Device " + theHalDevice.getHaldevicename() + " for hal "
+ halAddress.getName() + ". Status: " + theStatus.getStatus() + ", with description: "
+ theStatus.getDescription());
}
return deviceList;
}
theHalDevice.setButtons(theHalApiResponse);
deviceList.add(theHalDevice);
} else {
log.warn("Get Hal buttons for IR Device " + theHalDevice.getHaldevicename() + " for "
+ halAddress.getName() + " - returned null, no data.");
}
}
return deviceList;
}
// This function executes the url against the hal
protected String doHttpGETRequest(String url) {
String theContent = null;
log.debug("calling GET on URL: " + url);
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse response = httpClient.execute(httpGet);
log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
}
} catch (IOException e) {
log.error("doHttpGETRequest: Error calling out to HA gateway: " + e.getMessage());
}
return theContent;
}
public NamedIP getHalAddress() {
return halAddress;
}
public void setHalAddress(NamedIP halAddress) {
this.halAddress = halAddress;
}
}

View File

@@ -0,0 +1,18 @@
package com.bwssystems.hal;
public class StatusDescription {
private String Status;
private String Description;
public String getStatus() {
return Status;
}
public void setStatus(String status) {
Status = status;
}
public String getDescription() {
return Description;
}
public void setDescription(String description) {
Description = description;
}
}

View File

@@ -1,5 +1,7 @@
package com.bwssystems.harmony;
import java.io.UnsupportedEncodingException;
import net.whistlingfish.harmony.config.Activity;
public class HarmonyActivity {
@@ -15,6 +17,14 @@ public class HarmonyActivity {
return activity;
}
public void setActivity(Activity activity) {
byte ptext[];
String theLabel = activity.getLabel();
try {
ptext = theLabel.getBytes("ISO-8859-1");
activity.setLabel(new String(ptext, "UTF-8"));
} catch (UnsupportedEncodingException e) {
activity.setLabel(theLabel);
}
this.activity = activity;
}

View File

@@ -1,5 +1,7 @@
package com.bwssystems.harmony;
import java.io.UnsupportedEncodingException;
import net.whistlingfish.harmony.config.Device;
public class HarmonyDevice {
@@ -9,6 +11,14 @@ public class HarmonyDevice {
return device;
}
public void setDevice(Device device) {
byte ptext[];
String theLabel = device.getLabel();
try {
ptext = theLabel.getBytes("ISO-8859-1");
device.setLabel(new String(ptext, "UTF-8"));
} catch (UnsupportedEncodingException e) {
device.setLabel(theLabel);
}
this.device = device;
}
public String getHub() {

View File

@@ -119,4 +119,14 @@ public class HarmonyHandler {
return true;
}
public void shutdown() {
log.debug("Harmony api shutdown requested.");
if(devMode)
return;
harmonyClient.disconnect();
harmonyClient = null;
}
}

View File

@@ -10,7 +10,7 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.IpList;
import com.bwssystems.HABridge.NamedIP;
@@ -20,13 +20,15 @@ import net.whistlingfish.harmony.config.Device;
public class HarmonyHome {
private static final Logger log = LoggerFactory.getLogger(HarmonyHome.class);
private Map<String, HarmonyServer> hubs;
private Boolean isDevMode;
public HarmonyHome(BridgeSettings bridgeSettings) {
public HarmonyHome(BridgeSettingsDescriptor bridgeSettings) {
super();
isDevMode = Boolean.parseBoolean(System.getProperty("dev.mode", "false"));
hubs = new HashMap<String, HarmonyServer>();
if(!bridgeSettings.isValidHarmony() && !bridgeSettings.isDevMode())
if(!bridgeSettings.isValidHarmony() && !isDevMode)
return;
if(bridgeSettings.isDevMode()) {
if(isDevMode) {
NamedIP devModeIp = new NamedIP();
devModeIp.setIp("10.10.10.10");
devModeIp.setName("devMode");
@@ -40,7 +42,7 @@ public class HarmonyHome {
while(theList.hasNext()) {
NamedIP aHub = theList.next();
try {
hubs.put(aHub.getName(), HarmonyServer.setup(bridgeSettings, aHub));
hubs.put(aHub.getName(), HarmonyServer.setup(bridgeSettings, isDevMode, aHub));
} catch (Exception e) {
log.error("Cannot get harmony client (" + aHub.getName() + ") setup, Exiting with message: " + e.getMessage(), e);
return;
@@ -48,6 +50,16 @@ public class HarmonyHome {
}
}
public void shutdownHarmonyHubs() {
if(isDevMode)
return;
Iterator<String> keys = hubs.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
hubs.get(key).getMyHarmony().shutdown();
}
}
public HarmonyHandler getHarmonyHandler(String aName) {
HarmonyHandler aHandler = null;
if(aName == null || aName.equals("")) {

View File

@@ -7,7 +7,7 @@ import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.NamedIP;
import com.google.inject.Guice;
import com.google.inject.Injector;
@@ -26,7 +26,7 @@ public class HarmonyServer {
private DevModeResponse devResponse;
private OAReplyProvider dummyProvider;
private NamedIP myNameAndIP;
private Boolean isDevMode;
private Logger log = LoggerFactory.getLogger(HarmonyServer.class);
public HarmonyServer(NamedIP theHarmonyAddress) {
@@ -34,33 +34,35 @@ public class HarmonyServer {
myHarmony = null;
dummyProvider = null;
myNameAndIP = theHarmonyAddress;
isDevMode = false;
}
public static HarmonyServer setup(BridgeSettings bridgeSettings, NamedIP theHarmonyAddress) throws Exception {
if(!bridgeSettings.isValidHarmony() && !bridgeSettings.isDevMode()) {
public static HarmonyServer setup(BridgeSettingsDescriptor bridgeSettings, Boolean harmonyDevMode, NamedIP theHarmonyAddress) throws Exception {
if(!bridgeSettings.isValidHarmony() && harmonyDevMode) {
return new HarmonyServer(theHarmonyAddress);
}
Injector injector = null;
if(!bridgeSettings.isDevMode())
if(!harmonyDevMode)
injector = Guice.createInjector(new HarmonyClientModule());
HarmonyServer mainObject = new HarmonyServer(theHarmonyAddress);
if(!bridgeSettings.isDevMode())
if(!harmonyDevMode)
injector.injectMembers(mainObject);
mainObject.execute(bridgeSettings);
mainObject.execute(bridgeSettings, harmonyDevMode);
return mainObject;
}
private void execute(BridgeSettings mySettings) throws Exception {
private void execute(BridgeSettingsDescriptor mySettings, Boolean harmonyDevMode) throws Exception {
Boolean noopCalls = Boolean.parseBoolean(System.getProperty("noop.calls", "false"));
String modeString = "";
isDevMode = harmonyDevMode;
String modeString = "";
if(dummyProvider != null)
log.debug("something is very wrong as dummyProvider is not null...");
if(mySettings.isDevMode())
if(isDevMode)
modeString = " (development mode)";
else if(noopCalls)
modeString = " (no op calls to harmony)";
log.info("setup initiated " + modeString + "....");
if(mySettings.isDevMode())
if(isDevMode)
{
harmonyClient = null;
devResponse = new DevModeResponse();

View File

@@ -0,0 +1,35 @@
package com.bwssystems.hue;
import com.bwssystems.HABridge.api.hue.DeviceResponse;
public class HueDevice {
private DeviceResponse device;
private String huedeviceid;
private String hueaddress;
private String huename;
public DeviceResponse getDevice() {
return device;
}
public void setDevice(DeviceResponse adevice) {
this.device = adevice;
}
public String getHuedeviceid() {
return huedeviceid;
}
public void setHuedeviceid(String huedeviceid) {
this.huedeviceid = huedeviceid;
}
public String getHueaddress() {
return hueaddress;
}
public void setHueaddress(String ahueaddress) {
this.hueaddress = ahueaddress;
}
public String getHuename() {
return huename;
}
public void setHuename(String ahuename) {
this.huename = ahuename;
}
}

View File

@@ -0,0 +1,18 @@
package com.bwssystems.hue;
public class HueDeviceIdentifier {
private String ipAddress;
private String deviceId;
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
}

View File

@@ -0,0 +1,5 @@
package com.bwssystems.hue;
public interface HueErrorStringSet {
public void setErrorString(String anError);
}

View File

@@ -0,0 +1,73 @@
package com.bwssystems.hue;
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.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.hue.DeviceResponse;
import com.bwssystems.HABridge.api.hue.HueApiResponse;
public class HueHome {
private static final Logger log = LoggerFactory.getLogger(HueHome.class);
private Map<String, HueInfo> hues;
private String theHUERegisteredUser;
public HueHome(BridgeSettingsDescriptor bridgeSettings) {
hues = new HashMap<String, HueInfo>();
if(!bridgeSettings.isValidHue())
return;
Iterator<NamedIP> theList = bridgeSettings.getHueaddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aHue = theList.next();
hues.put(aHue.getName(), new HueInfo(aHue, this));
}
theHUERegisteredUser = null;
}
public List<HueDevice> getDevices() {
log.debug("consolidating devices for hues");
Iterator<String> keys = hues.keySet().iterator();
ArrayList<HueDevice> deviceList = new ArrayList<HueDevice>();
while(keys.hasNext()) {
String key = keys.next();
HueApiResponse theResponse = hues.get(key).getHueApiResponse();
if(theResponse != null) {
Map<String, DeviceResponse> theDevices = theResponse.getLights();
if(theDevices != null) {
Iterator<String> deviceKeys = theDevices.keySet().iterator();
while(deviceKeys.hasNext()) {
String theDeviceKey = deviceKeys.next();
HueDevice aNewHueDevice = new HueDevice();
aNewHueDevice.setDevice(theDevices.get(theDeviceKey));
aNewHueDevice.setHuedeviceid(theDeviceKey);
aNewHueDevice.setHueaddress(hues.get(key).getHueAddress().getIp());
aNewHueDevice.setHuename(key);
deviceList.add(aNewHueDevice);
}
}
else {
deviceList = null;
break;
}
}
else
log.warn("Cannot get lights for Hue with name: " + key);
}
return deviceList;
}
public String getTheHUERegisteredUser() {
return theHUERegisteredUser;
}
public void setTheHUERegisteredUser(String theHUERegisteredUser) {
this.theHUERegisteredUser = theHUERegisteredUser;
}
}

View File

@@ -0,0 +1,111 @@
package com.bwssystems.hue;
import java.io.IOException;
import java.nio.charset.Charset;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.hue.HueApiResponse;
import com.google.gson.Gson;
public class HueInfo implements HueErrorStringSet {
private static final Logger log = LoggerFactory.getLogger(HueInfo.class);
private HttpClient httpClient;
private NamedIP hueAddress;
private String theUser;
private HueHome theHueHome;
private String errorString = null;
public HueInfo(NamedIP addressName, HueHome aHueHome) {
super();
httpClient = HttpClients.createDefault();
hueAddress = addressName;
theUser = "habridge";
theHueHome = aHueHome;
}
public HueApiResponse getHueApiResponse() {
HueApiResponse theHueApiResponse = null;
String theUrl = "http://" + hueAddress.getIp() + HueUtil.HUE_REQUEST + "/" + theUser;
String theData;
boolean loopControl = true;
int retryCount = 0;
while(loopControl) {
if(retryCount > 3) {
log.warn("Max Retry reached to get Hue data from " + hueAddress.getName());
loopControl = false;
break;
}
theUrl = "http://" + hueAddress.getIp() + HueUtil.HUE_REQUEST + "/" + theUser;
theData = doHttpGETRequest(theUrl);
if(theData != null) {
log.debug("GET HueApiResponse - data: " + theData);
if(theData.contains("[{\"error\":")) {
if(theData.contains("unauthorized user")) {
theUser = HueUtil.registerWithHue(httpClient, hueAddress.getIp(), hueAddress.getName(), theHueHome.getTheHUERegisteredUser(), this);
if(theUser == null) {
log.warn("Register to Hue for " + hueAddress.getName() + " returned error: " + errorString);
return null;
}
else
theHueHome.setTheHUERegisteredUser(theUser);
retryCount++;
}
else {
log.warn("GET HueApiResponse for " + hueAddress.getName() + " - returned error: " + theData);
return null;
}
}
else {
theHueApiResponse = new Gson().fromJson(theData, HueApiResponse.class);
log.debug("GET HueApiResponse for " + hueAddress.getName() + " - Gson parse - name: " + theHueApiResponse.getConfig().getName() + ", mac addr: " + theHueApiResponse.getConfig().getMac());
loopControl = false;
}
}
else {
log.warn("GET HueApiResponse for " + hueAddress.getName() + " - returned null, no data.");
loopControl = false;
}
}
return theHueApiResponse;
}
// This function executes the url against the vera
protected String doHttpGETRequest(String url) {
String theContent = null;
log.debug("calling GET on URL: " + url);
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse response = httpClient.execute(httpGet);
log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
}
} catch (IOException e) {
log.error("doHttpGETRequest: Error calling out to HA gateway: " + e.getMessage());
}
return theContent;
}
public NamedIP getHueAddress() {
return hueAddress;
}
public void setHueAddress(NamedIP hueAddress) {
this.hueAddress = hueAddress;
}
@Override
public void setErrorString(String anError) {
errorString = anError;
}
}

View File

@@ -0,0 +1,55 @@
package com.bwssystems.hue;
import java.io.IOException;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.api.SuccessUserResponse;
import com.bwssystems.HABridge.api.UserCreateRequest;
import com.google.gson.Gson;
public class HueUtil {
private static final Logger log = LoggerFactory.getLogger(HueUtil.class);
public static final String HUE_REQUEST = "/api";
public static final String registerWithHue(HttpClient anHttpClient, String ipAddress, String aName, String theUser, HueErrorStringSet errorStringSet) {
UserCreateRequest theLogin = new UserCreateRequest();
theLogin.setDevicetype("HABridge#MyMachine");
HttpPost postRequest = new HttpPost("http://" + ipAddress + HUE_REQUEST);
ContentType parsedContentType = ContentType.parse("application/json");
StringEntity requestBody = new StringEntity(new Gson().toJson(theLogin), parsedContentType);
HttpResponse response = null;
postRequest.setEntity(requestBody);
try {
response = anHttpClient.execute(postRequest);
log.debug("POST execute on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
String theBody = EntityUtils.toString(response.getEntity());
log.debug("registerWithHue response data: " + theBody);
if(theBody.contains("[{\"error\":")) {
if(theBody.contains("link button not")) {
log.warn("registerWithHue needs link button pressed on HUE bridge: " + aName);
}
else
log.warn("registerWithHue returned an unexpected error: " + theBody);
errorStringSet.setErrorString(theBody);
}
else {
SuccessUserResponse[] theResponses = new Gson().fromJson(theBody, SuccessUserResponse[].class); //read content for data, SuccessUserResponse[].class);
theUser = theResponses[0].getSuccess().getUsername();
}
}
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
} catch (IOException e) {
log.warn("Error logging into HUE: IOException in log", e);
}
return theUser;
}
}

View File

@@ -0,0 +1,50 @@
/*
* http://www.jrecruiter.org
*
* Disclaimer of Warranty.
*
* Unless required by applicable law or agreed to in writing, Licensor provides
* the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
* including, without limitation, any warranties or conditions of TITLE,
* NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
* solely responsible for determining the appropriateness of using or
* redistributing the Work and assume any risks associated with Your exercise of
* permissions under this License.
*
*/
package com.bwssystems.logservices;
import java.util.Date;
/**
* Log file information.
*
*/
public class LogFileInfo {
private String fileName;
private Long fileSize;
private Date fileLastChanged;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Long getFileSize() {
return fileSize;
}
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
public Date getFileLastChanged() {
return fileLastChanged;
}
public void setFileLastChanged(Date fileLastChanged) {
this.fileLastChanged = fileLastChanged;
}
}

View File

@@ -0,0 +1,51 @@
package com.bwssystems.logservices;
import com.bwssystems.logservices.LoggingUtil.LogLevels;
/**
* Logger information.
*
*
*/
public class LoggerInfo {
private String loggerName;
private LogLevels logLevel;
private LogLevels newLogLevel;
//~~~~~Constructors~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
*
*/
public LoggerInfo() {
super();
}
public LoggerInfo(String loggerName, int logLevelAsInt) {
super();
this.loggerName = loggerName;
this.logLevel = LogLevels.getLogLevelFromId(logLevelAsInt);
}
//~~~~~Getters and Setters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public String getLoggerName() {
return loggerName;
}
public void setLoggerName(String loggerName) {
this.loggerName = loggerName;
}
public LogLevels getLogLevel() {
return logLevel;
}
public void setLogLevel(LogLevels logLevel) {
this.logLevel = logLevel;
}
public LogLevels getNewLogLevel() {
return newLogLevel;
}
public void setNewLogLevel(LogLevels newLogLevel) {
this.newLogLevel = newLogLevel;
}
}

View File

@@ -0,0 +1,58 @@
package com.bwssystems.logservices;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Form for the Logging Action.
*/
public class LoggingForm implements Serializable {
/** serialVersionUID. */
private static final long serialVersionUID = 5970927715241338665L;
/** List of Loggers, simplified for display purposes */
private List<LoggerInfo> updatedLoggers = new ArrayList<LoggerInfo>();
/** The user can enter a new logger to be configured */
private LoggerInfo newLogger;
/** Used for requesting a logfile download */
private String fileName;
//~~~~~Constructors~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public LoggingForm() {
super();
}
//~~~~~Getters and Setters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public List<LoggerInfo> getUpdatedLoggers() {
return updatedLoggers;
}
public void setUpdatedLoggers(List<LoggerInfo> updatedLoggers) {
this.updatedLoggers = updatedLoggers;
}
public LoggerInfo getNewLogger() {
return newLogger;
}
public void setNewLogger(LoggerInfo newLogger) {
this.newLogger = newLogger;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}

View File

@@ -0,0 +1,163 @@
package com.bwssystems.logservices;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import com.bwssystems.logservices.LoggingUtil;
import com.bwssystems.logservices.LoggingUtil.LogLevels;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
/**
* Show log files and allow to set log levels for the configured loggers (logback)
* to be changed dynamically at runtime.
*/
public class LoggingManager {
/** Show all loggers or only the configured loggers */
private boolean showAll = false;
/** List of log files and associated information */
private List<LogFileInfo> logFileInfos = new ArrayList<LogFileInfo>();
/** List of Loggers, simplified for display purposes */
private List<LoggerInfo> configuredLoggers = new ArrayList<LoggerInfo>();
/** Used to stream a logfile back to the client */
private transient InputStream fileToDownLoad;
private LoggingForm model = new LoggingForm();
//~~~~~Reference Data~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/** Defines a collection of log levels. Unfortunately logback does not use
* enums to define its log levels. */
private Set<LogLevels> logLevels = EnumSet.allOf(LogLevels.class);
//~~~~~Prepare data~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public void init() {
loadLoggers();
loadLogFiles();
}
//~~~~~Helper Methods~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private void loadLoggers() {
this.configuredLoggers.clear();
final List<Logger> loggers = LoggingUtil.getLoggers(this.showAll);
for (Logger logger : loggers) {
this.configuredLoggers.add(new LoggerInfo(logger.getName(), logger.getEffectiveLevel().levelInt));
}
}
private void loadLogFiles() {
this.logFileInfos = LoggingUtil.getLogFileInfos();
}
/** Updates loglevels for loggers */
public String updateLogLevels() {
if (this.model.getUpdatedLoggers() != null && !this.model.getUpdatedLoggers().isEmpty()) {
for (LoggerInfo loggerInfo : this.model.getUpdatedLoggers()) {
if (loggerInfo != null && loggerInfo.getNewLogLevel() != null) {
LoggingUtil.getLogger(loggerInfo.getLoggerName())
.setLevel(Level.toLevel(loggerInfo.getNewLogLevel().getLogLevel()));
}
}
//Need to refresh the loggers
loadLoggers();
}
return "successRedirect";
}
/** Adds a new logger at runtime. */
public String addNewLogger() {
if (this.model.getNewLogger() != null
&& this.model.getNewLogger().getLoggerName() != null
&& this.model.getNewLogger().getNewLogLevel() != null) {
final Logger newLogger = LoggingUtil.getLogger(this.model.getNewLogger().getLoggerName());
newLogger.setLevel(Level.toLevel(this.model.getNewLogger().getNewLogLevel().getLogLevel()));
//Need to refresh the loggers
loadLoggers();
}
return "successRedirect";
}
/**
* Retrieve the requested log file.
*
* @return
* @throws Exception
*/
public String download() throws Exception {
if (this.model.getFileName() == null) {
throw new IllegalArgumentException("FileName must not be null.");
}
final File logFile = LoggingUtil.getLogFile(this.model.getFileName());
if (logFile != null) {
this.fileToDownLoad = new FileInputStream(logFile);
}
return "download";
}
//~~~~~Getters and Setters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public List<LogFileInfo> getLogFileInfos() {
return logFileInfos;
}
public InputStream getFileToDownLoad() {
return fileToDownLoad;
}
public List<LoggerInfo> getConfiguredLoggers() {
return configuredLoggers;
}
public boolean isShowAll() {
return showAll;
}
public void setShowAll(boolean showAll) {
this.showAll = showAll;
}
public Set<LogLevels> getLogLevels() {
return logLevels;
}
public LoggingForm getModel() {
return model;
}
public void setModel(LoggingForm model) {
this.model = model;
}
}

View File

@@ -0,0 +1,198 @@
package com.bwssystems.logservices;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import com.bwssystems.logservices.LogFileInfo;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.FileAppender;
/**
* Contains utility methods to interact with the logback during runtime.
*/
public class LoggingUtil {
/**
* re-defines the logback logging levels as a Java enumeration. This is quite
* helpful if you need to render the various log-levels as select-box. I wish
* logback @see {@link ch.qos.logback.classic.Level} would not use static variables
* but use enums instead.
*/
public enum LogLevels {
OFF(Integer.MAX_VALUE, "Off"),
ERROR_INT(40000, "Error"),
WARN_INT(30000, "Warn"),
INFO_INT(20000, "Info"),
DEBUG_INT(10000, "Debug"),
TRACE(5000, "Trace"),
ALL(Integer.MIN_VALUE, "All");
private int logLevel;
private String logLevelName;
LogLevels(final int logLevel, final String logLevelName) {
this.logLevel = logLevel;
this.logLevelName = logLevelName;
}
public int getLogLevel() {
return this.logLevel;
}
public String getLogLevelName() {
return this.logLevelName;
}
public static LogLevels getLogLevelFromId(final int logLevelAsInt) {
for (LogLevels logLevel : LogLevels.values()) {
if (logLevelAsInt == logLevel.logLevel) {
return logLevel;
}
}
throw new IllegalStateException("Loglevel " + logLevelAsInt + " does not exist.");
}
@Override
public String toString() {
return this.logLevelName;
}
}
/**
* Retrieve all configured logback loggers.
*
* @param showAll If set return ALL loggers, not only the configured ones.
* @return List of Loggers
*/
public static List<ch.qos.logback.classic.Logger> getLoggers(final boolean showAll) {
final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
final List<ch.qos.logback.classic.Logger> loggers = new ArrayList<ch.qos.logback.classic.Logger>();
for (ch.qos.logback.classic.Logger log : lc.getLoggerList()) {
if(showAll == false) {
if(log.getLevel() != null || LoggingUtil.hasAppenders(log)) {
loggers.add(log);
}
} else {
loggers.add(log);
}
}
return loggers;
}
/**
* Get a single logger.
*
* @return Logger
*/
public static ch.qos.logback.classic.Logger getLogger(final String loggerName) {
final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
return lc.getLogger(loggerName);
}
/**
* Test whether the provided logger has appenders.
*
* @param logger The logger to test
* @return true if the logger has appenders.
*/
public static boolean hasAppenders(ch.qos.logback.classic.Logger logger) {
Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders();
return it.hasNext();
}
/**
* Get the logfile information for the roor logger.
*
* @return List of LogFileInfo obejcts
*/
public static List<LogFileInfo> getLogFileInfos() {
final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
final List<LogFileInfo> logFileInfos = new ArrayList<LogFileInfo>();
final Logger logger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
final Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders();
while (it.hasNext()) {
final Appender<ILoggingEvent> appender = it.next();
if (appender instanceof FileAppender) {
final FileAppender<ILoggingEvent> fileAppender = (FileAppender<ILoggingEvent>) appender;
final File logFile = new File(fileAppender.getFile());
final LogFileInfo logFileInfo = new LogFileInfo();
logFileInfo.setFileName(logFile.getName());
logFileInfo.setFileLastChanged(new Date(logFile.lastModified()));
logFileInfo.setFileSize(logFile.length());
logFileInfos.add(logFileInfo);
}
}
return logFileInfos;
}
/**
* Get the log file.
*
* @param logFileName The name of the log file
* @return The actual file
*/
public static File getLogFile(final String logFileName) {
if (logFileName == null) {
throw new IllegalArgumentException("logFileName cannot be null.");
}
final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
final Logger logger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
final Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders();
while (it.hasNext()) {
final Appender<ILoggingEvent> appender = it.next();
if (appender instanceof FileAppender) {
final FileAppender<ILoggingEvent> fileAppender = (FileAppender<ILoggingEvent>) appender;
final File logFile = new File(fileAppender.getFile());
if (logFile.getName().equalsIgnoreCase(logFileName)) {
return logFile;
}
}
}
return null;
}
}

View File

@@ -13,6 +13,8 @@ public class Device {
private String level;
private String state;
private String comment;
private String veraname;
private String veraaddress;
public String getName() {
return name;
}
@@ -79,5 +81,17 @@ public class Device {
public void setComment(String comment) {
this.comment = comment;
}
public String getVeraname() {
return veraname;
}
public void setVeraname(String veraname) {
this.veraname = veraname;
}
public String getVeraaddress() {
return veraaddress;
}
public void setVeraaddress(String veraaddress) {
this.veraaddress = veraaddress;
}
}

View File

@@ -5,6 +5,8 @@ public class Scene {
private String name;
private String id;
private String room;
private String veraname;
private String veraaddress;
public String getActive() {
return active;
}
@@ -29,5 +31,17 @@ public class Scene {
public void setRoom(String room) {
this.room = room;
}
public String getVeraname() {
return veraname;
}
public void setVeraname(String veraname) {
this.veraname = veraname;
}
public String getVeraaddress() {
return veraaddress;
}
public void setVeraaddress(String veraaddress) {
this.veraaddress = veraaddress;
}
}

View File

@@ -0,0 +1,95 @@
package com.bwssystems.util;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class BackupHandler {
final Logger log = LoggerFactory.getLogger(BackupHandler.class);
private Path repositoryPath;
private String fileExtension;
private String defaultName;
protected void setupParams(Path aPath, String anExtension, String adefaultName) {
repositoryPath = aPath;
if(anExtension.substring(0, 1).equalsIgnoreCase("."))
fileExtension = anExtension;
else
fileExtension = "." + anExtension;
defaultName = adefaultName;
log.debug("setupParams has defaultName: " + defaultName + " and file extension as: " + fileExtension);
}
public String backup(String aFilename) {
if(aFilename == null || aFilename.equalsIgnoreCase("")) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
aFilename = defaultName + dateFormat.format(Calendar.getInstance().getTime()) + fileExtension;
}
else
aFilename = aFilename + fileExtension;
try {
Files.copy(repositoryPath, FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException e) {
log.error("Could not backup to file: " + aFilename + " message: " + e.getMessage(), e);
}
log.debug("Backup repository: " + aFilename);
return aFilename;
}
public String deleteBackup(String aFilename) {
log.debug("Delete backup repository: " + aFilename);
try {
Files.delete(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename));
} catch (IOException e) {
log.error("Could not delete file: " + aFilename + " message: " + e.getMessage(), e);
}
return aFilename;
}
public String restoreBackup(String aFilename) {
log.debug("Restore backup repository: " + aFilename);
try {
Path target = null;
if(Files.exists(repositoryPath)) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
target = FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), defaultName + dateFormat.format(Calendar.getInstance().getTime()) + fileExtension);
Files.move(repositoryPath, target);
}
Files.copy(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), repositoryPath, StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException e) {
log.error("Error restoring the file: " + aFilename + " message: " + e.getMessage(), e);
return null;
}
return aFilename;
}
public List<String> getBackups() {
List<String> theFilenames = new ArrayList<String>();
Path dir = repositoryPath.getParent();
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(dir, "*.{"+ fileExtension.substring(1) + "}")) {
for (Path entry: stream) {
theFilenames.add(entry.getFileName().toString());
}
} catch (IOException x) {
// IOException can never be thrown by the iteration.
// In this snippet, it can // only be thrown by newDirectoryStream.
log.warn("Issue getting directory listing for backups in directory: " + x.getMessage());
}
return theFilenames;
}
}

View File

@@ -1,4 +1,4 @@
package com.bwssystems.HABridge;
package com.bwssystems.util;
import com.google.gson.Gson;
import spark.ResponseTransformer;

View File

@@ -0,0 +1,262 @@
package com.bwssystems.util;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
public final class TextStringFormatter {
private TextStringFormatter() {
// empty - prevent construction
}
/**
Escapes characters for text appearing as data in the
<a href='http://www.json.org/'>Javascript Object Notation</a>
(JSON) data interchange format.
<P>The following commonly used control characters are escaped :
<table border='1' cellpadding='3' cellspacing='0'>
<tr><th> Character </th><th> Escaped As </th></tr>
<tr><td> " </td><td> \" </td></tr>
<tr><td> \ </td><td> \\ </td></tr>
<tr><td> / </td><td> \/ </td></tr>
<tr><td> back space </td><td> \b </td></tr>
<tr><td> form feed </td><td> \f </td></tr>
<tr><td> line feed </td><td> \n </td></tr>
<tr><td> carriage return </td><td> \r </td></tr>
<tr><td> tab </td><td> \t </td></tr>
</table>
<P>See <a href='http://www.ietf.org/rfc/rfc4627.txt'>RFC 4627</a> for more information.
*/
public static String forJSON(String aText) {
final StringBuilder result = new StringBuilder();
StringCharacterIterator iterator = new StringCharacterIterator(aText);
char character = iterator.current();
while (character != StringCharacterIterator.DONE) {
if (character == '\"') {
result.append("\\\"");
} else if (character == '\\') {
result.append("\\\\");
} else if (character == '/') {
result.append("\\/");
} else if (character == '\b') {
result.append("\\b");
} else if (character == '\f') {
result.append("\\f");
} else if (character == '\n') {
result.append("\\n");
} else if (character == '\r') {
result.append("\\r");
} else if (character == '\t') {
result.append("\\t");
} else {
// the char is not a special one
// add it to the result as is
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
Escape characters for text appearing in HTML markup.
<P>This method exists as a defence against Cross Site Scripting (XSS) hacks.
The idea is to neutralize control characters commonly used by scripts, such that
they will not be executed by the browser. This is done by replacing the control
characters with their escaped equivalents.
See {@link hirondelle.web4j.security.SafeText} as well.
<P>The following characters are replaced with corresponding
HTML character entities :
<table border='1' cellpadding='3' cellspacing='0'>
<tr><th> Character </th><th>Replacement</th></tr>
<tr><td> < </td><td> &lt; </td></tr>
<tr><td> > </td><td> &gt; </td></tr>
<tr><td> & </td><td> &amp; </td></tr>
<tr><td> " </td><td> &quot;</td></tr>
<tr><td> \t </td><td> &#009;</td></tr>
<tr><td> ! </td><td> &#033;</td></tr>
<tr><td> # </td><td> &#035;</td></tr>
<tr><td> $ </td><td> &#036;</td></tr>
<tr><td> % </td><td> &#037;</td></tr>
<tr><td> ' </td><td> &#039;</td></tr>
<tr><td> ( </td><td> &#040;</td></tr>
<tr><td> ) </td><td> &#041;</td></tr>
<tr><td> * </td><td> &#042;</td></tr>
<tr><td> + </td><td> &#043; </td></tr>
<tr><td> , </td><td> &#044; </td></tr>
<tr><td> - </td><td> &#045; </td></tr>
<tr><td> . </td><td> &#046; </td></tr>
<tr><td> / </td><td> &#047; </td></tr>
<tr><td> : </td><td> &#058;</td></tr>
<tr><td> ; </td><td> &#059;</td></tr>
<tr><td> = </td><td> &#061;</td></tr>
<tr><td> ? </td><td> &#063;</td></tr>
<tr><td> @ </td><td> &#064;</td></tr>
<tr><td> [ </td><td> &#091;</td></tr>
<tr><td> \ </td><td> &#092;</td></tr>
<tr><td> ] </td><td> &#093;</td></tr>
<tr><td> ^ </td><td> &#094;</td></tr>
<tr><td> _ </td><td> &#095;</td></tr>
<tr><td> ` </td><td> &#096;</td></tr>
<tr><td> { </td><td> &#123;</td></tr>
<tr><td> | </td><td> &#124;</td></tr>
<tr><td> } </td><td> &#125;</td></tr>
<tr><td> ~ </td><td> &#126;</td></tr>
</table>
<P>Note that JSTL's {@code <c:out>} escapes <em>only the first
five</em> of the above characters.
*/
public static String forHTML(String aText) {
final StringBuilder result = new StringBuilder();
final StringCharacterIterator iterator = new StringCharacterIterator(aText);
char character = iterator.current();
while (character != CharacterIterator.DONE) {
if (character == '<') {
result.append("&lt;");
} else if (character == '>') {
result.append("&gt;");
} else if (character == '&') {
result.append("&amp;");
} else if (character == '\"') {
result.append("&quot;");
} else if (character == '\t') {
addCharEntity(9, result);
} else if (character == '!') {
addCharEntity(33, result);
} else if (character == '#') {
addCharEntity(35, result);
} else if (character == '$') {
addCharEntity(36, result);
} else if (character == '%') {
addCharEntity(37, result);
} else if (character == '\'') {
addCharEntity(39, result);
} else if (character == '(') {
addCharEntity(40, result);
} else if (character == ')') {
addCharEntity(41, result);
} else if (character == '*') {
addCharEntity(42, result);
} else if (character == '+') {
addCharEntity(43, result);
} else if (character == ',') {
addCharEntity(44, result);
} else if (character == '-') {
addCharEntity(45, result);
} else if (character == '.') {
addCharEntity(46, result);
} else if (character == '/') {
addCharEntity(47, result);
} else if (character == ':') {
addCharEntity(58, result);
} else if (character == ';') {
addCharEntity(59, result);
} else if (character == '=') {
addCharEntity(61, result);
} else if (character == '?') {
addCharEntity(63, result);
} else if (character == '@') {
addCharEntity(64, result);
} else if (character == '[') {
addCharEntity(91, result);
} else if (character == '\\') {
addCharEntity(92, result);
} else if (character == ']') {
addCharEntity(93, result);
} else if (character == '^') {
addCharEntity(94, result);
} else if (character == '_') {
addCharEntity(95, result);
} else if (character == '`') {
addCharEntity(96, result);
} else if (character == '{') {
addCharEntity(123, result);
} else if (character == '|') {
addCharEntity(124, result);
} else if (character == '}') {
addCharEntity(125, result);
} else if (character == '~') {
addCharEntity(126, result);
} else {
// the char is not a special one
// add it to the result as is
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
* Escape all ampersand characters in a URL.
*
* <P>
* Replaces all <tt>'&'</tt> characters with <tt>'&amp;'</tt>.
*
* <P>
* An ampersand character may appear in the query string of a URL. The
* ampersand character is indeed valid in a URL.
* <em>However, URLs usually appear as an <tt>HREF</tt> attribute, and such
* attributes have the additional constraint that ampersands must be
* escaped.</em>
*
* <P>
* The JSTL <c:url> tag does indeed perform proper URL encoding of query
* parameters. But it does not, in general, produce text which is valid as
* an <tt>HREF</tt> attribute, simply because it does not escape the
* ampersand character. This is a nuisance when multiple query parameters
* appear in the URL, since it requires a little extra work.
*/
public static String forHrefAmpersand(String aURL) {
return aURL.replace("&", "&amp;");
}
public static String forQuerySpace(String aURL) {
return aURL.replace(" ", "\u0020");
}
public static String forQuerySpaceUrl(String aURL) {
return aURL.replace(" ", "%20");
}
/**
* Synonym for <tt>URLEncoder.encode(String, "UTF-8")</tt>.
*
* <P>
* Used to ensure that HTTP query strings are in proper form, by escaping
* special characters such as spaces.
*
* <P>
* It is important to note that if a query string appears in an
* <tt>HREF</tt> attribute, then there are two issues - ensuring the query
* string is valid HTTP (it is URL-encoded), and ensuring it is valid HTML
* (ensuring the ampersand is escaped).
*/
public static String forURL(String aURLFragment) {
String result = null;
try {
result = URLEncoder.encode(aURLFragment, "UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("UTF-8 not supported", ex);
}
return result;
}
private static void addCharEntity(Integer aIdx, StringBuilder aBuilder) {
String padding = "";
if (aIdx <= 9) {
padding = "00";
} else if (aIdx <= 99) {
padding = "0";
} else {
// no prefix
}
String number = padding + aIdx.toString();
aBuilder.append("&#" + number + ";");
}
}

View File

@@ -0,0 +1,72 @@
package com.bwssystems.util;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UDPDatagramSender {
private Logger log = LoggerFactory.getLogger(UDPDatagramSender.class);
private DatagramSocket responseSocket = null;
private int udpResponsePort;
public UDPDatagramSender() {
super();
udpResponsePort = 0;
}
public static UDPDatagramSender createUDPDatagramSender(int udpResponsePort) {
UDPDatagramSender aDatagramSender = new UDPDatagramSender();
if(aDatagramSender.initializeSocket(udpResponsePort))
return aDatagramSender;
else
return null;
}
private boolean initializeSocket(int port) {
log.info("Initializing UDP response Seocket...");
udpResponsePort = port;
boolean portLoopControl = true;
int retryCount = 0;
while(portLoopControl) {
try {
responseSocket = new DatagramSocket(udpResponsePort);
portLoopControl = false;
} catch(SocketException e) {
if(retryCount == 0)
log.warn("UDP Response Port is in use, starting loop to find open port for 20 tries - configured port is: " + udpResponsePort);
if(retryCount >= 20) {
portLoopControl = false;
log.error("UDP Response Port issue, could not find open port - last port tried: " + udpResponsePort + " with message: " + e.getMessage());
return false;
}
}
if(portLoopControl) {
retryCount++;
udpResponsePort++;
}
}
log.info("UDP response Seocket initialized to: " + udpResponsePort);
return true;
}
public int getUdpResponsePort() {
return udpResponsePort;
}
public void closeResponseSocket() {
responseSocket.close();
}
public void sendUDPResponse(String udpResponse, InetAddress requester, int sourcePort) throws IOException {
log.debug("Sending response string: <<<" + udpResponse + ">>>");
if(responseSocket == null)
throw new IOException("Socket not initialized");
DatagramPacket response = new DatagramPacket(udpResponse.getBytes(), udpResponse.length(), requester, sourcePort);
responseSocket.send(response);
}
}

View File

@@ -0,0 +1,73 @@
package com.bwssystems.vera;
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.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Scene;
import com.bwssystems.luupRequests.Sdata;
public class VeraHome {
private static final Logger log = LoggerFactory.getLogger(VeraHome.class);
private Map<String, VeraInfo> veras;
public VeraHome(BridgeSettingsDescriptor bridgeSettings) {
veras = new HashMap<String, VeraInfo>();
if(!bridgeSettings.isValidVera())
return;
Iterator<NamedIP> theList = bridgeSettings.getVeraAddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aVera = theList.next();
veras.put(aVera.getName(), new VeraInfo(aVera));
}
}
public List<Device> getDevices() {
log.debug("consolidating devices for veras");
Iterator<String> keys = veras.keySet().iterator();
ArrayList<Device> deviceList = new ArrayList<Device>();
while(keys.hasNext()) {
String key = keys.next();
Sdata theSdata = veras.get(key).getSdata();
if(theSdata != null) {
Iterator<Device> devices = theSdata.getDevices().iterator();
while(devices.hasNext()) {
deviceList.add(devices.next());
}
}
else {
deviceList = null;
break;
}
}
return deviceList;
}
public List<Scene> getScenes() {
log.debug("consolidating scenes for veras");
Iterator<String> keys = veras.keySet().iterator();
ArrayList<Scene> sceneList = new ArrayList<Scene>();
while(keys.hasNext()) {
String key = keys.next();
Sdata theSdata = veras.get(key).getSdata();
if(theSdata != null) {
Iterator<Scene> scenes = theSdata.getScenes().iterator();
while(scenes.hasNext()) {
sceneList.add(scenes.next());
}
}
else {
sceneList = null;
break;
}
}
return sceneList;
}
}

View File

@@ -1,6 +1,7 @@
package com.bwssystems.vera;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.ListIterator;
import java.util.Map;
@@ -13,6 +14,7 @@ import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.luupRequests.Categorie;
import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Room;
@@ -25,27 +27,26 @@ public class VeraInfo {
private static final Logger log = LoggerFactory.getLogger(VeraInfo.class);
private HttpClient httpClient;
private static final String SDATA_REQUEST = ":3480/data_request?id=sdata&output_format=json";
private String veraAddressString;
private Boolean validVera;
private NamedIP veraAddress;
public VeraInfo(String addressString, Boolean isValidVera) {
public VeraInfo(NamedIP addressName) {
super();
httpClient = HttpClients.createDefault();
veraAddressString = addressString;
validVera = isValidVera;
veraAddress = addressName;
}
public Sdata getSdata() {
if(!validVera)
return new Sdata();
Sdata theSdata = null;
String theUrl = "http://" + veraAddressString + SDATA_REQUEST;
String theUrl = "http://" + veraAddress.getIp() + SDATA_REQUEST;
String theData;
theData = doHttpGETRequest(theUrl);
Sdata theSdata = new Gson().fromJson(theData, Sdata.class);
log.debug("GET sdata - full: " + theSdata.getFull() + ", version: " + theSdata.getVersion());
denormalizeSdata(theSdata);
if(theData != null) {
theSdata = new Gson().fromJson(theData, Sdata.class);
log.debug("GET sdata - full: " + theSdata.getFull() + ", version: " + theSdata.getVersion());
denormalizeSdata(theSdata);
}
return theSdata;
}
@@ -71,6 +72,8 @@ public class VeraInfo {
theDevice.setCategory(categoryMap.get(theDevice.getCategory()).getName());
else
theDevice.setCategory("<unknown>");
theDevice.setVeraaddress(veraAddress.getIp());
theDevice.setVeraname(veraAddress.getName());
}
ListIterator<Scene> theSecneIter = theSdata.getScenes().listIterator();
@@ -81,24 +84,26 @@ public class VeraInfo {
theScene.setRoom(roomMap.get(theScene.getRoom()).getName());
else
theScene.setRoom("no room");
theScene.setVeraaddress(veraAddress.getIp());
theScene.setVeraname(veraAddress.getName());
}
}
// This function executes the url against the vera
protected String doHttpGETRequest(String url) {
String theContent = null;
log.debug("calling GET on URL: " + url);
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse response = httpClient.execute(httpGet);
String theContent = EntityUtils.toString(response.getEntity()); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
return theContent;
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
}
} catch (IOException e) {
log.error("Error calling out to HA gateway", e);
log.error("doHttpGETRequest: Error calling out to HA gateway: " + e.getMessage());
}
return null;
return theContent;
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<appender name="CYCLIC" class="ch.qos.logback.core.read.CyclicBufferAppender">
<MaxSize>512</MaxSize>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="CYCLIC" />
</root>
</configuration>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
@-webkit-keyframes ngdialog-flyin{0%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes ngdialog-flyin{0%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes ngdialog-flyout{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}}@keyframes ngdialog-flyout{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}}.ngdialog.ngdialog-theme-default{padding-bottom:160px;padding-top:160px}.ngdialog.ngdialog-theme-default.ngdialog-closing .ngdialog-content{-webkit-animation:ngdialog-flyout .5s;animation:ngdialog-flyout .5s}.ngdialog.ngdialog-theme-default .ngdialog-content{-webkit-animation:ngdialog-flyin .5s;animation:ngdialog-flyin .5s;background:#f0f0f0;border-radius:5px;color:#444;font-family:Helvetica,sans-serif;font-size:1.1em;line-height:1.5em;margin:0 auto;max-width:100%;padding:1em;position:relative;width:450px}.ngdialog.ngdialog-theme-default .ngdialog-close{border-radius:5px;cursor:pointer;position:absolute;right:0;top:0}.ngdialog.ngdialog-theme-default .ngdialog-close:before{background:0 0;border-radius:3px;color:#bbb;content:'\00D7';font-size:26px;font-weight:400;height:30px;line-height:26px;position:absolute;right:3px;text-align:center;top:3px;width:30px}.ngdialog.ngdialog-theme-default .ngdialog-close:active:before,.ngdialog.ngdialog-theme-default .ngdialog-close:hover:before{color:#777}.ngdialog.ngdialog-theme-default .ngdialog-message{margin-bottom:.5em}.ngdialog.ngdialog-theme-default .ngdialog-input{margin-bottom:1em}.ngdialog.ngdialog-theme-default .ngdialog-input input[type=text],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=password],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=email],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=url],.ngdialog.ngdialog-theme-default .ngdialog-input textarea{background:#fff;border:0;border-radius:3px;font-family:inherit;font-size:inherit;font-weight:inherit;margin:0 0 .25em;min-height:2.5em;padding:.25em .67em;width:100%}.ngdialog.ngdialog-theme-default .ngdialog-input input[type=text]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=password]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=email]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=url]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input textarea:focus{box-shadow:inset 0 0 0 2px #8dbdf1;outline:0}.ngdialog.ngdialog-theme-default .ngdialog-buttons:after{content:'';display:table;clear:both}.ngdialog.ngdialog-theme-default .ngdialog-button{border:0;border-radius:3px;cursor:pointer;float:right;font-family:inherit;font-size:.8em;letter-spacing:.1em;line-height:1em;margin:0 0 0 .5em;padding:.75em 2em;text-transform:uppercase}.ngdialog.ngdialog-theme-default .ngdialog-button:focus{-webkit-animation:ngdialog-pulse 1.1s infinite;animation:ngdialog-pulse 1.1s infinite;outline:0}@media (max-width:568px){.ngdialog.ngdialog-theme-default .ngdialog-button:focus{-webkit-animation:none;animation:none}}.ngdialog.ngdialog-theme-default .ngdialog-button.ngdialog-button-primary{background:#3288e6;color:#fff}.ngdialog.ngdialog-theme-default .ngdialog-button.ngdialog-button-secondary{background:#e0e0e0;color:#777}

View File

@@ -0,0 +1 @@
.ngdialog,.ngdialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0}@-webkit-keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}@keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}.ngdialog{box-sizing:border-box;overflow:auto;-webkit-overflow-scrolling:touch;z-index:10000}.ngdialog *,.ngdialog :after,.ngdialog :before{box-sizing:inherit}.ngdialog.ngdialog-disabled-animation,.ngdialog.ngdialog-disabled-animation .ngdialog-content,.ngdialog.ngdialog-disabled-animation .ngdialog-overlay{-webkit-animation:none!important;animation:none!important}.ngdialog-overlay{background:rgba(0,0,0,.4);-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadein .5s;animation:ngdialog-fadein .5s}.ngdialog-no-overlay{pointer-events:none}.ngdialog.ngdialog-closing .ngdialog-overlay{-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadeout .5s;animation:ngdialog-fadeout .5s}.ngdialog-content{background:#fff;-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadein .5s;animation:ngdialog-fadein .5s;pointer-events:all}.ngdialog.ngdialog-closing .ngdialog-content{-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadeout .5s;animation:ngdialog-fadeout .5s}.ngdialog-close:before{font-family:Helvetica,Arial,sans-serif;content:'\00D7';cursor:pointer}body.ngdialog-open,html.ngdialog-open{overflow:hidden}

View File

@@ -0,0 +1,7 @@
/*!
* ngToast v1.5.6 (http://tameraydin.github.io/ngToast)
* Copyright 2015 Tamer Aydin (http://tamerayd.in)
* Licensed under MIT (http://tameraydin.mit-license.org/)
*/
.ng-toast{position:fixed;z-index:1080;width:100%;height:0;margin-top:20px;text-align:center}.ng-toast.ng-toast--top,.ng-toast.ng-toast--top .ng-toast__list{top:0;bottom:auto}.ng-toast.ng-toast--top.ng-toast--center .ng-toast__list{position:static}.ng-toast.ng-toast--bottom,.ng-toast.ng-toast--bottom .ng-toast__list{top:auto;bottom:0}.ng-toast.ng-toast--bottom.ng-toast--center .ng-toast__list{pointer-events:none}.ng-toast.ng-toast--bottom.ng-toast--center .ng-toast__message .alert{pointer-events:auto}.ng-toast.ng-toast--right .ng-toast__list{left:auto;right:0;margin-right:20px}.ng-toast.ng-toast--right .ng-toast__message{text-align:right}.ng-toast.ng-toast--left .ng-toast__list{right:auto;left:0;margin-left:20px}.ng-toast.ng-toast--left .ng-toast__message{text-align:left}.ng-toast .ng-toast__list{display:inline-block;position:absolute;right:0;left:0;margin:0 auto;padding:0;list-style:none}.ng-toast .ng-toast__message{display:block;width:100%;text-align:center}.ng-toast .ng-toast__message .alert{display:inline-block}.ng-toast .ng-toast__message__count{display:inline-block;margin:0 15px 0 5px}

View File

@@ -0,0 +1,2 @@
/*! angularjs-slider - v2.8.0 - (c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com> - https://github.com/angular-slider/angularjs-slider - 2016-02-08 */
rzslider{position:relative;display:inline-block;width:100%;height:4px;margin:35px 0 15px 0;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}rzslider[disabled]{cursor:not-allowed}rzslider[disabled] .rz-pointer{cursor:not-allowed;background-color:#d8e0f3}rzslider span{position:absolute;display:inline-block;white-space:nowrap}rzslider .rz-base{width:100%;height:100%;padding:0}rzslider .rz-bar-wrapper{left:0;z-index:1;width:100%;height:32px;padding-top:16px;margin-top:-16px;box-sizing:border-box}rzslider .rz-bar-wrapper.rz-draggable{cursor:move}rzslider .rz-bar{left:0;z-index:1;width:100%;height:4px;background:#d8e0f3;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}rzslider .rz-bar.rz-selection{z-index:2;background:#0db9f0;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}rzslider .rz-pointer{top:-14px;z-index:3;width:32px;height:32px;cursor:pointer;background-color:#0db9f0;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px}rzslider .rz-pointer:after{position:absolute;top:12px;left:12px;width:8px;height:8px;background:#fff;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;content:''}rzslider .rz-pointer:hover:after{background-color:#fff}rzslider .rz-pointer.rz-active{z-index:4}rzslider .rz-pointer.rz-active:after{background-color:#451aff}rzslider .rz-bubble{bottom:16px;padding:1px 3px;color:#55637d;cursor:default}rzslider .rz-bubble.rz-selection{top:16px}rzslider .rz-bubble.rz-limit{color:#55637d}rzslider .rz-ticks{position:absolute;top:-3px;left:0;z-index:1;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;height:0;padding:0 11px;margin:0;list-style:none;box-sizing:border-box;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}rzslider .rz-ticks .tick{width:10px;height:10px;text-align:center;cursor:pointer;background:#d8e0f3;border-radius:50%}rzslider .rz-ticks .tick.selected{background:#0db9f0}rzslider .rz-ticks .tick .tick-value{position:absolute;top:-30px;transform:translate(-50%,0)}rzslider.vertical{position:relative;width:4px;height:100%;padding:0;margin:0 20px;vertical-align:baseline}rzslider.vertical .rz-base{width:100%;height:100%;padding:0}rzslider.vertical .rz-bar-wrapper{top:auto;left:0;width:32px;height:100%;padding:0 0 0 16px;margin:0 0 0 -16px}rzslider.vertical .rz-bar{bottom:0;left:auto;width:4px;height:100%}rzslider.vertical .rz-pointer{top:auto;bottom:0;left:-14px!important}rzslider.vertical .rz-bubble{bottom:0;left:16px!important;margin-left:3px}rzslider.vertical .rz-bubble.rz-selection{top:auto;left:16px!important}rzslider.vertical .rz-ticks{top:0;left:-3px;z-index:1;width:0;height:100%;padding:11px 0;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}rzslider.vertical .rz-ticks .tick{vertical-align:middle}rzslider.vertical .rz-ticks .tick .tick-value{top:auto;right:-30px;transform:translate(0,-28%)}

View File

@@ -0,0 +1,128 @@
.scrollableContainer {
max-height: 436px; /* sets max-height value for all standards-compliant browsers */
position: relative;
padding-top: 35px;
overflow: hidden;
}
.scrollableContainer .headerSpacer {
border: 1px solid #d5d5d5;
border-bottom-color: #bbb;
position: absolute;
height: 36px;
top: 0;
right: 0;
left: 0;
}
.scrollableContainer th .orderWrapper {
position: absolute;
top: 0;
right: 2px;
cursor: pointer;
}
.scrollableContainer th .orderWrapper .order {
font-size: 8pt;
color: #BDBDBD;
}
.scrollableContainer th .orderWrapper .active {
color: #464646;
}
.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 */
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; */
}
.scrollArea table {
overflow-x: hidden;
overflow-y: auto;
margin-bottom: 0;
width: 100%;
border: none;
/*border-collapse: separate;*/
}
.scrollArea table th {
padding: 0 !important;
border: none !important;
min-width: 60px;
}
.scrollArea table .th-inner {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: absolute;
top: 0;
height: 36px;
line-height: 36px;
}
.scrollArea table th .box {
padding: 0 8px;
padding-right: 11px; /* order icon width*/
border-left: 1px solid #ddd;
}
/* to hack fix firefox border issue */
@-moz-document url-prefix() {
.scrollArea table th .box{
border-right: 1px solid #ddd;
border-left: none;
}
}
.scrollArea table .th-inner .ng-scope {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
.scrollArea table tr th:first-child th .box {
border-left: none;
}
.scrollArea table .th-inner.condensed {
padding: 0 3px;
}
.scrollArea table tbody tr td:first-child {
border-left: none;
}
.scrollArea table tbody tr td:last-child {
border-right: none;
}
.scrollArea table tbody tr:first-child td {
border-top: none;
}
.scrollArea table tbody tr:last-child td {
border-bottom: 2px solid #ddd;
}
.scrollArea table tbody tr td {
border-bottom: 1px solid #ddd;
overflow: hidden;
text-overflow: ellipsis;
}
.scrollableContainer .scaler {
position: absolute;
top: 0px;
width: 2px;
height: 100%;
background-color: #CFCFCF;
}
.scrollableContainer th .resize-rod {
position: absolute;
top: 0;
right: 0;
cursor: col-resize;
width: 4px;
height: 100%;
}
.scrollable-resizing .scrollableContainer {
cursor: col-resize;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}

View File

@@ -7,6 +7,12 @@
<title>HA Bridge</title>
<link href="css/main.css" rel="stylesheet">
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/bootstrap-theme.min.css" rel="stylesheet">
<link href="css/ngToast.min.css" rel="stylesheet">
<link href="css/rzslider.min.css" rel="stylesheet">
<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">
<!--[if lt IE 9]>
<script type="text/javascript" src="js/html5shiv.min.js"></script>
@@ -14,27 +20,27 @@
<![endif]-->
</head>
<body>
<toast></toast>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container" ng-controller="VersionController">
<div class="navbar-header">
<button type="button" class="navbar-toggle">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">HA Bridge</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="http://echo.amazon.com/#cards" target="_blank">My Echo</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="dropdownMenu1" 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="dropdownMenu1">
<a id="dLabel" 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>
@@ -50,11 +56,15 @@
</div>
<script src="js/jquery-1.11.3.min.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/angular-route.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/angular-sanitize.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/ngToast.min.js"></script>
<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="scripts/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
/*
AngularJS v1.4.3
(c) 2010-2015 Google, Inc. http://angularjs.org
License: MIT
*/
(function(n,h,p){'use strict';function E(a){var f=[];r(f,h.noop).chars(a);return f.join("")}function g(a,f){var d={},c=a.split(","),b;for(b=0;b<c.length;b++)d[f?h.lowercase(c[b]):c[b]]=!0;return d}function F(a,f){function d(a,b,d,l){b=h.lowercase(b);if(s[b])for(;e.last()&&t[e.last()];)c("",e.last());u[b]&&e.last()==b&&c("",b);(l=v[b]||!!l)||e.push(b);var m={};d.replace(G,function(b,a,f,c,d){m[a]=q(f||c||d||"")});f.start&&f.start(b,m,l)}function c(b,a){var c=0,d;if(a=h.lowercase(a))for(c=e.length-
1;0<=c&&e[c]!=a;c--);if(0<=c){for(d=e.length-1;d>=c;d--)f.end&&f.end(e[d]);e.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,e=[],m=a,l;for(e.last=function(){return e[e.length-1]};a;){l="";k=!0;if(e.last()&&w[e.last()])a=a.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*"+e.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");f.chars&&f.chars(q(b));return""}),c("",e.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",
b)===b&&(f.comment&&f.comment(a.substring(4,b)),a=a.substring(b+3),k=!1);else if(x.test(a)){if(b=a.match(x))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(y))a=a.substring(b[0].length),b[0].replace(y,c),k=!1}else K.test(a)&&((b=a.match(z))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(z,d)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),f.chars&&f.chars(q(l)))}if(a==m)throw L("badparse",a);m=a}c()}function q(a){if(!a)return"";A.innerHTML=
a.replace(/</g,"&lt;");return A.textContent}function B(a){return a.replace(/&/g,"&amp;").replace(M,function(a){var d=a.charCodeAt(0);a=a.charCodeAt(1);return"&#"+(1024*(d-55296)+(a-56320)+65536)+";"}).replace(N,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function r(a,f){var d=!1,c=h.bind(a,a.push);return{start:function(a,k,e){a=h.lowercase(a);!d&&w[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(k,function(d,e){var k=h.lowercase(e),g="img"===a&&"src"===k||
"background"===k;!0!==O[k]||!0===D[k]&&!f(d,g)||(c(" "),c(e),c('="'),c(B(d)),c('"'))}),c(e?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c("</"),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||c(B(a))}}}var L=h.$$minErr("$sanitize"),z=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,y=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^</,J=/^<\//,H=/\x3c!--(.*?)--\x3e/g,x=/<!DOCTYPE([^>]*?)>/i,
I=/<!\[CDATA\[(.*?)]]\x3e/g,M=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,N=/([^\#-~| |!])/g,v=g("area,br,col,hr,img,wbr");n=g("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr");p=g("rp,rt");var u=h.extend({},p,n),s=h.extend({},n,g("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),t=h.extend({},p,g("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
n=g("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,stop,svg,switch,text,title,tspan,use");var w=g("script,style"),C=h.extend({},v,s,t,u,n),D=g("background,cite,href,longdesc,src,usemap,xlink:href");n=g("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,valign,value,vspace,width");
p=g("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan",
!0);var O=h.extend({},D,p,n),A=document.createElement("pre");h.module("ngSanitize",[]).provider("$sanitize",function(){this.$get=["$$sanitizeUri",function(a){return function(f){var d=[];F(f,r(d,function(c,b){return!/^unsafe/.test(a(c,b))}));return d.join("")}}]});h.module("ngSanitize").filter("linky",["$sanitize",function(a){var f=/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,d=/^mailto:/i;return function(c,b){function k(a){a&&g.push(E(a))}function e(a,
c){g.push("<a ");h.isDefined(b)&&g.push('target="',b,'" ');g.push('href="',a.replace(/"/g,"&quot;"),'">');k(c);g.push("</a>")}if(!c)return c;for(var m,l=c,g=[],n,p;m=l.match(f);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),e(n,m[0].replace(d,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular);
//# sourceMappingURL=angular-sanitize.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,6 @@
/*!
* ngToast v1.5.6 (http://tameraydin.github.io/ngToast)
* Copyright 2015 Tamer Aydin (http://tamerayd.in)
* Licensed under MIT (http://tameraydin.mit-license.org/)
*/
!function(a,b,c){"use strict";b.module("ngToast.provider",[]).provider("ngToast",[function(){function a(a){for(var d=Math.floor(1e3*Math.random());c.indexOf(d)>-1;)d=Math.floor(1e3*Math.random());this.id=d,this.count=0,this.animation=e.animation,this.className=e.className,this.additionalClasses=e.additionalClasses,this.dismissOnTimeout=e.dismissOnTimeout,this.timeout=e.timeout,this.dismissButton=e.dismissButton,this.dismissButtonHtml=e.dismissButtonHtml,this.dismissOnClick=e.dismissOnClick,this.compileContent=e.compileContent,b.extend(this,a)}var c=[],d=[],e={animation:!1,className:"success",additionalClasses:null,dismissOnTimeout:!0,timeout:4e3,dismissButton:!1,dismissButtonHtml:"&times;",dismissOnClick:!0,compileContent:!1,combineDuplications:!1,horizontalPosition:"right",verticalPosition:"top",maxNumber:0};this.configure=function(a){b.extend(e,a)},this.$get=[function(){var b=function(a,b){return b="object"==typeof b?b:{content:b},b.className=a,this.create(b)};return{settings:e,messages:c,dismiss:function(a){if(a){for(var b=c.length-1;b>=0;b--)if(c[b].id===a)return c.splice(b,1),void d.splice(d.indexOf(a),1)}else{for(;c.length>0;)c.pop();d=[]}},create:function(b){if(b="object"==typeof b?b:{content:b},e.combineDuplications)for(var f=d.length-1;f>=0;f--){var g=c[f],h=b.className||"success";if(g.content===b.content&&g.className===h)return void c[f].count++}e.maxNumber>0&&d.length>=e.maxNumber&&this.dismiss(d[0]);var i=new a(b);return"bottom"===e.verticalPosition?c.unshift(i):c.push(i),d.push(i.id),i.id},success:function(a){return b.call(this,"success",a)},info:function(a){return b.call(this,"info",a)},warning:function(a){return b.call(this,"warning",a)},danger:function(a){return b.call(this,"danger",a)}}}]}])}(window,window.angular),function(a,b){"use strict";b.module("ngToast.directives",["ngToast.provider"]).run(["$templateCache",function(a){a.put("ngToast/toast.html",'<div class="ng-toast ng-toast--{{hPos}} ng-toast--{{vPos}} {{animation ? \'ng-toast--animate-\' + animation : \'\'}}"><ul class="ng-toast__list"><toast-message ng-repeat="message in messages" message="message" count="message.count"><span ng-bind-html="message.content"></span></toast-message></ul></div>'),a.put("ngToast/toastMessage.html",'<li class="ng-toast__message {{message.additionalClasses}}"ng-mouseenter="onMouseEnter()"ng-mouseleave="onMouseLeave()"><div class="alert alert-{{message.className}}" ng-class="{\'alert-dismissible\': message.dismissButton}"><button type="button" class="close" ng-if="message.dismissButton" ng-bind-html="message.dismissButtonHtml" ng-click="!message.dismissOnClick && dismiss()"></button><span ng-if="count" class="ng-toast__message__count">{{count + 1}}</span><span ng-if="!message.compileContent" ng-transclude></span></div></li>')}]).directive("toast",["ngToast","$templateCache","$log",function(a,b,c){return{replace:!0,restrict:"EA",templateUrl:"ngToast/toast.html",compile:function(d,e){if(e.template){var f=b.get(e.template);f?d.replaceWith(f):c.warn("ngToast: Provided template could not be loaded. Please be sure that it is populated before the <toast> element is represented.")}return function(b){b.hPos=a.settings.horizontalPosition,b.vPos=a.settings.verticalPosition,b.animation=a.settings.animation,b.messages=a.messages}}}}]).directive("toastMessage",["$timeout","$compile","ngToast",function(a,b,c){return{replace:!0,transclude:!0,restrict:"EA",scope:{message:"=",count:"="},controller:["$scope","ngToast",function(a,b){a.dismiss=function(){b.dismiss(a.message.id)}}],templateUrl:"ngToast/toastMessage.html",link:function(d,e,f,g,h){e.attr("data-message-id",d.message.id);var i,j=d.message.compileContent;if(d.cancelTimeout=function(){a.cancel(i)},d.startTimeout=function(){d.message.dismissOnTimeout&&(i=a(function(){c.dismiss(d.message.id)},d.message.timeout))},d.onMouseEnter=function(){d.cancelTimeout()},d.onMouseLeave=function(){d.startTimeout()},j){var k;h(d,function(a){k=a,e.children().append(k)}),a(function(){b(k.contents())("boolean"==typeof j?d.$parent:j,function(a){k.replaceWith(a)})},0)}d.startTimeout(),d.message.dismissOnClick&&e.bind("click",function(){c.dismiss(d.message.id),d.$apply()})}}}])}(window,window.angular),function(a,b){"use strict";b.module("ngToast",["ngSanitize","ngToast.directives","ngToast.provider"])}(window,window.angular);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,131 +1,136 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="active"><a href="#">Configuration</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"><a href="#/editor">Manual Add</a></li>
</ul>
<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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div ng-controller="ErrorsController">
<div ng-if="bridge.error"
class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h2 ng-show='bridge.error != ""'>ERROR</h2>
<div ng-show='bridge.error != ""'>{{bridge.error}}</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Current devices
({{bridge.devices.length}})</h2>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Current devices</h2>
</div>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('id')">ID</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span></th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span></th>
<th>
<a href="" ng-click="order('deviceType')">Type</a>
<span class="sortorder" ng-show="predicate === 'deviceType'" ng-class="{reverse:reverse}"></span></th>
<th>
<a href="" ng-click="order('targetDevice')">Target</a>
<span class="sortorder" ng-show="predicate === 'targetDevice'" ng-class="{reverse:reverse}"></span></th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices | orderBy:predicate:reverse">
<td>{{device.id}}</td>
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>
<form name="form">
<p>
<button class="btn btn-primary" type="submit" ng-click="renumberDevices()">Renumber Devices</button>
</p>
</form>
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="id">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</button>
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 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"></a>
</h1>
</div>
<div ng-if="visibleBk" class="animate-if" 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>
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h1 class="panel-title">Bridge settings <a ng-click="toggle()"><span class={{imgUrl}} aria-hidden="true"></a></h1>
<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 ng-if="visible" class="animate-if" class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="bridge-base">Bridge
server</label>
<div class="col-xs-8 col-sm-7">
<input id="bridge-base" class="form-control" type="text"
ng-model="bridge.base" placeholder="URL to bridge">
</div>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="setBridgeUrl(bridge.base)">Load</button>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="goBridgeUrl(bridge.base)">Go</button>
</div>
</form>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Setting</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td>upnp.config.address</td>
<td>{{BridgeSettings.upnpconfigaddress}}</td>
</tr>
<tr>
<td>server.port</td>
<td>{{BridgeSettings.serverport}}</td>
</tr>
<tr>
<td>upnp.devices.db</td>
<td>{{BridgeSettings.upnpdevicedb}}</td>
</tr>
<tr>
<td>upnp.response.port</td>
<td>{{BridgeSettings.upnpresponseport}}</td>
</tr>
<tr>
<td>vera.address</td>
<td>{{BridgeSettings.veraaddress}}</td>
</tr>
<tr>
<td>harmony.address</td>
<td>{{BridgeSettings.harmonyaddress}}</td>
</tr>
<tr>
<td>upnp.strict</td>
<td>{{BridgeSettings.upnpstrict}}</td>
</tr>
<tr>
<td>trace.upnp</td>
<td>{{BridgeSettings.traceupnp}}</td>
</tr>
<tr>
<td>dev.mode</td>
<td>{{BridgeSettings.devmode}}</td>
</tr>
</table>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-primary" ng-click="setValue()">Set</button>
</div>
</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

@@ -1,152 +1,256 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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"><a href="#/editor">Manual Add</a></li>
<li role="presentation" class="active"><a href="#/editdevice">Edit Device</a></li>
</ul>
<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 role="presentation"><a href="#/editor">Manual Add</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li role="presentation" class="active"><a href="#/editdevice">Edit
Device</a></li>
</ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Edit a device</h2>
<h2 class="panel-title">Edit/Copy a device</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="panel-body">
<p class="text-muted">This screen allows the modification of many
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>When copying, update the name and select the "Add Bridge
Device" Button.</p>
</div>
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary">
Update Device</button>
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-target">Target
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-success"
ng-click="addDevice()">Update Bridge Device</button>
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-target">Target
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-target"
ng-model="device.targetDevice" placeholder="default">
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-map-type">Map Type
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-target"
ng-model="device.targetDevice" placeholder="default">
</div>
<button class="btn btn-primary" ng-click="copyDevice()">Add
Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-type">Device
Type </label>
<div class="col-xs-8 col-sm-7">
<select name="device-map-type" id="device-map-type" ng-model="device.mapType">
<option value="">---Please select---</option> <!-- not selected / blank option -->
<option value="veraDevice">Vera Device</option>
<option value="veraScene">Vera Scene</option>
<option value="harmonyActivity">Harmony Activity</option>
<option value="harmonyButton">Harmony Button</option>
</select>
</div>
</div>
<div class="col-xs-8 col-sm-7">
<select name="device-type" id="device-type"
ng-model="device.deviceType">
<option value="">---Types if needed---</option>
<!-- not selected / blank option -->
<option value="custom">Custom</option>
<option value="UDP">UDP</option>
<option value="TCP">TCP</option>
<option value="exec">Execute Script/Program</option>
<option value="switch">Switch</option>
<option value="scene">Scene</option>
<option value="macro">Macro</option>
<option value="group">Group</option>
<option value="activity">Activity</option>
<option value="button">Button</option>
<option value="thermo">Thermo</option>
<option value="passthru">Pass Thru</option>
</select>
</div>
<div ng-if="device.mapType" class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-map-id">Map ID
</label>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-map-type">Map Type </label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-map-id"
ng-model="device.mapId" placeholder="1111">
</div>
<div class="col-xs-8 col-sm-7">
<select name="device-map-type" id="device-map-type"
ng-model="device.mapType">
<option value="">---Please select---</option>
<!-- not selected / blank option -->
<option value="veraDevice">Vera Device</option>
<option value="veraScene">Vera Scene</option>
<option value="harmonyActivity">Harmony Activity</option>
<option value="harmonyButton">Harmony Button</option>
<option value="nestHomeAway">Nest Home Status</option>
<option value="nestThermoSet">Nest Thermostat</option>
<option value="hueDevice">Hue Device</option>
<option value="halDevice">HAL Device</option>
<option value="halHome">HAL Home Status</option>
<option value="halThermoSet">HAL Thermostat</option>
</select>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-unique-id">Unique Id (used for Hue responses) </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-unique-id"
ng-model="device.uniqueid" placeholder="AA:BB:CC:DD:EE:FF-XX" readonly>
</div>
</div>
<div ng-if="device.mapType" class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-map-id">Map
ID </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-http-verb">Http Verb
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-map-id"
ng-model="device.mapId" placeholder="1111">
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<div class="col-xs-8 col-sm-7">
<select name="device-http-verb" id="device-http-verb" ng-model="device.httpVerb">
<option value="">---Please select---</option> <!-- not selected / blank option -->
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="POST">POST</option>
</select>
</div>
</div>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-content-type">Content Type
</label>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-dim-url">Dim
URL </label>
<div class="col-xs-8 col-sm-7">
<select name="device-content-type" id="device-content-type" ng-model="device.contentType">
<option value="">---Please select---</option> <!-- not selected / blank option -->
<option value="application/atom+xml">application/atom+xml</option>
<option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>
<option value="application/json">application/json</option>
<option value="application/octet-stream">application/octet-stream</option>
<option value="application/svg+xml">application/svg+xml</option>
<option value="application/xhtml+xml">application/xhtml+xml</option>
<option value="application/xml">application/xml</option>
<option value="*">*</option>
<option value="multipart/form-data">multipart/form-data</option>
<option value="text/html">text/html</option>
<option value="text/plain">text/plain</option>
<option value="text/xml">text/xml</option>
<option value="*/*">*/*</option>
</select>
</div>
</div>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-dim-url"
ng-model="device.dimUrl" placeholder="URL to dim device"></textarea>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body">Content Body On</label>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-off-url">Off
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-content-body"
ng-model="device.contentBody" placeholder="Content Body On for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body-off">Content Body Off</label>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-headers">HTTP
Headers </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-content-body-off"
ng-model="device.contentBodyOff" placeholder="Content Body Off for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-headers"
ng-model="device.headers"
placeholder="format like: [{&quot;name&quot;:&quot;A name&quot;,&quot;value&quot;:&quot;a value&quot;}]"></textarea>
</div>
</form>
</li>
</ul>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-http-verb">Http Verb </label>
<div class="col-xs-8 col-sm-7">
<select name="device-http-verb" id="device-http-verb"
ng-model="device.httpVerb">
<option value="">---Please select---</option>
<!-- not selected / blank option -->
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="POST">POST</option>
</select>
</div>
</div>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-type">Content Type </label>
<div class="col-xs-8 col-sm-7">
<select name="device-content-type" id="device-content-type"
ng-model="device.contentType">
<option value="">---Please select---</option>
<!-- not selected / blank option -->
<option value="application/atom+xml">application/atom+xml</option>
<option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>
<option value="application/json">application/json</option>
<option value="application/octet-stream">application/octet-stream</option>
<option value="application/svg+xml">application/svg+xml</option>
<option value="application/xhtml+xml">application/xhtml+xml</option>
<option value="application/xml">application/xml</option>
<option value="*">*</option>
<option value="multipart/form-data">multipart/form-data</option>
<option value="text/html">text/html</option>
<option value="text/plain">text/plain</option>
<option value="text/xml">text/xml</option>
<option value="*/*">*/*</option>
</select>
</div>
</div>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body">Content Body On</label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-content-body"
ng-model="device.contentBody"
placeholder="Content Body On for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body-dim">Content Body Dim</label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-content-body-dim"
ng-model="device.contentBodyDim"
placeholder="Content Body Dim for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body-off">Content Body Off</label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control"
id="device-content-body-off" ng-model="device.contentBodyOff"
placeholder="Content Body Off for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
</div>
</form>
</div>

View File

@@ -1,181 +1,263 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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="#/editor">Manual Add</a></li>
</ul>
<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="#/editor">Manual
Add</a></li>
</ul>
<div class="panel panel-default bridgeServer" ng-if="bridge.showVera">
<div ng-if="bridge.showVera" class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Generate a new device/scene/control point</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<p class="text-muted">You can generate on/off URLs by filling in
the Vera server URL, port, and device ID; or you can fill them out
manually in the lower section.</p>
<div class="panel-body">
<p class="text-muted">You can generate on/off URLs by filling in
the Vera server URL, port, and device ID; or you can fill them out
manually in the lower section.</p>
</div>
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="vera-base">Vera
Server URL </label>
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="vera-base">Vera
Server URL </label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="vera-base"
ng-model="vera.base"
placeholder="Vera URL (e.g. http://192.168.1.100)">
</div>
</div>
<div class="form-group">
<label class="col-xs-2 col-sm-2 control-label" for="vera-port">Vera
Request Port </label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="vera-base"
ng-model="vera.base"
placeholder="Vera URL (e.g. http://192.168.1.100)">
</div>
</div>
<div class="form-group">
<label class="col-xs-2 col-sm-2 control-label" for="vera-port">Vera
Request Port </label>
<div class="col-xs-10 col-sm-2">
<input type="text" class="form-control" id="vera-port"
ng-model="vera.port" placeholder="Vera Port (typically 3480)">
</div>
<div class="col-xs-10 col-sm-2">
<input type="text" class="form-control" id="vera-port"
ng-model="vera.port" placeholder="Vera Port (typically 3480)">
</div>
<label class="col-xs-2 col-sm-2 control-label" for="vera-id">Device
ID </label>
<label class="col-xs-2 col-sm-2 control-label" for="vera-id">Device
ID </label>
<div class="col-xs-10 col-sm-2">
<input type="text" class="form-control" id="vera-id"
ng-model="vera.id" placeholder="ID">
</div>
</div>
<div class="form-group">
<label class="col-xs-2 col-sm-2 control-label"
for="device-dim-control">Device Dim Control</label>
<div class="col-xs-10 col-sm-2">
<input type="text" class="form-control" id="vera-id"
ng-model="vera.id" placeholder="ID">
</div>
</div>
<div class="form-group">
<label class="col-xs-2 col-sm-2 control-label" for="device-dim-control">Device Dim Control</label>
<div class="col-xs-10 col-sm-2">
<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>
</div>
</div>
<div class="form-group">
<button type="submit" ng-click="buildUrlsUsingDevice(device_dim_control)"
class="col-xs-2 col-sm-2 col-xs-offset-2 col-sm-offset-2 btn btn-success">Generate Device
URLs</button>
<button type="submit" ng-click="buildUrlsUsingScene()"
class="col-xs-2 col-sm-2 col-xs-offset-2 col-sm-offset-2 btn btn-success">Generate Scene
URLs</button>
</div>
</form>
</li>
</ul>
<div class="col-xs-10 col-sm-2">
<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>
</div>
</div>
<div class="form-group">
<button type="submit"
ng-click="buildUrlsUsingDevice(device_dim_control)"
class="col-xs-2 col-sm-2 col-xs-offset-2 col-sm-offset-2 btn btn-success">Generate
Device URLs</button>
<button type="submit" ng-click="buildUrlsUsingScene()"
class="col-xs-2 col-sm-2 col-xs-offset-2 col-sm-offset-2 btn btn-success">Generate
Scene URLs</button>
</div>
</form>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a new device</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()">
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="panel-body">
<p class="text-muted">This area allows you to create any http 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. Currently, https is not supported. For Execution of a
script or program, plese fill in the path. All manually entered calls
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
also work.</p>
</div>
<form class="form-horizontal">
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary">
Add Device</button>
</div>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-type">Device
Type </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
</div>
<div class="col-xs-8 col-sm-7">
<select name="device-type" id="device-type"
ng-model="device.deviceType">
<option value="">---Types if needed---</option>
<!-- not selected / blank option -->
<option value="custom">Custom</option>
<option value="UDP">UDP</option>
<option value="TCP">TCP</option>
<option value="exec">Execute Script/Program</option>
</select>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-http-verb">Http Verb
</label>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-dim-url">Dim
URL </label>
<div class="col-xs-8 col-sm-7">
<select name="device-http-verb" id="device-http-verb" ng-model="device.httpVerb">
<option value="">---Please select---</option> <!-- not selected / blank option -->
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="POST">POST</option>
</select>
</div>
</div>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-dim-url"
ng-model="device.dimUrl" placeholder="URL to dim device"></textarea>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-content-type">Content Type
</label>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-off-url">Off
URL </label>
<div class="col-xs-8 col-sm-7">
<select name="device-content-type" id="device-content-type" ng-model="device.contentType">
<option value="">---Please select---</option> <!-- not selected / blank option -->
<option value="application/atom+xml">application/atom+xml</option>
<option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>
<option value="application/json">application/json</option>
<option value="application/octet-stream">application/octet-stream</option>
<option value="application/svg+xml">application/svg+xml</option>
<option value="application/xhtml+xml">application/xhtml+xml</option>
<option value="application/xml">application/xml</option>
<option value="*">*</option>
<option value="multipart/form-data">multipart/form-data</option>
<option value="text/html">text/html</option>
<option value="text/plain">text/plain</option>
<option value="text/xml">text/xml</option>
<option value="*/*">*/*</option>
</select>
</div>
</div>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body">Content Body On</label>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-headers">HTTP
Headers </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-content-body"
ng-model="device.contentBody" placeholder="Content Body On for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-headers"
ng-model="device.headers"
placeholder="format like: [{&quot;name&quot;:&quot;A name&quot;,&quot;value&quot;:&quot;a value&quot;}]"></textarea>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body-off">Content Body Off</label>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-http-verb">Http Verb </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-content-body-off"
ng-model="device.contentBodyOff" placeholder="Content Body Off for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
<div class="col-xs-8 col-sm-7">
<select name="device-http-verb" id="device-http-verb"
ng-model="device.httpVerb">
<option value="">---Please select---</option>
<!-- not selected / blank option -->
<option value="GET">GET</option>
<option value="PUT">PUT</option>
<option value="POST">POST</option>
</select>
</div>
</form>
</li>
</ul>
</div>
</div>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-type">Content Type </label>
<div class="col-xs-8 col-sm-7">
<select name="device-content-type" id="device-content-type"
ng-model="device.contentType">
<option value="">---Please select---</option>
<!-- not selected / blank option -->
<option value="application/atom+xml">application/atom+xml</option>
<option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>
<option value="application/json">application/json</option>
<option value="application/octet-stream">application/octet-stream</option>
<option value="application/svg+xml">application/svg+xml</option>
<option value="application/xhtml+xml">application/xhtml+xml</option>
<option value="application/xml">application/xml</option>
<option value="*">*</option>
<option value="multipart/form-data">multipart/form-data</option>
<option value="text/html">text/html</option>
<option value="text/plain">text/plain</option>
<option value="text/xml">text/xml</option>
<option value="*/*">*/*</option>
</select>
</div>
</div>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body">Content Body On</label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-content-body"
ng-model="device.contentBody"
placeholder="Content Body On for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body-dim">Content Body Dim</label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-content-body-dim"
ng-model="device.contentBodyDim"
placeholder="Content Body Dim for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
</div>
<div ng-if="device.httpVerb" class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-content-body-off">Content Body Off</label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control"
id="device-content-body-off" ng-model="device.contentBodyOff"
placeholder="Content Body Off for specific GET/PUT/POST type"></textarea>
</div>
<div class="clearfix visible-xs"></div>
</div>
</div>
</form>
</div>

View File

@@ -0,0 +1,217 @@
<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.showHAL" role="presentation"><a href="#/HAL">HAL</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 role="presentation"><a href="#/editor">Manual Add</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 action buttons
to generate the device addition information below automatically. Then
you can modify the name to anything you want that will be the keyword
for Alexa. 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>
</div>
<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>Actions</th>
</tr>
</thead>
<tr ng-repeat="haldevice in bridge.haldevices | availableHalDeviceId">
<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)">Generate
Bridge Device</button>
<button ng-if="haldevice.haldevicetype == 'Home'" class="btn btn-success" type="submit"
ng-click="buildHALHomeUrls(haldevice)">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 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>
<scrollable-table ng-if="buttonsVisible" 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>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | unavailableHalDeviceId">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(device.mapId, device.mapType)">Delete</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add Bridge Device for a HAL Device</h2>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-dim-url">Dim URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-dim-url"
ng-model="device.dimUrl" placeholder="URL to dim device"></textarea>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</form>
</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,106 +1,113 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Harmony Activity List</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<p class="text-muted">You can select a Harmony Activity and generate
the add activity box selections automatically.</p>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="harmonyactivity in bridge.harmonyactivities | availableHarmonyActivityId | orderBy:predicate:reverse">
<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)">Generate
Activity URLs</button>
</td>
</tr>
</table>
</li>
</ul>
<div class="panel-heading">
<h2 class="panel-title">Already Configured Activities <a ng-click="toggleActivities()"><span class={{imgActivitiesUrl}} aria-hidden="true"></a></h2>
<div class="panel-body">
<p class="text-muted">For any Harmony Activity, use the action
buttons to generate the device addition information below
automatically. Then you can modify the name to anything you want that
will be the keyword for Alexa. 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>
</div>
<ul ng-if="activitiesVisible" class="list-group">
<li class="list-group-item">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="harmonyactivity in bridge.harmonyactivities | unavailableHarmonyActivityId | orderBy:predicate:reverse">
<td>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td>
<td>{{harmonyactivity.hub}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(harmonyactivity.activity.id, 'harmonyActivity')">Delete</button></td>
</tr>
</table>
</li>
</ul>
<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="id">Id</th>
<th sortable-header col="hub">Hub</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="harmonyactivity in bridge.harmonyactivities | availableHarmonyActivityId">
<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)">Generate
Bridge Device</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Harmony Activity</h2>
<h2 class="panel-title">
Already Configured Activities <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<scrollable-table ng-if="buttonsVisible"
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="id">Id</th>
<th sortable-header col="hub">Hub</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="harmonyactivity in bridge.harmonyactivities | unavailableHarmonyActivityId">
<td>{{$index+1}}</td>
<td>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td>
<td>{{harmonyactivity.hub}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(harmonyactivity.activity.id, 'harmonyActivity')">Delete</button></td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for a Harmony
Activity</h2>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary">
Add Activity</button>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<div class="form-group">
<div class="row">
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
@@ -108,10 +115,12 @@
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
<div class="form-group">
<div class="row">
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
@@ -119,9 +128,18 @@
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</div>
</form>
</li>
</ul>
</div>
</form>
</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,137 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Harmony Device List</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<p class="text-muted">You can select a Harmony Device and Button and generate
the add button box selections automatically.</p>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th>On Button</th>
<th>Off Button</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="harmonydevice in bridge.harmonydevices | orderBy:predicate:reverse">
<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">{{funcon.name}}</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">{{funcoff.name}}</option>
</optgroup >
</select>
</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff)">Generate
Button URLs</button>
</td>
</tr>
</table>
</li>
</ul>
<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 class="panel-body">
<p class="text-muted">For any Harmony Device and Buttons, use the
build button to generate the configuration for this bridge device.
You can add button presses by selecting yoru devic and buttons and
then selecting the Build Button. This will allow multiple button
presses to be built for a given device. Then you can modify the name
to anything you want that will be the keyword for Alexa. 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>
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Device Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('targetDevice')">Hub</a>
<span class="sortorder" ng-show="predicate === 'targetDevice'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('mapId')">Harmony Device-Button On-Button Off</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices | configuredButtons | orderBy:predicate:reverse">
<td>{{device.name}}</td>
<td>{{device.id}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(device.mapId, device.mapType)">Delete</button>
</td>
</tr>
</table>
</li>
<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="id">Id</th>
<th sortable-header col="hub">Hub</th>
<th>On Button</th>
<th>Off Button</th>
<th>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>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Harmony Button</h2>
<h2 class="panel-title">
Already Configured Harmony Buttons <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<scrollable-table ng-if="buttonsVisible" 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="id">Device Id</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 | configuredButtons | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.id}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(device.mapId, device.mapType)">Delete</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for Harmony Buttons</h2>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary">
Add Button</button>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<div class="form-group">
<div class="row">
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
@@ -131,10 +139,12 @@
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
<div class="form-group">
<div class="row">
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
@@ -142,9 +152,18 @@
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</div>
</form>
</li>
</ul>
</div>
</form>
</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,148 @@
<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 role="presentation"><a href="#/editor">Manual Add</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 action buttons
to generate the device addition information below automatically. Then
you can modify the name to anything you want that will be the keyword
for Alexa. 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>
</div>
<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>Actions</th>
</tr>
</thead>
<tr ng-repeat="huedevice in bridge.huedevices | availableHueDeviceId">
<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)">Generate Bridge
Device</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 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>
<scrollable-table ng-if="buttonsVisible" 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="id">Id</th>
<th sortable-header col="huename">hue</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="huedevice in bridge.huedevices | unavailableHueDeviceId">
<td>{{$index+1}}</td>
<td>{{huedevice.device.name}}</td>
<td>{{huedevice.device.uniqueid}}</td>
<td>{{huedevice.huename}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(huedevice.device.uniqueid, 'hueDevice')">Delete</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add Bridge Device for a Hue Device</h2>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
</form>
</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,85 @@
<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 role="presentation"><a href="#/editor">Manual Add</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>
</div>
<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 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="animate-if" 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

@@ -0,0 +1,164 @@
<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 role="presentation"><a href="#/editor">Manual Add</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 action buttons to
generate the device addition information below automatically. Then
you can modify the name to anything you want that will be the keyword
for Alexa. 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>
</div>
<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>Actions</th>
</tr>
</thead>
<tr
ng-repeat="nestitem in bridge.nestitems | availableNestItemId | 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 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>
<scrollable-table ng-if="buttonsVisible" 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">Device Id</th>
<th>mapId</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | unavailableNestItemId | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.id}}</td>
<td>{{device.mapId}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(device.mapId, 'nest')">Delete</button></td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for a Nest Item</h2>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</div>
</form>
</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,326 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation" class="active"><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"><a href="#/editor">Manual Add</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">Bridge Settings</h1>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="bridge-base">Bridge
server</label>
<div class="col-xs-8 col-sm-7">
<input id="bridge-base" class="form-control" type="text"
ng-model="bridge.base" placeholder="URL to bridge">
</div>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="goBridgeUrl(bridge.base)">Test</button>
</div>
</form>
<form name="form">
<p>
<button ng-disabled="bridge.isInControl"
class="btn btn-success" type="submit" ng-click="saveSettings()">Save</button>
<button ng-disabled="bridge.isInControl" class="btn btn-warning"
type="submit" ng-click="bridgeReinit()">Bridge
Reinitialize</button>
<button ng-disabled="bridge.isInControl" class="btn btn-danger"
type="submit" ng-click="bridgeStop()">Bridge Stop</button>
<button class="btn btn-primary" type="submit" onclick="myRefresh()">Refresh</button>
<script>
function myRefresh() {
location.reload();
}
</script>
</p>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Setting</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td>Configuration Path and File</td>
<td><input id="bridge-settings-configfile"
class="form-control" type="text"
ng-model="bridge.settings.configfile"
placeholder="data/ha-bridge.config"></td>
</tr>
<tr>
<td>Device DB Path and File</td>
<td><input id="bridge-settings-upnpdevicedb"
class="form-control" type="text"
ng-model="bridge.settings.upnpdevicedb"
placeholder="data/device.db"></td>
</tr>
<tr>
<td>UPNP IP Address</td>
<td><input id="bridge-settings-upnpconfigaddress"
class="form-control" type="text"
ng-model="bridge.settings.upnpconfigaddress"
placeholder="192.168.1.1"></td>
</tr>
<tr>
<td>Web Server Port</td>
<td><input id="bridge-settings-serverport"
class="form-control" type="number"
ng-model="bridge.settings.serverport" min="1" max="65535"></td>
</tr>
<tr>
<td>UPNP Response Port</td>
<td><input id="bridge-settings-upnpresponseport"
class="form-control" type="number"
ng-model="bridge.settings.upnpresponseport" min="1" max="65535"></td>
</tr>
<tr>
<td>Vera Names and IP Addresses</td>
<td><table
class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="vera in bridge.settings.veraaddress.devices">
<td>{{vera.name}}</td>
<td>{{vera.ip}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeVeratoSettings(vera.name, vera.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-vera-name"
class="form-control" type="text" ng-model="newveraname"
placeholder="A Vera"></td>
<td><input id="bridge-settings-next-vera-ip"
class="form-control" type="text" ng-model="newveraip"
placeholder="192.168.1.2"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addVeratoSettings(newveraname, newveraip)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>Harmony Names and IP Addresses</td>
<td><table
class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="harmony in bridge.settings.harmonyaddress.devices">
<td>{{harmony.name}}</td>
<td>{{harmony.ip}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHarmonytoSettings(harmony.name, harmony.ip)">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><button class="btn btn-success" type="submit"
ng-click="addHarmonytoSettings(newharmonyname, newharmonyip)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>Harmony Username</td>
<td><input id="bridge-settings-harmonyuser"
class="form-control" type="text"
ng-model="bridge.settings.harmonyuser"
placeholder="someone@gmail.com"></td>
</tr>
<tr>
<td>Harmony Password</td>
<td><input id="bridge-settings-harmonypwd"
class="form-control" type="password"
ng-model="bridge.settings.harmonypwd" placeholder="thepassword"></td>
</tr>
<tr>
<td>Hue Names and IP Addresses</td>
<td><table
class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="hue in bridge.settings.hueaddress.devices">
<td>{{hue.name}}</td>
<td>{{hue.ip}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHuetoSettings(hue.name, hue.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-hue-name"
class="form-control" type="text" ng-model="newhuename"
placeholder="A Hue"></td>
<td><input id="bridge-settings-next-hue-ip"
class="form-control" type="text" ng-model="newhueip"
placeholder="192.168.1.3"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHuetoSettings(newhuename, newhueip)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>HAL Names and IP Addresses</td>
<td><table
class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="hal in bridge.settings.haladdress.devices">
<td>{{hal.name}}</td>
<td>{{hal.ip}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHaltoSettings(hal.name, hal.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-hal-name"
class="form-control" type="text" ng-model="newhalname"
placeholder="A Hal"></td>
<td><input id="bridge-settings-next-hal-ip"
class="form-control" type="text" ng-model="newhalip"
placeholder="192.168.1.3:82"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHaltoSettings(newhalname, newhalip)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>HAL Token</td>
<td><input id="bridge-settings-haltoken" class="form-control"
type="password" ng-model="bridge.settings.haltoken"
placeholder="thetoken"></td>
</tr>
<tr>
<td>Nest Username</td>
<td><input id="bridge-settings-nestuser" class="form-control"
type="text" ng-model="bridge.settings.nestuser"
placeholder="someone@gmail.com"></td>
</tr>
<tr>
<td>Nest Password</td>
<td><input id="bridge-settings-nestpwd" class="form-control"
type="password" ng-model="bridge.settings.nestpwd"
placeholder="thepassword"></td>
</tr>
<tr>
<td>Nest Temp Farenheit</td>
<td><input type="checkbox"
ng-model="bridge.settings.farenheit" ng-true-value=true
ng-false-value=false> {{bridge.settings.farenheit}}</td>
</tr>
<tr>
<td>Button Press/Call Item Loop Sleep Interval (ms)</td>
<td><input id="bridge-settings-buttonsleep"
class="form-control" type="number" name="input"
ng-model="bridge.settings.buttonsleep" min="100" max="9999"></td>
</tr>
<tr>
<td>Log Messages to Buffer</td>
<td><input id="bridge-settings-numberoflogmessages"
class="form-control" type="number"
ng-model="bridge.settings.numberoflogmessages" min="100"
max="65535"></td>
</tr>
<tr>
<td>UPNP Strict Handling</td>
<td><input type="checkbox"
ng-model="bridge.settings.upnpstrict" ng-true-value=true
ng-false-value=false> {{bridge.settings.upnpstrict}}</td>
</tr>
<tr>
<td>Trace UPNP Calls</td>
<td><input type="checkbox"
ng-model="bridge.settings.traceupnp" ng-true-value=true
ng-false-value=false> {{bridge.settings.traceupnp}}</td>
</tr>
<tr>
<td>My Echo URL</td>
<td><input id="bridge-settings-myechourl" class="form-control"
type="text" ng-model="bridge.settings.myechourl"
placeholder="echo.amazon.com/#cards"></td>
</tr>
</table>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">
Bridge Settings Backup <a ng-click="toggle()"><span
class={{imgUrl}} aria-hidden="true"></a>
</h1>
</div>
<div ng-if="visible" class="animate-if" 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
Settings 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="backupSettings(optionalbackupname)">Backup
Settings</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.configs">
<td>{{backup}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="restoreSettings(backup)">Restore</button>
<button class="btn btn-warning" type="submit"
ng-click="deleteSettingsBackup(backup)">Delete</button>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -1,125 +1,146 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Vera Device List</h2>
<h2 class="panel-title">Vera Device List
({{bridge.veradevices.length}})</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<p class="text-muted">You can select a Vera device and generate
the add device box selections automatically.</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>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('category')">Category</a>
<span class="sortorder" ng-show="predicate === 'category'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="veradevice in bridge.veradevices | availableVeraDeviceId | orderBy:predicate:reverse">
<td>{{veradevice.name}}</td>
<td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td>
<td>{{veradevice.room}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(veradevice, device_dim_control)">Generate
Device URLs</button>
</td>
</tr>
</table>
</li>
</ul>
<div class="panel-heading">
<h2 class="panel-title">Already Configured Vera Devices <a ng-click="toggleDevices()"><span class={{imgDevicesUrl}} aria-hidden="true"></a></h2>
<div class="panel-body">
<p class="text-muted">For any Vera Device, use the action buttons
to generate the device addition information below automatically. Then
you can modify the name to anything you want that will be the keyword
for Alexa. 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>
</div>
<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">Id</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="veradevice in bridge.veradevices | availableVeraDeviceId">
<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)">Generate
Bridge Device</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>
<ul ng-if="devicesVisible" class="list-group">
<li class="list-group-item">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('category')">Category</a>
<span class="sortorder" ng-show="predicate === 'category'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="veradevice in bridge.veradevices | unavailableVeraDeviceId | orderBy:predicate:reverse">
<td>{{veradevice.name}}</td>
<td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td>
<td>{{veradevice.room}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(veradevice.id, 'veraDevice')">Delete</button>
</td>
</tr>
</table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Vera device</h2>
<h2 class="panel-title">
Already Configured Vera Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a></a>
</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<scrollable-table ng-if="buttonsVisible" watch="bridge.veradevices">
<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">Id</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="veradevice in bridge.veradevices | unavailableVeraDeviceId">
<td>{{$index+1}}</td>
<td>{{veradevice.name}}</td>
<td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td>
<td>{{veradevice.room}}</td>
<td>{{veradevice.veraname}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(veradevice.id, 'veraDevice')">Delete</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add Bridge Device for a Vera Device</h2>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary">
Add Device</button>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<div class="form-group">
<div class="row">
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
@@ -127,18 +148,40 @@
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<label class="col-xs-12 col-sm-2 control-label"
for="device-dim-url">Dim URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-dim-url"
ng-model="device.dimUrl" placeholder="URL to dim device"></textarea>
</div>
</div>
</div>
</form>
</li>
</ul>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</form>
</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,108 +1,115 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<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 role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Vera Scene List</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<p class="text-muted">You can select a Vera scene and generate
the add scene box selections automatically.</p>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="verascene in bridge.verascenes | availableVeraSceneId | orderBy:predicate:reverse">
<td>{{verascene.name}}</td>
<td>{{verascene.id}}</td>
<td>{{verascene.room}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildSceneUrls(verascene)">Generate
Scene URLs</button>
</td>
</tr>
</table>
</li>
</ul>
<div class="panel-heading">
<h2 class="panel-title">Already Configured Vera Scenes <a ng-click="toggleScenes()"><span class={{imgScenesUrl}} aria-hidden="true"></a></h2>
<div class="panel-body">
<p class="text-muted">For any Vera Scene, use the action buttons
to generate the device addition information below automatically. Then
you can modify the name to anything you want that will be the keyword
for Alexa. 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>
</div>
<ul ng-if="scenesVisible" class="list-group">
<li class="list-group-item">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="verascene in bridge.verascenes | unavailableVeraSceneId | orderBy:predicate:reverse">
<td>{{verascene.name}}</td>
<td>{{verascene.id}}</td>
<td>{{verascene.room}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(verascene.id, 'veraScene')">Delete</button>
</td>
</tr>
</table>
</li>
</ul>
<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">Id</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="verascene in bridge.verascenes | availableVeraSceneId">
<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)">Generate Bridge
Device</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Vera scene</h2>
<h2 class="panel-title">
Already Configured Vera Scenes <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<scrollable-table ng-if="buttonsVisible" 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">Id</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="verascene in bridge.verascenes | unavailableVeraSceneId">
<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-danger" type="submit"
ng-click="deleteDeviceByMapId(verascene.id, 'veraScene')">Delete</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for a Vera scene</h2>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary">
Add Scene</button>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<div class="form-group">
<div class="row">
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
@@ -110,18 +117,29 @@
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</div>
</form>
</li>
</ul>
</form>
</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>