mirror of
https://github.com/bwssytems/ha-bridge.git
synced 2025-12-16 18:24:36 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9e09851ee | ||
|
|
d57bfec7ba | ||
|
|
483b165f2a | ||
|
|
e2969c1a49 | ||
|
|
c99777efe7 | ||
|
|
8363d4c78c | ||
|
|
e015bca721 | ||
|
|
3b5eea0dce | ||
|
|
25e8b3f24f | ||
|
|
7f72b0311a | ||
|
|
b3f2c25721 | ||
|
|
98008428a7 | ||
|
|
675e74df7b | ||
|
|
fb7aabb780 | ||
|
|
969ed352f7 | ||
|
|
c98513c365 | ||
|
|
add9617a07 | ||
|
|
f9e9f16756 | ||
|
|
f15cc0d53a | ||
|
|
0f791c1e71 | ||
|
|
9887042f4d | ||
|
|
c840f2bc4d | ||
|
|
9a355b7906 | ||
|
|
0d568d8d68 | ||
|
|
f5e100667e | ||
|
|
c376253488 | ||
|
|
a3fd2ca722 | ||
|
|
fe4df16e10 | ||
|
|
9399af7ec7 | ||
|
|
be72f7e62c | ||
|
|
768eebfc78 | ||
|
|
79ce23b80a | ||
|
|
bddc7c1c31 | ||
|
|
51c6ffc48a | ||
|
|
ee4afc00c0 | ||
|
|
8b48f23741 | ||
|
|
87f79df8b1 | ||
|
|
1142704d22 | ||
|
|
d014240fba | ||
|
|
5c2d30e24b | ||
|
|
28d84f667a | ||
|
|
755533b30d | ||
|
|
53208ddabc | ||
|
|
ff2973e473 | ||
|
|
743656cab3 | ||
|
|
53be3ba213 | ||
|
|
a5ee0aafc8 | ||
|
|
aed8ffa8d3 | ||
|
|
369e5a25e6 | ||
|
|
3fe19f5d4e | ||
|
|
5736bb92db | ||
|
|
b61f334826 | ||
|
|
556a5fef1c | ||
|
|
4b0152060f | ||
|
|
fcb31b8f76 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -12,6 +12,7 @@ data
|
||||
/.settings/
|
||||
/start.bat
|
||||
/.classpath
|
||||
/.project
|
||||
|
||||
sftp-config\.json
|
||||
/bin/
|
||||
@@ -22,4 +23,5 @@ sftp-config\.json
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
package-lock.json
|
||||
package-lock.json
|
||||
.project
|
||||
|
||||
23
.project
23
.project
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ha-bridge</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
224
README.md
224
README.md
@@ -1,5 +1,5 @@
|
||||
# ha-bridge
|
||||
Emulates Philips Hue API to other home automation gateways such as an Amazon Echo/Dot Gen 1 (gen 2 has issues discovering ha-bridge) or other systems that support Philips Hue. The Bridge handles basic commands such as "On", "Off" and "brightness" commands of the hue protocol. This bridge can control most devices that have a distinct API.
|
||||
Emulates Philips Hue API to other home automation gateways such as an Amazon Echo/Dot or other systems that support Philips Hue local network discovery. The ha-bridge is not part of meethue.philips.com so the cloud sign in does not apply to this system. The Bridge handles basic commands such as "On", "Off", "Brightness" and "Color" commands of the hue protocol. This bridge can control most devices that have a distinct API.
|
||||
|
||||
Here are some diagrams to put this software in perspective.
|
||||
|
||||
@@ -35,15 +35,13 @@ A Custom implementation path looks like this:
|
||||
|
||||
**NOTE: This software does not control Philips Hue devices directly. A physical Philips Hue Hub is required for that, by which the ha-bridge can then proxy all of your real Hue bridges behind this bridge.**
|
||||
|
||||
**ISSUE: Amazon Echo (2nd Generation) has issues finding the ha-bridge. The only workaround is to have a first generation Echo or Dot on your network that finds the ha-bridge.**
|
||||
|
||||
**ISSUE: Google Home now seems to not support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local API, and is not associated with Philips, this method will not work. If you have an older Google Home application, this may still work. YMMV.**
|
||||
**ISSUE: Google Home does NOT support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local API, and is not associated with Philips, this method will not work.**
|
||||
|
||||
**FAQ: Please look here for the current FAQs! https://github.com/bwssytems/ha-bridge/wiki/HA-Bridge-FAQs**
|
||||
|
||||
In the cases of systems that require authorization and/or have APIs that cannot be handled in the current method, a module may need to be built. The Harmony Hub is such a module and so is the Nest module. The Bridge has helpers to build devices for the gateway for the Logitech Harmony Hub, Vera, Vera Lite or Vera Edge, Nest, Somfy Tahoma, Home Assistant, Domoticz, MQTT, HAL, Fibaro, HomeWizard, LIFX, OpenHAB, FHEM, Broadlink and the ability to proxy all of your real Hue bridges behind this bridge.
|
||||
In the cases of systems that require authorization and/or have APIs that cannot be handled in the current method, a module may need to be built. The Harmony Hub is such a module and so is the Nest module. The Bridge has helpers to build devices for the gateway for the Logitech Harmony Hub, Vera, Vera Lite or Vera Edge, Nest, Somfy Tahoma, Home Assistant, Domoticz, MQTT, HAL, Fibaro, HomeWizard, LIFX, OpenHAB, FHEM, Broadlink, Mozilla IOT, HomeGenie and the ability to proxy all of your real Hue bridges behind this bridge.
|
||||
|
||||
Alternatively the Bridge supports custom calls as well using http/https/udp and tcp such as the LimitlessLED/MiLight bulbs using the UDP protocol. Binary data is supported with UDP/TCP.
|
||||
Alternatively the Bridge supports custom calls and executing programs/scripts as well using http/https/udp and tcp such as the LimitlessLED/MiLight bulbs using the UDP protocol. Binary data is supported with UDP/TCP.
|
||||
|
||||
This bridge was built to help put the Internet of Things together.
|
||||
## Build
|
||||
@@ -59,20 +57,20 @@ Then locate the jar and start the server with:
|
||||
ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below.
|
||||
|
||||
```
|
||||
java -jar ha-bridge-5.3.0.jar
|
||||
java -jar ha-bridge-5.4.1.jar
|
||||
```
|
||||
|
||||
## Manual installation of ha-bridge and setup of systemd service
|
||||
Next gen Linux systems (this includes the Raspberry Pi), use systemd to run and manage services.
|
||||
Here is a link on how to use systemd: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units
|
||||
|
||||
Create the directory and make sure that ha-bridge-5.3.0.jar is in your /home/pi/ha-bridge directory.
|
||||
Create the directory and make sure that ha-bridge-5.4.1.jar is in your /home/pi/ha-bridge directory.
|
||||
|
||||
```
|
||||
pi@raspberrypi:~ $ mkdir ha-bridge
|
||||
pi@raspberrypi:~ $ cd ha-bridge
|
||||
|
||||
pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.0/ha-bridge-5.3.0.jar
|
||||
pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.4.1/ha-bridge-5.4.1.jar
|
||||
```
|
||||
|
||||
Create the ha-bridge.service unit file:
|
||||
@@ -91,7 +89,7 @@ After=network.target
|
||||
Type=simple
|
||||
|
||||
WorkingDirectory=/home/pi/ha-bridge
|
||||
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.0.jar
|
||||
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.4.1.jar
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -163,7 +161,7 @@ pi@raspberrypi:~ $ docker run \
|
||||
--volume=$PWD:/ha-bridge/data \
|
||||
--volume=/etc/localtime:/etc/localtime:ro \
|
||||
--volume=/etc/timezone:/etc/timezone:ro \
|
||||
habridge/ha-bridge-raspberry-pi3 \
|
||||
habridge/ha-bridge-raspberrypi3 \
|
||||
-Dserver.port=8080 \
|
||||
-Dsecurity.key=secret
|
||||
```
|
||||
@@ -224,7 +222,7 @@ proxy.server = (
|
||||
```
|
||||
### nginx Example
|
||||
```
|
||||
location /api/ {
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:8080/api;
|
||||
}
|
||||
```
|
||||
@@ -264,15 +262,27 @@ java -jar -Dexec.garden=C:\Users\John\bin
|
||||
```
|
||||
## HA Bridge Usage and Configuration
|
||||
This section will cover the basics of configuration and where this configuration can be done. This requires that you have started your bridge process and then have pointed your
|
||||
favorite web interface by going to the http://<my ip address>:<port> or http://localhost:<port> with port you have assigned. The default quick link is http://localhost for your reference.
|
||||
favorite web interface by going to the `http://<my ip address>:<port>` or `http://localhost:<port>` with port you have assigned. The default quick link is http://localhost for your reference.
|
||||
### The Bridge Devices Tab
|
||||
This screen allows you to see your devices you have configured for the ha-bridge to present to a controller, such as an Amazon Echo/Dot. It gives you a count of devices as there have been reports that the Echo only supports a limited number, but has been growing as of late, YMMV. You can test each device from this page as this calls the ha-bridge just as a controller would, i.e. the Echo. This is useful to make sure your configuration for each device is correct and for trouble shooting. You can also manages your devices as well by editing and making a new device copy as well as deleting it.
|
||||
This screen allows you to see your devices you have configured for the ha-bridge to present to a controller, such as an Amazon Echo/Dot. You can test each device from this page as this calls the ha-bridge just as a controller would, i.e. the Echo. This is useful to make sure your configuration for each device is correct and for trouble shooting. You can also manage your devices as well by editing and making a new device copy as well as deleting it.
|
||||
|
||||
At the bottom of the screen is the "Bridge Device DB Backup" which can be accessed with clicking on the `+` to expand this frame. Here you can backup and restore configurations that you have saved. These configs can be named or by clicking the `Backup Device DB' button will create a backup and name it for you. You can manage these backups by restoring them or deleting them.
|
||||
Each of the columns are sortable by the use of the little arrow in the upper left corner of the heading.
|
||||
|
||||
If you click on the ID number on a Device, this will toggle the ID number to lock/unlock that is used during renumbering. When the ID is '<b>Bolded</b>', it is locked. You can also hover your mouse to see the state. This allows you to renumber a part of your devices and keep the ID for others. All IDs will be unique.
|
||||
|
||||
There is a new feature for the Devices that can be selected on the 'Startup' column. If you choose to have certain devices executed when the bridge is started or restarted. You can can click this field to bring up the dialog which will allow you to enter what you want the device to do on startup. The default is to do nothing.
|
||||
|
||||
At the bottom of the screen is the "Bridge Device DB Backup" which can be accessed with clicking on the `+` to expand this frame. Here you can backup and restore configurations that you have saved. These configs can be named or by clicking the `Backup Device DB' button will create a backup and name it for you. You can manage these backups by restoring them, deleting them, downloading them by clicking on the file name and uploading a saved device DB backup on your local machine.
|
||||
#### Renumber Devices
|
||||
This changes the numbering of the added devices to start at 1 and goes up from there. It was originally intended for a conversion from the previous system version that used large numbers and was not necessary. This also allows the system to try and number sequentially. If you use this button, you will need to re-discover your devices as their ID's will have changed.
|
||||
This changes the numbering of the added devices to start at the nmuber given in the 'Bridge Control' tab field 'ID Seed' and the default is 100 and goes up from there. It was originally intended for a conversion from the previous system version that used large numbers and was not necessary. This also allows the system to try and number sequentially. If you use this button, you will need to re-discover your devices as their ID's will have changed.
|
||||
#### Link
|
||||
If this is present, you have enabled the Hue link button feature for the ha-bridge. If you want a new system to recognize the ha-bridge, you will need to press this button when you are doing a discovery.
|
||||
#### Manage Links
|
||||
If this is present, you have enabled the Hue link button feature for the ha-bridge. This button will bring up a dialog which contains all of the registered users created by linking the ha-bridge with a device. This allows you to revoke control for a created user so that it cannot access the ha-bridge by deleting that link user.
|
||||
#### Show devices visible to
|
||||
This filter is to sort devices that have an IP filter set. Type in the IP that is used in your device filter field.
|
||||
#### Filter device type
|
||||
This filter is for a quick method to find specific devices by their type listed in the dropdown.
|
||||
### The Bridge Control Tab
|
||||
This is where all of the configuration occurs for what ports and IP's the bridge runs on. It also contains the configurations for target devices so that Helper Tabs for configuration can be added as well as the connection information to control those devices.
|
||||
#### Bridge server
|
||||
@@ -280,11 +290,35 @@ This field is used to test the bridge server with the UPNP IP Address and to mak
|
||||
#### Bridge Control Buttons
|
||||
These buttons are for managing the bridge. The Save button is enabled when there is a change to the configuration. The Bridge Reinitialize button will recycle the internal running of the bridge in the java process. The Stop button will stop the java process. The Refresh button will refresh the page and settings.
|
||||
#### The Security Dialog
|
||||
This is where you can set the different security settings for the ha-bridge. There are two settings, one for enabling Hue like operation to secure the Hue API with the internally generated user for the calls that are done after the link button. The other is to secure the hue API with a username/password that is created as well. The other fields are to add and delete users and to set and change passwords for those users. If there are no users in the system, the system will not require a username/password to operate.
|
||||
This is where you can set the different security settings for the ha-bridge. The settings are the https enablement with the Keyfile path and password and described below, enabling Hue like operation to secure the Hue API with the internally generated user for the calls that are done after the link button. Another is to secure the hue API with a username/password that is created as well. The last one is the path to the exec garden if used for only allowing scripts and programs to be executed from this location only. The other fields are to add and delete users and to set and change passwords for those users. If there are no users in the system, the system will not require a username/password to operate.
|
||||
|
||||
The use https selection will require you to generate a java keytool keystore file for the ha-bridge to use. This will require the path to the keyfile and the password that was used to secure the keyfile. There are mulitple ways to add the key file. The basic way is to generate a self signed keystore using keytool as follows:
|
||||
|
||||
Step 1. Open the command console
|
||||
|
||||
Step 2. Run this command (Where indicate the number of days for which the certificate will be valid)
|
||||
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass ```<password>``` -validity 365 -keysize 2048
|
||||
|
||||
Step 3. Enter a password for the keystore. Note this ```<password>``` as you require this for configuring the server
|
||||
|
||||
Step 4. When prompted for first name and last name, enter the domain name of the server. For example, myserver or myserver.mycompany.com.
|
||||
|
||||
Step 5. Enter the other details, such as Organizational Unit, Organization, City, State, and Country.
|
||||
|
||||
Step 6. When prompted with Enter key ```<password>``` for , press Enter to use the same ```<password>``` as the keystore ```<password>```
|
||||
|
||||
Step 7. Run this command to verify the contents of the keystore
|
||||
keytool -list -v -keystore selfsigned.jks
|
||||
|
||||
Step 8. When prompted, enter the keystore password note in Step 3. The basic information about the generated certificate is displayed. Verify that the Owner and Issuer are the same. Also, you should see the information you provided in Step 4 and 5.
|
||||
|
||||
The second way is to acquire a certified certificate to use in the keyfile. Such way to get one is use letsencrypt. Once you have the certifcate files you can follow this to create the keystore: https://community.letsencrypt.org/t/tutorial-java-keystores-jks-with-lets-encrypt/34754.
|
||||
|
||||
The easiest place to keep the keystore files is in your ha bridge data directory.
|
||||
#### Configuration Path and File
|
||||
The default location for the configuration file to contain the settings for the bridge is the relative path from where the bridge is started in "data/habridge.config". If you would like a different filename or directory, specify `<directory>/<filename>` explicitly.
|
||||
#### Device DB Path and File
|
||||
The default location for the db to contain the devices as they are added is "data/devices.db". If you would like a different filename or directory, specify `<directory>/<filename> explicitly.
|
||||
The default location for the db to contain the devices as they are added is "data/devices.db". If you would like a different filename or directory, specify `<directory>/<filename>` explicitly.
|
||||
#### UPNP IP Address
|
||||
The server defaults to the first available address on the host if this is not given. This default may NOT be the correct IP that is your public IP for your host on the network. It is best to set this parameter to not have discovery issues. Replace this value with the server ipv4 address you would like to use as the address that any upnp device will call after discovery.
|
||||
#### Use UPNP Address Interface
|
||||
@@ -298,9 +332,9 @@ The server defaults to running on port 80. To override what the default is, spec
|
||||
#### UPNP Response Port
|
||||
The upnp response port that will be used. The default is 50000.
|
||||
#### Vera Names and IP Addresses
|
||||
Provide IP Addresses of your Veras that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Vera and device/scene you configure.
|
||||
Provide IP Addresses of your Veras that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Vera device/scene you configure.
|
||||
#### Fibaro Names and IP Addresses
|
||||
Provide IP Addresses of your Fibaros that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Fibaro and device/scene you configure. There are filter switches available to limit some of the returns for devices and scenes such as use save logs, use user description, use only Lili command and a switch that cleans up format Trash Chars. The default filters are false for everything but Trash Chars.
|
||||
Provide IP Addresses of your Fibaros that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Fibaro device/scene you configure. There are filter switches available to limit some of the returns for devices and scenes such as use save logs, use user description, use only Lili command and a switch that cleans up format Trash Chars. The default filters are false for everything but Trash Chars.
|
||||
#### Harmony Names and IP Addresses
|
||||
Provide IP Addresses of your Harmony Hubs that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the activity or buttons by the call it receives and send it to the target Harmony Hub and activity/button you configure. Also, an option of webhook can be called when the activity changes on the harmony hub that will send an HTTP GET call to the the address of your choosing. This can contain the replacement variables of ${activity.id} and/or ${activity.label}. Example : http://192.168.0.1/activity/${activity.id}/${activity.label} OR http://hook?a=${activity.label}
|
||||
#### Hue Names and IP Addresses
|
||||
@@ -308,27 +342,27 @@ Provide IP Addresses of your Hue Bridges that you want to proxy through the brid
|
||||
|
||||
Don't forget - You will need to push the link button when you got to the Hue Tab the first time after the process comes up. (The user name is not persistent when the process comes up.)
|
||||
#### HAL Names and IP Addresses
|
||||
Provide IP Addresses of your HAL Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target HAL and device/scene you configure.
|
||||
Provide IP Addresses of your HAL Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target HAL device you configure.
|
||||
#### MQTT Client IDs and IP Addresses
|
||||
Provide Client ID and IP Addresses and ports of your MQTT Brokers that you want to utilize with the bridge. Also, you can provide the username and password if you have secured your MQTT broker which is optional. When these Client ID and IP's are given, the bridge will be able to publish MQTT messages by the call it receives and send it to the target MQTT Broker you configure. The MQTT Messages Tab will become available to help you build messages.
|
||||
#### Home Assistant Names and IP Addresses
|
||||
<Provide IP Addresses and ports of your Home Assistant that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Home Assistant and device/scene you configure.
|
||||
<Provide IP Addresses and ports of your Home Assistant that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Home Assistant device you configure.
|
||||
#### HomeWizard Gateways Names and IP Addresses
|
||||
Provide IP Addresses of your HomeWizard Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target HomeWizard and device/scene you configure.
|
||||
Provide IP Addresses of your HomeWizard Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target HomeWizard device you configure.
|
||||
#### Domoticz Names and IP Addresses
|
||||
Provide IP Addresses of your Domoticz Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Domoticz and device/scene you configure.
|
||||
Provide IP Addresses of your Domoticz Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Domoticz device you configure.
|
||||
#### Somfy Tahoma Names and IP Addresses
|
||||
Provide user name and password used to login to www.tahomalink.com. This needs to be provided if you're using the Somfy Tahoma features (for connecting to IO Homecontrol used by Velux among others). There is no need to give any IP address or host information as this contacts your cloud account. *Note:* you have to 'turn on' a window to open it, and 'turn off' to close.
|
||||
#### OpenHAB Names and IP Addresses
|
||||
Provide IP Addresses of your OpenHAB Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target OpenHAB and device/scene you configure.
|
||||
Provide IP Addresses of your OpenHAB Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target OpenHAB device you configure.
|
||||
#### FHEM Names and IP Addresses
|
||||
Provide IP Addresses of your FHEM Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target FHEM and device/scene you configure.
|
||||
Provide IP Addresses of your FHEM Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target FHEM device you configure.
|
||||
#### Mozilla IOT Names and IP Addresses
|
||||
Provide IP Addresses of your Mozilla IOT Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices by the call it receives and send it to the target Mozilla IOT device you configure.
|
||||
#### HomeGenie Names and IP Addresses
|
||||
Provide IP Addresses of your HomeGenie Systems that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices by the call it receives and send it to the target HomeGenie device you configure. There is an extra Other Types field that you can add types that are not within the defualt of "light", "Switch" and "Dimmer" that you may want to control.
|
||||
#### Nest Username
|
||||
The user name of the home.nest.com account for the Nest user. This needs to be given if you are using the Nest features. There is no need to give any ip address or host information as this contacts your cloud account.
|
||||
#### Nest Password
|
||||
The password for the user name of the home.nest.com account for the Nest user. This needs to be given if you are using the Nest features.
|
||||
#### Nest Temp Fahrenheit
|
||||
This setting allows the value being sent into the bridge to be interpreted as Fahrenheit or Celsius. The default is to have Fahrenheit.
|
||||
Provide the username and password of the home.nest.com account for the Nest user. This needs to be given if you are using the Nest features. There is no need to give any ip address or host information as this contacts your cloud account. Also, you can set Nest Temp Fahrenheit that allows the value being sent into the bridge to be interpreted as Fahrenheit or Celsius. The default is to have Fahrenheit.
|
||||
#### LIFX Support
|
||||
This setting will have the ha-bridge look for LIFX devices on your network. Since this is broadcast based, there is no other info needed.
|
||||
#### Broadlink Support
|
||||
@@ -341,14 +375,22 @@ This setting is in bridge-id, uuid, etc. in ha-bridge hue config replies. Leave
|
||||
This setting is the time used in between button presses when there is multiple buttons in a button device. It also controls the time between multiple items in a custom device call. This is defaulted to 100ms and the number represents milliseconds (1000 milliseconds = 1 second).
|
||||
#### Log Messages to Buffer
|
||||
This controls how many log messages will be kept and displayed on the log tab. This does not affect what is written to the standard output for logging. The default is 512. Changing this will incur more memory usage of the process.
|
||||
#### UPNP Original (simple version) ####
|
||||
Use very simplistic UPNP handling that was used in versions previous to 4.0. (Not Recommended)
|
||||
#### UPNP Advanced (use multiple responses and notifies) ####
|
||||
Turns on advanced UPP that hue bridge version 1 used at the latest release, not very stable. (Not Recommended)
|
||||
#### Trace UPNP Calls
|
||||
Turn on tracing for upnp discovery messages to the log. The default is false.
|
||||
#### Trace State Changes
|
||||
Turn on tracing for calls to the ha-bridge and send these information messages to the log for debugging. This a quick way to watch the state changes without turning the HueMulator debugging. The default is false.
|
||||
#### UPNP Send Delay
|
||||
This setting is for the upnp spec to delay a certain amount between upnp response messages sent back to a client. This is defaulted to 650ms and can be tuned to any value up to 1500ms.
|
||||
#### ID Seed
|
||||
The seed that starts numbering from this value and is used in the 'Renumbering' button on the 'Bridge Devices' tab. The defaul for this value is 100.
|
||||
#### My Echo URL
|
||||
This sets the URL that is used in the menu bar to ge to your echo. For certain countries, this needs to be set to a different URL.
|
||||
|
||||
At the bottom of the screen is the "Bridge Settings Backup" which can be accessed with clicking on the `+` to expand this frame. Here you can backup and restore configurations that you have saved. These configs can be named or by clicking the `Backup Settings' button will create a backup and name it for you. You can manage these backups by restoring them or deleting them.
|
||||
At the bottom of the screen is the "Bridge Settings Backup" which can be accessed with clicking on the `+` to expand this frame. Here you can backup and restore configurations that you have saved. These configs can be named or by clicking the `Backup Settings' button will create a backup and name it for you. You can manage these backups by restoring them, deleting them, downloading them by clicking on the file name and uploading a saved configuration backup on your local machine.
|
||||
### The Logs Tab
|
||||
This screen displays the last 512 or number of rows defined in the config screen of the log so you don't have to go to the output of your process. The `Update Log` button refreshes the log as this screen does not auto refresh. FYI, when the trace upnp setting is turned on in the configuration, the messages will show here.
|
||||
|
||||
@@ -358,13 +400,13 @@ You must configure devices before you will have anything for the Echo or other c
|
||||
#### Helpers
|
||||
The easy way to get devices configured is with the use of the helpers for the Vera or Harmony, Nest, Hue and others to create devices that the bridge will present.
|
||||
|
||||
For the Helpers, each item being presented from the target system has a button such as `Build Item`, `Build A Button` or specific tasks such as `Temp` for thermostats that is used to create the specific device parameters. The build action buttons will put you into the edit screen. The next thing to check is the name for the bridge device that it is something that makes sense especially if you using the ha-bridge with an Echo or Google Home as this is what the Echo or Google Home will interpret as the device you want. Also, you can go back to any helper tab and click a build action button to add another item for a multi-command. After you are done in the edit tab, click the `Add Bridge Device` to finish that selection setup.
|
||||
For the Helpers, each item being presented from the target system has a button such as `Build Item`, `Build A Button` or specific tasks such as `Temp` for thermostats that is used to create the specific device parameters. The build action buttons will put you into the edit screen. The next thing to check is the name for the bridge device that it is something that makes sense especially if you using the ha-bridge with an Echo or Google Home as this is what the Echo or Google Home will interpret as the device you want. Also, you can go back to any helper tab and click a build action button to add another item for a multi-command. After you are done in the edit tab, click the `Add Bridge Device` to finish that selection setup. OR you can go back to any helper and use `Build Item` or as such to add another item into the current device being shown in the `Add/Edit` tab. This allows you to create custom devices that execute many devices at once.
|
||||
|
||||
The helper tabs will also show you what you have already configured for that target type. Click on the `+` and you will see them and be able to delete them.
|
||||
#### The Add/Edit Tab
|
||||
Another way to add a device is through the Manual Add Tab. This allows you to manually enter the name, the on and off URLs and select if there are custom handling with the type of call that can be made. This allows for control of anything that has a distinct request that can be executed so you are not limited to the Vera, Harmony, Nest or other Hue.
|
||||
This is the main device editing page to modify details or you can add a device through this tab. This allows you to manually enter the name, the on/dim/off/color items and select if there are custom handling with the type of call that can be made. This allows for control of anything that has a distinct request that can be executed so you are not limited to the Vera, Harmony, Nest or other Hue.
|
||||
|
||||
There is a new format for the on/dim/off URL areas. The new editor handles the intricacies of the components, but is broken down here for explanation.
|
||||
There is a new format for the on/dim/off/color URL areas. The new editor handles the intricacies of the components, but is broken down here for explanation.
|
||||
|
||||
It is imperative when adding a line by hand that you hit the ```Add``` button at the end of the line before adding or updating the whole entry.
|
||||
|
||||
@@ -443,34 +485,15 @@ Examples:
|
||||
```
|
||||
|
||||
#### Multiple Call Construct
|
||||
Also available is the ability to specify multiple commands in the On URL, Dim URL and Off URL areas by adding Json constructs listed here. This is only for the types of tcp, udp, http, https or a new exec type. Also within the item format you can specify delay in milliseconds and count per item. These new parameters work on device buttons for the Harmony as well.
|
||||
Format Example in the URL areas:
|
||||
```
|
||||
[{"item":"http://192.168.1.1:8180/do/this/thing","type":"httpDevice"},
|
||||
{"item":"http://192.168.1.1:8180/do/the/next/thing","delay":1000,"count":2,"type":"httpDevice"},
|
||||
{"item":"http://192.168.1.1:8180/do/another/thing","type":"httpDevice"}]
|
||||
|
||||
|
||||
[{"item":"udp://192.168.1.1:5000/0x450555","type":"udpDevice"},
|
||||
{"item":"udp://192.168.1.1:5000/0x45${intensity.percent}55","type":"udpDevice"}]
|
||||
|
||||
[{"item":"udp://192.168.1.1:5000/0x450555","type":"udpDevice"},
|
||||
{"item":"http://192.168.1.1:8180/do/this/thing","type":"httpDevice"},
|
||||
{"item":"tcp://192.168.2.1/sendthisdata","type":"tcpDevice"},
|
||||
{"item":"https://192.168.12.1/do/this/secure/thing","type":"httpDevice"},
|
||||
{"item":"exec://notepad.exe","type":"cmdDevice"}]
|
||||
```
|
||||
Also available is the ability to specify multiple commands in the On Items, Dim Items, Off Items and Color Items by adding a new line to the item. When doing this manually, make sure to hit the `Add` button to the right of the row. If you do not hit the add button, it will not save when the add or update device buttons are selected. You can also add by going to a helper tab after you have entered the `Add/Edit` tab and using the helpers 'Build' buttons to add a new item line in the respected On/Dim/Off/Color items areas.
|
||||
#### Script or Command Execution
|
||||
The release as of v2.0.0 will now support the execution of a local script or program. This will blindly fire off a process to run and is bound by the privileges of the java process.
|
||||
|
||||
To configure this type of manual add, you will need to select the Device type of "Execute Script/Program".
|
||||
|
||||
In the URL areas, the format of the execution is just providing what command line you would like to run, or using the multiple call item construct described above.
|
||||
```
|
||||
[{"item":"exec://C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1","type":"cmdDevice"},{"item":"exec://notepad.exe","type":"cmdDevice"}]
|
||||
|
||||
[{"item":"/home/pi/scripts/dothisscript.sh","type":"cmdDevice"}]
|
||||
```
|
||||
If you are running a script on a system, you will need to provide the shell interface it will use before the script such as `sh` or `cmd.exe`.
|
||||
#### Value Passing Controls
|
||||
There are multiple replacement constructs available to be put into any of the calls except Harmony items, Net Items and HAL items. These constructs are: "${time.format(Java time format string)}", "${intensity.percent}", "${intensity.percent.hex}", "${intensity.decimal_percent}", "${intensity.byte}", "${intensity.byte.hex}", "${intensity.math(using X in your calc)}" and "${intensity.math(using X in your calc).hex}".
|
||||
|
||||
@@ -497,6 +520,59 @@ e.g.
|
||||
[{"item":{"clientId":"TestClient","topic":"Yep","message":"This is the time ${time.format(yyyy-MM-ddTHH:mm:ssXXX)}"},"type":"mqttDevice"}]
|
||||
|
||||
```
|
||||
Listing of all intensity replacement values that can be used.
|
||||
|
||||
Replacement target text | Description
|
||||
------------------------|------------
|
||||
${intensity.percent} | Insert the whole number percentage value e.g. 45
|
||||
${intensity.decimal_percent} | Insert the decimal percentage value e.g. 0.45
|
||||
${intensity.byte} | Insert the byte value
|
||||
${intensity.math(X)} | Insert the math function identified by X
|
||||
${intensity.math(X).hex} | Insert the hex value of the math function identified by X
|
||||
${intensity.percent.hex} | Insert the hex value of the integer percentage value
|
||||
${intensity.byte.hex} | Insert the hex value of the byte value
|
||||
${intensity.previous_percent} | Insert the previous integer percentage value
|
||||
${intensity.previous_decimal_percent}Insert the previous decimal percentage value
|
||||
${intensity.previous_byte} | Insert the previous byte value
|
||||
|
||||
Listing of all color replacement values that can be used.
|
||||
|
||||
Replacement target text | Description
|
||||
------------------------| -----------
|
||||
${color.r} | Insert the integer value of Red e.g. 123
|
||||
${color.g} | Insert the integer value of Green e.g. 241
|
||||
${color.b} | Insert the integer value of Blue e.g. 255
|
||||
${color.rx} | Insert the hex value of Red e.g. 7BX
|
||||
${color.gx} | Insert the hex value of Green e.g. F1X
|
||||
${color.bx} | Insert the hex value of Blue e.g. FFX
|
||||
${color.rgbx} | Insert the hex value of all rgb e.g. 7BF1FFX
|
||||
${color.hsb} | Insert the hsb value e.g. 186.3636,100.0,74.1176
|
||||
${color.h} | Insert the decimal value of hue e.g. 186.3636
|
||||
${color.s} | Insert the decimal value of saturation e.g. 100.0
|
||||
${colorbri} | Insert the integer value of the intensity
|
||||
${color.milight:([01234])} | Insert the converted value for milight
|
||||
|
||||
Listing of all ha-bridge device data replacement values that can be used.
|
||||
|
||||
Replacement target text | Description
|
||||
------------------------|------------
|
||||
${device.id} | Insert the ID of the device
|
||||
${device.uniqueid} | Insert the unique ID of the device
|
||||
${device.name} | Insert the name of the device
|
||||
${device.mapId} | Insert the map ID of the deivice
|
||||
${device.mapType} | Insert the map type of the device
|
||||
${device.deviceType} | Insert the device type of the device
|
||||
${device.targetDevice} | Insert the target device of the device
|
||||
${device.requesterAddress} | Insert the requester address of the device being addressed
|
||||
${device.description} | Insert the description of the device
|
||||
${device.comments} | Insert the comments of the device
|
||||
|
||||
Listing of all time data replacement values that can be used.
|
||||
|
||||
Replacement target text | Description
|
||||
------------------------|------------
|
||||
${time.format([Java SimpleDateFormat style string]) | Insert the current time described by java SimpleDateFormat string descriptor
|
||||
${time.millis} | Insert the current time in milliseconds that the system returns
|
||||
|
||||
Also, you may want to use the REST APIs listed below to configure your devices.
|
||||
## Ask Alexa
|
||||
@@ -518,41 +594,7 @@ DIM Commands| Alexa, set `<Device Name>` to `<Position>`
|
||||
|
||||
To see what Alexa thinks you said, you can check in the home page for your Alexa.
|
||||
|
||||
To view or remove devices that Alexa knows about, you can use the mobile app `Menu / Settings / Connected Home` or go to http://echo.amazon.com/#cards.
|
||||
|
||||
## Google Assistant
|
||||
Google Home is supported as of v3.2.0 and forward, but only if the bridge is running on port 80.
|
||||
|
||||
**ISSUE: Google Home now seems to not support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local API, and is not associated with Philips, this method will not work. If you have an older Google Home application, this may still work. YMMV.**
|
||||
|
||||
Use the Google Home app on a phone to add new "home control" devices by going into `Settings / Home Control / +`
|
||||
as described [here](https://support.google.com/googlehome/answer/7124115?hl=en&ref_topic=7125624#homecontrol).
|
||||
Click on `Philips Hue` under the `Add new` section. If ha-bridge is on the same network as the
|
||||
phone as well as the Home device, then the app should quickly pass through the pairing step and
|
||||
populate with all of the devices. If instead it takes you to a Philips Hue login page, this means
|
||||
that the bridge was not properly discovered.
|
||||
|
||||
Then you can say "OK Google, Turn on the office light" or whatever name you have given your configured devices.
|
||||
|
||||
The Google Assistant can also group lights into rooms as described in the main [help article](https://support.google.com/googlehome/answer/7072090?hl=en&ref_topic=7029100).
|
||||
|
||||
Here is the table of items to use to tell Google what you want to do. Note that either "OK Google"
|
||||
or "Hey Google" can be used as a trigger.
|
||||
|
||||
To do this: | Say "Hey Google", then...
|
||||
------------|--------------------------
|
||||
To turn on/off a light | "Turn on <light name>"
|
||||
Dim a light | "Dim the <light name>"
|
||||
Brighten a light | "Brighten the <light name>"
|
||||
Set a light brightness to a certain percentage | "Set <light name> to 50%"
|
||||
Dim/Brighten lights by a certain percentage | "Dim/Brighten <light name> by 50%"
|
||||
Turn on/off all lights in room | "Turn on/off lights in <room name>"
|
||||
Turn on/off all lights | "Turn on/off all of the lights"
|
||||
|
||||
To see what Home thinks you said, you can ask "Hey Google, What did I say?" or check the history in the app.
|
||||
|
||||
New or removed devices are picked up automatically as soon as they are added/removed from ha-bridge.
|
||||
No re-discovery step is necessary.
|
||||
To view or remove devices that Alexa knows about, you can use the mobile app click on the devices icon or go to http://echo.amazon.com/#cards.
|
||||
|
||||
## Configuration REST API Usage
|
||||
This section will describe the REST API available for configuration. The REST body examples are all formatted for easy reading, the actual body usage should be like this:
|
||||
|
||||
273
java8_pom.xml
Normal file
273
java8_pom.xml
Normal file
@@ -0,0 +1,273 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.bwssystems.HABridge</groupId>
|
||||
<artifactId>ha-bridge</artifactId>
|
||||
<version>5.4.0</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 or Nest, using lightweight frameworks</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>Eclipse Paho Repo</id>
|
||||
<url>https://repo.eclipse.org/content/repositories/paho-releases/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.bwssytems</groupId>
|
||||
<artifactId>harmony-java-client</artifactId>
|
||||
<version>master-SNAPSHOT</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.14</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.7.2</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.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
<version>4.4.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.24</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.eval</groupId>
|
||||
<artifactId>eval</artifactId>
|
||||
<version>0.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>4.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.igniterealtime.smack</groupId>
|
||||
<artifactId>smack-core</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.paho</groupId>
|
||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.bwssytems</groupId>
|
||||
<artifactId>lifx-sdk-java</artifactId>
|
||||
<version>2.1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.mob41</groupId>
|
||||
<artifactId>broadlink-java-api</artifactId>
|
||||
<version>master-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jmdns</groupId>
|
||||
<artifactId>jmdns</artifactId>
|
||||
<version>3.5.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>version.properties</include>
|
||||
</includes>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<excludes>
|
||||
<exclude>version.properties</exclude>
|
||||
</excludes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>3.0.0-M2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enforce-maven</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireMavenVersion>
|
||||
<version>3.3</version>
|
||||
</requireMavenVersion>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin> <plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M3</version>
|
||||
<configuration>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
<exclude>META-INF/*.txt</exclude>
|
||||
<exclude>META-INF/maven/**</exclude>
|
||||
<exclude>about_files/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.slf4j:slf4j-api</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>commons-logging:commons-logging</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>xpp3:xpp3</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.igniterealtime.smack:*</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>com.github.bwssytems:harmony-java-client</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.eclipse.paho:org.eclipse.paho.client.mqttv3</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
</filters>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.bwssystems.HABridge.HABridge</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
19
pom.xml
19
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.bwssystems.HABridge</groupId>
|
||||
<artifactId>ha-bridge</artifactId>
|
||||
<version>5.3.0RC5</version>
|
||||
<version>5.4.1-java11</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>HA Bridge</name>
|
||||
@@ -71,7 +71,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.1</version>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
@@ -111,12 +111,12 @@
|
||||
<dependency>
|
||||
<groupId>org.eclipse.paho</groupId>
|
||||
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -134,6 +134,11 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jmdns</groupId>
|
||||
<artifactId>jmdns</artifactId>
|
||||
<version>3.5.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -167,7 +172,7 @@
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireMavenVersion>
|
||||
<!-- Change this to Version 3.3 for Java 1.8 and Raspberry PI compilation -->
|
||||
<!-- Change this to Version 3.3 for Java 1.8 and Raspberry PI compilation, Java 11 is 3.6 -->
|
||||
<version>3.6</version>
|
||||
</requireMavenVersion>
|
||||
</rules>
|
||||
@@ -179,7 +184,7 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<!-- Comment this release line out for Java 1.8 and Raspberry PI compilation -->
|
||||
<!-- Comment this release line out for Java 1.8 and Raspberry PI compilation -->
|
||||
<release>11</release>
|
||||
<!-- Uncomment the next two lines for Java 1.8 and Raspberry PI compilation -->
|
||||
<!-- <source>1.8</source> -->
|
||||
@@ -191,7 +196,7 @@
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M3</version>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -38,7 +38,7 @@ public class BridgeSecurity {
|
||||
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
|
||||
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
|
||||
};
|
||||
private char[] habridgeKey;
|
||||
private static char[] habridgeKey;
|
||||
private String execGarden;
|
||||
private BridgeSecurityDescriptor securityDescriptor;
|
||||
private boolean settingsChanged;
|
||||
@@ -146,7 +146,16 @@ public class BridgeSecurity {
|
||||
public String getExecGarden() {
|
||||
return execGarden;
|
||||
}
|
||||
public void setUseLinkButton(boolean useThis) {
|
||||
|
||||
String getKeyfilePath() {
|
||||
return securityDescriptor.getKeyfilePath();
|
||||
}
|
||||
|
||||
String getKeyfilePassword() {
|
||||
return securityDescriptor.getKeyfilePassword();
|
||||
}
|
||||
|
||||
private void setUseLinkButton(boolean useThis) {
|
||||
securityDescriptor.setUseLinkButton(useThis);
|
||||
settingsChanged = true;
|
||||
}
|
||||
@@ -155,16 +164,77 @@ public class BridgeSecurity {
|
||||
return securityDescriptor.isSecureHueApi();
|
||||
}
|
||||
|
||||
public void setSecureHueApi(boolean theState) {
|
||||
securityDescriptor.setSecureHueApi(theState);
|
||||
public boolean isUseHttps() {
|
||||
return securityDescriptor.isUseHttps();
|
||||
}
|
||||
|
||||
public boolean isKeyfilePW() {
|
||||
if(securityDescriptor.getKeyfilePassword() != null && !securityDescriptor.getKeyfilePassword().trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setSecureHueApi(boolean theState) {
|
||||
securityDescriptor.setSecureHueApi(theState);
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
private void setUseHttps(boolean usehttps, String keyfilepath, String keyfilepassword) {
|
||||
if(usehttps) {
|
||||
if(!isUseHttps()) {
|
||||
securityDescriptor.setKeyfilePath(keyfilepath);
|
||||
securityDescriptor.setKeyfilePassword(keyfilepassword);
|
||||
securityDescriptor.setUseHttps(usehttps);
|
||||
settingsChanged = true;
|
||||
} else {
|
||||
if(!keyfilepassword.equals("########")) {
|
||||
securityDescriptor.setKeyfilePassword(keyfilepassword);
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
if(!securityDescriptor.getKeyfilePath().equals(keyfilepath)) {
|
||||
securityDescriptor.setKeyfilePath(keyfilepath);
|
||||
settingsChanged = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(isUseHttps()) {
|
||||
securityDescriptor.setKeyfilePassword("");
|
||||
securityDescriptor.setKeyfilePath("");
|
||||
securityDescriptor.setUseHttps(usehttps);
|
||||
settingsChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SecurityInfo getSecurityInfo() {
|
||||
SecurityInfo theInfo = new SecurityInfo();
|
||||
theInfo.setUseLinkButton(isUseLinkButton());
|
||||
theInfo.setSecureHueApi(isSecureHueApi());
|
||||
theInfo.setSecure(isSecure());
|
||||
theInfo.setUseHttps(isUseHttps());
|
||||
theInfo.setKeyfilePath(securityDescriptor.getKeyfilePath());
|
||||
|
||||
if(isKeyfilePW()) {
|
||||
theInfo.setKeyfilePassword("########");
|
||||
}
|
||||
else {
|
||||
theInfo.setKeyfilePassword("");
|
||||
}
|
||||
if(isSecure()) {
|
||||
theInfo.setExecGarden(execGarden);
|
||||
}
|
||||
return theInfo;
|
||||
}
|
||||
|
||||
public void setSecurityDataByInfo(SecurityInfo theInfo) {
|
||||
setUseLinkButton(theInfo.isUseLinkButton());
|
||||
setSecureHueApi(theInfo.isSecureHueApi());
|
||||
setUseHttps(theInfo.isUseHttps(), theInfo.getKeyfilePath(), theInfo.getKeyfilePassword());
|
||||
}
|
||||
|
||||
public LoginResult validatePassword(User targetUser) throws IOException {
|
||||
LoginResult result = new LoginResult();
|
||||
if(targetUser != null && targetUser.getUsername() != null) {
|
||||
@@ -306,6 +376,13 @@ public class BridgeSecurity {
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
public void removeHttpsSettings() {
|
||||
securityDescriptor.setUseHttps(false);
|
||||
securityDescriptor.setKeyfilePassword(null);
|
||||
securityDescriptor.setKeyfilePath(null);
|
||||
setSettingsChanged(true);
|
||||
}
|
||||
|
||||
public void removeTestUsers() {
|
||||
if (securityDescriptor.getWhitelist() != null) {
|
||||
@@ -324,7 +401,7 @@ public class BridgeSecurity {
|
||||
}
|
||||
}
|
||||
|
||||
private String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException {
|
||||
static String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException {
|
||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
|
||||
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(habridgeKey));
|
||||
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
|
||||
@@ -336,7 +413,7 @@ public class BridgeSecurity {
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
|
||||
private String decrypt(String property) throws GeneralSecurityException, IOException {
|
||||
static String decrypt(String property) throws GeneralSecurityException, IOException {
|
||||
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
|
||||
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(habridgeKey));
|
||||
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
|
||||
|
||||
@@ -9,7 +9,10 @@ public class BridgeSecurityDescriptor {
|
||||
private String execGarden;
|
||||
private boolean secureHueApi;
|
||||
private Map<String, WhitelistEntry> whitelist;
|
||||
|
||||
private boolean useHttps;
|
||||
private String keyfilePassword;
|
||||
private String keyfilePath;
|
||||
|
||||
public BridgeSecurityDescriptor() {
|
||||
super();
|
||||
this.setUseLinkButton(false);
|
||||
@@ -67,4 +70,28 @@ public class BridgeSecurityDescriptor {
|
||||
return secureFlag;
|
||||
|
||||
}
|
||||
|
||||
public boolean isUseHttps() {
|
||||
return useHttps;
|
||||
}
|
||||
|
||||
public void setUseHttps(boolean useHttps) {
|
||||
this.useHttps = useHttps;
|
||||
}
|
||||
|
||||
public String getKeyfilePassword() {
|
||||
return keyfilePassword;
|
||||
}
|
||||
|
||||
public void setKeyfilePassword(String keyfilePassword) {
|
||||
this.keyfilePassword = keyfilePassword;
|
||||
}
|
||||
|
||||
public String getKeyfilePath() {
|
||||
return keyfilePath;
|
||||
}
|
||||
|
||||
public void setKeyfilePath(String keyfilePath) {
|
||||
this.keyfilePath = keyfilePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +200,10 @@ public class BridgeSettings extends BackupHandler {
|
||||
theBridgeSettings.setNumberoflogmessages(Integer.valueOf(Configuration.NUMBER_OF_LOG_MESSAGES));
|
||||
|
||||
if(theBridgeSettings.getButtonsleep() == null || theBridgeSettings.getButtonsleep() < 0)
|
||||
theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP));
|
||||
theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP));
|
||||
|
||||
if(theBridgeSettings.getLinkbuttontimeout() < 30)
|
||||
theBridgeSettings.setLinkbuttontimeout(Configuration.LINK_BUTTON_TIMEOUT);
|
||||
|
||||
theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera());
|
||||
theBridgeSettings.setFibaroconfigured(theBridgeSettings.isValidFibaro());
|
||||
@@ -250,10 +253,10 @@ public class BridgeSettings extends BackupHandler {
|
||||
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());
|
||||
} catch (Exception e) {
|
||||
log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed. Using default settings.");
|
||||
theBridgeSettings = new BridgeSettingsDescriptor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,19 @@ public class BridgeSettingsDescriptor {
|
||||
@SerializedName("seedid")
|
||||
@Expose
|
||||
private Integer seedid;
|
||||
@SerializedName("haaddressessecured")
|
||||
@Expose
|
||||
private boolean haaddressessecured;
|
||||
@SerializedName("upnpadvanced")
|
||||
@Expose
|
||||
private boolean upnpadvanced;
|
||||
@SerializedName("linkbuttontimeout")
|
||||
@Expose
|
||||
private Integer linkbuttontimeout;
|
||||
@SerializedName("uidnineoctets")
|
||||
@Expose
|
||||
private boolean uidnineoctets;
|
||||
|
||||
// @SerializedName("activeloggers")
|
||||
// @Expose
|
||||
// private List<NameValue> activeloggers;
|
||||
@@ -188,6 +201,11 @@ public class BridgeSettingsDescriptor {
|
||||
this.tracestate = false;
|
||||
this.upnporiginal = false;
|
||||
this.seedid = 100;
|
||||
this.haaddressessecured = false;
|
||||
this.configfile = Configuration.CONFIG_FILE;
|
||||
this.upnpadvanced = false;
|
||||
this.linkbuttontimeout = Configuration.LINK_BUTTON_TIMEOUT;
|
||||
this.uidnineoctets = false;
|
||||
}
|
||||
|
||||
public String getUpnpConfigAddress() {
|
||||
@@ -255,6 +273,7 @@ public class BridgeSettingsDescriptor {
|
||||
}
|
||||
|
||||
public IpList getVeraAddress() {
|
||||
|
||||
return veraaddress;
|
||||
}
|
||||
|
||||
@@ -835,4 +854,35 @@ public class BridgeSettingsDescriptor {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isHaaddressessecured() {
|
||||
return haaddressessecured;
|
||||
}
|
||||
|
||||
public void setHaaddressessecured(boolean haaddressessecured) {
|
||||
this.haaddressessecured = haaddressessecured;
|
||||
}
|
||||
|
||||
public boolean isUpnpadvanced() {
|
||||
return upnpadvanced;
|
||||
}
|
||||
|
||||
public void setUpnpadvanced(boolean upnpadvanced) {
|
||||
this.upnpadvanced = upnpadvanced;
|
||||
}
|
||||
|
||||
public Integer getLinkbuttontimeout() {
|
||||
return linkbuttontimeout;
|
||||
}
|
||||
|
||||
public void setLinkbuttontimeout(Integer linkbuttontimeout) {
|
||||
this.linkbuttontimeout = linkbuttontimeout;
|
||||
}
|
||||
|
||||
public boolean isUidnineoctets() {
|
||||
return uidnineoctets;
|
||||
}
|
||||
|
||||
public void setUidnineoctets(boolean uidnineoctets) {
|
||||
this.uidnineoctets = uidnineoctets;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,5 @@ public class Configuration {
|
||||
public static final int UPNP_SEND_DELAY = 650;
|
||||
public static final int BROADLINK_DISCOVER_PORT = 40000;
|
||||
public static final int BROADLINK_DISCONVER_TIMEOUT = 5000;
|
||||
public static final int LINK_BUTTON_TIMEOUT = 45;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.bwssystems.HABridge.upnp.UpnpSettingsResource;
|
||||
import com.bwssystems.HABridge.util.UDPDatagramSender;
|
||||
|
||||
public class HABridge {
|
||||
private static SystemControl theSystem;
|
||||
|
||||
/*
|
||||
* This program is based on the work of armzilla from this github repository:
|
||||
@@ -39,7 +40,6 @@ public class HABridge {
|
||||
UDPDatagramSender udpSender;
|
||||
UpnpSettingsResource theSettingResponder;
|
||||
UpnpListener theUpnpListener;
|
||||
SystemControl theSystem;
|
||||
BridgeSettings bridgeSettings;
|
||||
Version theVersion;
|
||||
@SuppressWarnings("unused")
|
||||
@@ -54,9 +54,13 @@ public class HABridge {
|
||||
bridgeSettings = new BridgeSettings();
|
||||
// sparkjava config directive to set html static file location for Jetty
|
||||
while(!bridgeSettings.getBridgeControl().isStop()) {
|
||||
bridgeSettings.buildSettings();
|
||||
bridgeSettings.getBridgeSecurity().removeTestUsers();
|
||||
log.info("HA Bridge (v{}) initializing....", theVersion.getVersion() );
|
||||
bridgeSettings.buildSettings();
|
||||
if(bridgeSettings.getBridgeSecurity().isUseHttps()) {
|
||||
secure(bridgeSettings.getBridgeSecurity().getKeyfilePath(), bridgeSettings.getBridgeSecurity().getKeyfilePassword(), null, null);
|
||||
log.info("Using https for web and api calls");
|
||||
}
|
||||
bridgeSettings.getBridgeSecurity().removeTestUsers();
|
||||
// sparkjava config directive to set ip address for the web server to listen on
|
||||
ipAddress(bridgeSettings.getBridgeSettingsDescriptor().getWebaddress());
|
||||
// sparkjava config directive to set port for the web server to listen on
|
||||
@@ -108,7 +112,7 @@ public class HABridge {
|
||||
// start the upnp ssdp discovery listener
|
||||
theUpnpListener = null;
|
||||
try {
|
||||
theUpnpListener = new UpnpListener(bridgeSettings.getBridgeSettingsDescriptor(), bridgeSettings.getBridgeControl(), udpSender);
|
||||
theUpnpListener = new UpnpListener(bridgeSettings, bridgeSettings.getBridgeControl(), udpSender);
|
||||
} catch (IOException e) {
|
||||
log.error("Could not initialize UpnpListener, exiting....", e);
|
||||
theUpnpListener = null;
|
||||
@@ -149,8 +153,14 @@ public class HABridge {
|
||||
}
|
||||
|
||||
private static void theExceptionHandler(Exception e, Integer thePort) {
|
||||
Logger log = LoggerFactory.getLogger(HABridge.class);
|
||||
log.error("Could not start ha-bridge webservice on port [{}] due to: {}", thePort, e.getMessage());
|
||||
System.exit(0);
|
||||
}
|
||||
Logger log = LoggerFactory.getLogger(HABridge.class);
|
||||
if(e.getMessage().equals("no valid keystore") || e.getMessage().equals("keystore password was incorrect")) {
|
||||
log.error("Https settings have been removed as {}. Restart system manually after this process exits....", e.getMessage());
|
||||
log.warn(theSystem.removeHttpsSettings());
|
||||
}
|
||||
else {
|
||||
log.error("Could not start ha-bridge webservice on port [{}] due to: {}", thePort, e.getMessage());
|
||||
log.warn(theSystem.stop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ public class SecurityInfo {
|
||||
private boolean useLinkButton;
|
||||
private boolean secureHueApi;
|
||||
private boolean isSecure;
|
||||
private String execGarden;
|
||||
private boolean useHttps;
|
||||
private String keyfilePath;
|
||||
private String keyfilePassword;
|
||||
|
||||
public boolean isUseLinkButton() {
|
||||
return useLinkButton;
|
||||
@@ -23,4 +27,36 @@ public class SecurityInfo {
|
||||
public void setSecure(boolean isSecure) {
|
||||
this.isSecure = isSecure;
|
||||
}
|
||||
|
||||
public boolean isUseHttps() {
|
||||
return useHttps;
|
||||
}
|
||||
|
||||
public void setUseHttps(boolean useHttps) {
|
||||
this.useHttps = useHttps;
|
||||
}
|
||||
|
||||
public String getKeyfilePath() {
|
||||
return keyfilePath;
|
||||
}
|
||||
|
||||
public void setKeyfilePath(String keyfilePath) {
|
||||
this.keyfilePath = keyfilePath;
|
||||
}
|
||||
|
||||
public String getExecGarden() {
|
||||
return execGarden;
|
||||
}
|
||||
|
||||
public void setExecGarden(String execGarden) {
|
||||
this.execGarden = execGarden;
|
||||
}
|
||||
|
||||
public String getKeyfilePassword() {
|
||||
return keyfilePassword;
|
||||
}
|
||||
|
||||
public void setKeyfilePassword(String keyfilePassword) {
|
||||
this.keyfilePassword = keyfilePassword;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,12 +276,12 @@ public class SystemControl {
|
||||
if(!request.body().isEmpty()) {
|
||||
linkParams = new Gson().fromJson(request.body(), LinkParams.class);
|
||||
if(linkParams.getSeconds() <= 0)
|
||||
linkParams.setSeconds(1);
|
||||
linkParams.setSeconds(3);
|
||||
}
|
||||
else {
|
||||
linkParams = new LinkParams();
|
||||
linkParams.setSilent(false);
|
||||
linkParams.setSeconds(30);
|
||||
linkParams.setSeconds(bridgeSettings.getBridgeSettingsDescriptor().getLinkbuttontimeout());
|
||||
}
|
||||
if(!linkParams.isSilent())
|
||||
log.info("Link button pressed....");
|
||||
@@ -314,8 +314,7 @@ public class SystemControl {
|
||||
post(SYSTEM_CONTEXT + "/changesecurityinfo", (request, response) -> {
|
||||
log.debug("changesecurityinfo....");
|
||||
SecurityInfo theInfo = new Gson().fromJson(request.body(), SecurityInfo.class);
|
||||
bridgeSettings.getBridgeSecurity().setUseLinkButton(theInfo.isUseLinkButton());
|
||||
bridgeSettings.getBridgeSecurity().setSecureHueApi(theInfo.isSecureHueApi());
|
||||
bridgeSettings.getBridgeSecurity().setSecurityDataByInfo(theInfo);
|
||||
bridgeSettings.save(bridgeSettings.getBridgeSettingsDescriptor());
|
||||
response.status(HttpStatus.SC_OK);
|
||||
response.type("application/json");
|
||||
@@ -599,7 +598,12 @@ public class SystemControl {
|
||||
log.warn("Error pinging listener. " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String removeHttpsSettings() {
|
||||
bridgeSettings.getBridgeSecurity().removeHttpsSettings();
|
||||
return stop();
|
||||
}
|
||||
|
||||
public String reinit() {
|
||||
bridgeSettings.getBridgeControl().setReinit(true);
|
||||
pingListener();
|
||||
|
||||
@@ -17,6 +17,7 @@ public class DeviceResponse {
|
||||
private String swversion;
|
||||
private String swconfigid;
|
||||
private String productid;
|
||||
private String productname;
|
||||
|
||||
public DeviceState getState() {
|
||||
return state;
|
||||
@@ -90,6 +91,14 @@ public class DeviceResponse {
|
||||
this.productid = productid;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productname;
|
||||
}
|
||||
|
||||
public void setProductName(String productname) {
|
||||
this.productname = productname;
|
||||
}
|
||||
|
||||
|
||||
public String getLuminaireuniqueid() {
|
||||
return luminaireuniqueid;
|
||||
@@ -109,10 +118,11 @@ public class DeviceResponse {
|
||||
|
||||
if (device.isColorDevice()) {
|
||||
response.setType("Extended color light");
|
||||
response.setModelid("LCT010");
|
||||
response.setSwversion("1.15.2_r19181");
|
||||
response.setSwconfigid("F921C859");
|
||||
response.setProductid("Philips-LCT010-1-A19ECLv4");
|
||||
response.setModelid("LCT015");
|
||||
response.setSwversion("1.46.13_r26312");
|
||||
response.setSwconfigid("52E3234B");
|
||||
response.setProductid("Philips-LCT015-1-A19ECLv5");
|
||||
response.setProductName("Hue color lamp");
|
||||
} else {
|
||||
response.setType("Dimmable light");
|
||||
response.setModelid("LWB007");
|
||||
@@ -129,13 +139,14 @@ public class DeviceResponse {
|
||||
response.setState(group.getAction());
|
||||
|
||||
response.setName(group.getName());
|
||||
response.setUniqueid("00:17:88:5E:D3:FF-" + String.format("%02X", Integer.parseInt(group.getId())));
|
||||
response.setUniqueid("00:11:22:33:44:55:66:77-" + String.format("%02X", Integer.parseInt(group.getId())));
|
||||
response.setManufacturername("Philips");
|
||||
response.setType("Extended color light");
|
||||
response.setModelid("LCT010");
|
||||
response.setSwversion("1.15.2_r19181");
|
||||
response.setSwconfigid("F921C859");
|
||||
response.setProductid("Philips-LCT010-1-A19ECLv4");
|
||||
response.setModelid("LCT015");
|
||||
response.setSwversion("1.46.13_r26312");
|
||||
response.setSwconfigid("52E3234B");
|
||||
response.setProductid("Philips-LCT015-1-A19ECLv5");
|
||||
response.setProductName("Hue color lamp");
|
||||
|
||||
response.setLuminaireuniqueid(null);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.bwssystems.HABridge.api.hue;
|
||||
|
||||
public class HueConstants {
|
||||
public final static String HUB_VERSION = "9999999999";
|
||||
public final static String API_VERSION = "1.19.0";
|
||||
public final static String API_VERSION = "1.17.0";
|
||||
public final static String MODEL_ID = "BSB002";
|
||||
public final static String UUID_PREFIX = "2f402f80-da50-11e1-9b23-";
|
||||
}
|
||||
|
||||
@@ -92,7 +92,13 @@ public class DeviceDescriptor{
|
||||
@SerializedName("startupActions")
|
||||
@Expose
|
||||
private String startupActions;
|
||||
|
||||
@SerializedName("dimNoOn")
|
||||
@Expose
|
||||
private boolean dimNoOn;
|
||||
@SerializedName("dimOnColor")
|
||||
@Expose
|
||||
private boolean dimOnColor;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -355,4 +361,20 @@ public class DeviceDescriptor{
|
||||
public void setStartupActions(String startupActions) {
|
||||
this.startupActions = startupActions;
|
||||
}
|
||||
|
||||
public boolean isDimNoOn() {
|
||||
return dimNoOn;
|
||||
}
|
||||
|
||||
public void setDimNoOn(boolean dimNoOn) {
|
||||
this.dimNoOn = dimNoOn;
|
||||
}
|
||||
|
||||
public boolean isDimOnColor() {
|
||||
return dimOnColor;
|
||||
}
|
||||
|
||||
public void setDimOnColor(boolean dimOnColor) {
|
||||
this.dimOnColor = dimOnColor;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import com.bwssystems.HABridge.DeviceMapTypes;
|
||||
import com.bwssystems.HABridge.api.CallItem;
|
||||
import com.bwssystems.HABridge.api.hue.DeviceResponse;
|
||||
import com.bwssystems.HABridge.api.hue.DeviceState;
|
||||
import com.bwssystems.HABridge.dao.DeviceDescriptor;
|
||||
// import com.bwssystems.HABridge.dao.DeviceDescriptor;
|
||||
import com.bwssystems.HABridge.plugins.hue.HueHome;
|
||||
import com.bwssystems.HABridge.util.BackupHandler;
|
||||
import com.bwssystems.HABridge.util.JsonTransformer;
|
||||
@@ -30,6 +30,8 @@ import com.google.gson.JsonSyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/*
|
||||
* This is an in memory list to manage the configured devices and saves the list as a JSON string to a file for later
|
||||
@@ -41,9 +43,10 @@ public class DeviceRepository extends BackupHandler {
|
||||
private Gson gson;
|
||||
private Integer nextId;
|
||||
private Integer seedId;
|
||||
private boolean uidnineoctets;
|
||||
private Logger log = LoggerFactory.getLogger(DeviceRepository.class);
|
||||
|
||||
public DeviceRepository(String deviceDb, Integer seedid) {
|
||||
public DeviceRepository(String deviceDb, Integer seedid, boolean uidnineoctets_setting) {
|
||||
super();
|
||||
gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
|
||||
repositoryPath = null;
|
||||
@@ -51,6 +54,7 @@ public class DeviceRepository extends BackupHandler {
|
||||
setupParams(repositoryPath, ".bk", "device.db-");
|
||||
nextId = seedid;
|
||||
seedId = seedid;
|
||||
uidnineoctets = uidnineoctets_setting;
|
||||
_loadRepository(repositoryPath);
|
||||
}
|
||||
|
||||
@@ -75,6 +79,8 @@ public class DeviceRepository extends BackupHandler {
|
||||
nextId = Integer.decode(list[i].getId());
|
||||
}
|
||||
}
|
||||
|
||||
nextId = nextId + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,9 +192,7 @@ public class DeviceRepository extends BackupHandler {
|
||||
nextId++;
|
||||
}
|
||||
if (descriptors[i].getUniqueid() == null || descriptors[i].getUniqueid().length() == 0) {
|
||||
String hexValue = HexLibrary.encodeUsingBigIntegerToString(descriptors[i].getId());
|
||||
|
||||
descriptors[i].setUniqueid("00:17:88:5E:D3:" + hexValue + "-" + hexValue);
|
||||
descriptors[i].setUniqueid(hueUniqueId(Integer.valueOf(descriptors[i].getId())));
|
||||
}
|
||||
put(descriptors[i].getId(), descriptors[i]);
|
||||
theNames = theNames + " " + descriptors[i].getName() + ", ";
|
||||
@@ -203,16 +207,13 @@ public class DeviceRepository extends BackupHandler {
|
||||
Iterator<DeviceDescriptor> deviceIterator = list.iterator();
|
||||
Map<String, DeviceDescriptor> newdevices = new HashMap<String, DeviceDescriptor>();
|
||||
List<String> lockedIds = new ArrayList<String>();
|
||||
String hexValue;
|
||||
Integer newValue;
|
||||
DeviceDescriptor theDevice;
|
||||
boolean findNext = true;
|
||||
|
||||
|
||||
nextId = seedId;
|
||||
while(deviceIterator.hasNext()) {
|
||||
while (deviceIterator.hasNext()) {
|
||||
theDevice = deviceIterator.next();
|
||||
if(theDevice.isLockDeviceId()) {
|
||||
if (theDevice.isLockDeviceId()) {
|
||||
lockedIds.add(theDevice.getId());
|
||||
}
|
||||
}
|
||||
@@ -222,22 +223,15 @@ public class DeviceRepository extends BackupHandler {
|
||||
theDevice = deviceIterator.next();
|
||||
if (!theDevice.isLockDeviceId()) {
|
||||
findNext = true;
|
||||
while(findNext) {
|
||||
if(lockedIds.contains(String.valueOf(nextId))) {
|
||||
while (findNext) {
|
||||
if (lockedIds.contains(String.valueOf(nextId))) {
|
||||
nextId++;
|
||||
} else {
|
||||
findNext = false;
|
||||
}
|
||||
}
|
||||
theDevice.setId(String.valueOf(nextId));
|
||||
newValue = nextId % 256;
|
||||
if (newValue <= 0)
|
||||
newValue = 1;
|
||||
else if (newValue > 255)
|
||||
newValue = 255;
|
||||
hexValue = HexLibrary.encodeUsingBigIntegerToString(newValue.toString());
|
||||
|
||||
theDevice.setUniqueid("00:17:88:5E:D3:" + hexValue + "-" + hexValue);
|
||||
theDevice.setUniqueid(hueUniqueId(nextId));
|
||||
nextId++;
|
||||
}
|
||||
newdevices.put(theDevice.getId(), theDevice);
|
||||
@@ -304,4 +298,66 @@ public class DeviceRepository extends BackupHandler {
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
private String hueUniqueId(Integer anId) {
|
||||
String theUniqueId = null;
|
||||
Integer newValue;
|
||||
String hexValueLeft;
|
||||
String hexValueRight;
|
||||
|
||||
MessageDigest md = null;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
log.warn("Cannot get MD5 utility to hash unique ids.");
|
||||
}
|
||||
|
||||
if (md != null) {
|
||||
md.update(anId.toString().getBytes());
|
||||
byte[] digest = md.digest();
|
||||
if (uidnineoctets) {
|
||||
theUniqueId = String.format("00:%s:%s:%s:%s:%s:%s:%s-%s",
|
||||
HexLibrary.encodeHexString(digest).substring(0, 2),
|
||||
HexLibrary.encodeHexString(digest).substring(2, 4),
|
||||
HexLibrary.encodeHexString(digest).substring(4, 6),
|
||||
HexLibrary.encodeHexString(digest).substring(6, 8),
|
||||
HexLibrary.encodeHexString(digest).substring(8, 10),
|
||||
HexLibrary.encodeHexString(digest).substring(10, 12),
|
||||
HexLibrary.encodeHexString(digest).substring(12, 14),
|
||||
HexLibrary.encodeHexString(digest).substring(14, 16));
|
||||
|
||||
} else {
|
||||
theUniqueId = String.format("%s:%s:%s:%s:%s:%s:%s-%s",
|
||||
HexLibrary.encodeHexString(digest).substring(0, 2),
|
||||
HexLibrary.encodeHexString(digest).substring(2, 4),
|
||||
HexLibrary.encodeHexString(digest).substring(4, 6),
|
||||
HexLibrary.encodeHexString(digest).substring(6, 8),
|
||||
HexLibrary.encodeHexString(digest).substring(8, 10),
|
||||
HexLibrary.encodeHexString(digest).substring(10, 12),
|
||||
HexLibrary.encodeHexString(digest).substring(12, 14),
|
||||
HexLibrary.encodeHexString(digest).substring(14, 16));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (theUniqueId == null) {
|
||||
newValue = anId % 256;
|
||||
if (newValue <= 0)
|
||||
newValue = 1;
|
||||
else if (newValue > 255)
|
||||
newValue = 255;
|
||||
hexValueLeft = HexLibrary.byteToHex(newValue.byteValue());
|
||||
newValue = anId / 256;
|
||||
newValue = newValue % 256;
|
||||
if (newValue < 0)
|
||||
newValue = 0;
|
||||
else if (newValue > 255)
|
||||
newValue = 255;
|
||||
hexValueRight = HexLibrary.byteToHex(newValue.byteValue());
|
||||
|
||||
theUniqueId = String.format("11:22:33:44:55:66:%s-%s", hexValueLeft, hexValueRight).toUpperCase();
|
||||
}
|
||||
return theUniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,11 +201,15 @@ public class GroupRepository extends BackupHandler {
|
||||
private String repositoryReader(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...");
|
||||
if(Files.notExists(filePath)){
|
||||
log.debug("Error, the file: " + filePath + " - does not exist. continuing...");
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!Files.isReadable(filePath)){
|
||||
log.warn("Error, the file: " + filePath + " - is not readable. continuing...");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
content = new String(Files.readAllBytes(filePath));
|
||||
|
||||
@@ -48,7 +48,7 @@ public class DeviceResource {
|
||||
|
||||
public DeviceResource(BridgeSettings theSettings, HomeManager aHomeManager) {
|
||||
bridgeSettings = theSettings;
|
||||
this.deviceRepository = new DeviceRepository(bridgeSettings.getBridgeSettingsDescriptor().getUpnpDeviceDb(), bridgeSettings.getBridgeSettingsDescriptor().getSeedid());
|
||||
this.deviceRepository = new DeviceRepository(bridgeSettings.getBridgeSettingsDescriptor().getUpnpDeviceDb(), bridgeSettings.getBridgeSettingsDescriptor().getSeedid(), bridgeSettings.getBridgeSettingsDescriptor().isUidnineoctets());
|
||||
this.groupRepository = new GroupRepository(bridgeSettings.getBridgeSettingsDescriptor().getUpnpGroupDb());
|
||||
homeManager = aHomeManager;
|
||||
aGsonHandler = new GsonBuilder().create();
|
||||
|
||||
@@ -21,12 +21,15 @@ public class BrightnessDecode {
|
||||
private static final String INTENSITY_MATH_CLOSE_HEX = ").hex}";
|
||||
private static final String INTENSITY_PERCENT_HEX = "${intensity.percent.hex}";
|
||||
private static final String INTENSITY_BYTE_HEX = "${intensity.byte.hex}";
|
||||
private static final String INTENSITY_PREVIOUS_PERCENT = "${intensity.previous_percent}";
|
||||
private static final String INTENSITY_PREVIOUS_DECIMAL_PERCENT = "${intensity.previous_decimal_percent}";
|
||||
private static final String INTENSITY_PREVIOUS_BYTE = "${intensity.previous_byte}";
|
||||
|
||||
public static int calculateIntensity(int setIntensity, Integer targetBri, Integer targetBriInc) {
|
||||
if (targetBri != null) {
|
||||
setIntensity = targetBri;
|
||||
} else if (targetBriInc != null) {
|
||||
if ((setIntensity + targetBriInc) <= 0)
|
||||
if ((setIntensity + targetBriInc) <= 1)
|
||||
setIntensity = targetBriInc;
|
||||
else if ((setIntensity + targetBriInc) > 254)
|
||||
setIntensity = targetBriInc;
|
||||
@@ -45,7 +48,7 @@ public class BrightnessDecode {
|
||||
* intensity.math(X*1) : where X is the value from the interface call and
|
||||
* can use net.java.dev.eval math
|
||||
*/
|
||||
public static String replaceIntensityValue(String request, int intensity, boolean isHex) {
|
||||
private static String replaceIntensityValue(String request, int previous_intensity, int intensity, boolean isHex) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -53,7 +56,9 @@ public class BrightnessDecode {
|
||||
String replaceValue = null;
|
||||
String replaceTarget = null;
|
||||
int percentBrightness = 0;
|
||||
float decimalBrightness = (float) 0.0;
|
||||
float decimalBrightness = (float) 1.0;
|
||||
int previousPercentBrightness = 0;
|
||||
float previousDecimalBrightness = (float) 1.0;
|
||||
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
|
||||
String mathDescriptor = null;
|
||||
|
||||
@@ -64,8 +69,19 @@ public class BrightnessDecode {
|
||||
else
|
||||
percentBrightness = (int) Math.round(intensity / 255.0 * 100);
|
||||
} else {
|
||||
decimalBrightness = (float) 0.0;
|
||||
percentBrightness = 0;
|
||||
decimalBrightness = (float) 1.0;
|
||||
percentBrightness = 1;
|
||||
}
|
||||
|
||||
if(previous_intensity > 0) {
|
||||
previousDecimalBrightness = (float) (previous_intensity / 255.0);
|
||||
if(previous_intensity > 0 && previous_intensity < 5)
|
||||
previousPercentBrightness = 1;
|
||||
else
|
||||
previousPercentBrightness = (int) Math.round(previous_intensity / 255.0 * 100);
|
||||
} else {
|
||||
previousDecimalBrightness = (float) 1.0;
|
||||
previousPercentBrightness = 1;
|
||||
}
|
||||
|
||||
while(notDone) {
|
||||
@@ -78,6 +94,14 @@ public class BrightnessDecode {
|
||||
}
|
||||
replaceTarget = INTENSITY_BYTE;
|
||||
notDone = true;
|
||||
} else if (request.contains(INTENSITY_PREVIOUS_BYTE)) {
|
||||
if (isHex) {
|
||||
replaceValue = convertToHex(previous_intensity);
|
||||
} else {
|
||||
replaceValue = String.valueOf(previous_intensity);
|
||||
}
|
||||
replaceTarget = INTENSITY_PREVIOUS_BYTE;
|
||||
notDone = true;
|
||||
} else if (request.contains(INTENSITY_BYTE_HEX)) {
|
||||
replaceValue = convertToHex(intensity);
|
||||
replaceTarget = INTENSITY_BYTE_HEX;
|
||||
@@ -90,6 +114,14 @@ public class BrightnessDecode {
|
||||
}
|
||||
replaceTarget = INTENSITY_PERCENT;
|
||||
notDone = true;
|
||||
} else if (request.contains(INTENSITY_PREVIOUS_PERCENT)) {
|
||||
if (isHex) {
|
||||
replaceValue = convertToHex(previousPercentBrightness);
|
||||
} else {
|
||||
replaceValue = String.valueOf(previousPercentBrightness);
|
||||
}
|
||||
replaceTarget = INTENSITY_PREVIOUS_PERCENT;
|
||||
notDone = true;
|
||||
} else if (request.contains(INTENSITY_PERCENT_HEX)) {
|
||||
replaceValue = convertToHex(percentBrightness);
|
||||
replaceTarget = INTENSITY_PERCENT_HEX;
|
||||
@@ -98,6 +130,10 @@ public class BrightnessDecode {
|
||||
replaceValue = String.format(Locale.ROOT, "%1.2f", decimalBrightness);
|
||||
replaceTarget = INTENSITY_DECIMAL_PERCENT;
|
||||
notDone = true;
|
||||
} else if (request.contains(INTENSITY_PREVIOUS_DECIMAL_PERCENT)) {
|
||||
replaceValue = String.format(Locale.ROOT, "%1.2f", previousDecimalBrightness);
|
||||
replaceTarget = INTENSITY_PREVIOUS_DECIMAL_PERCENT;
|
||||
notDone = true;
|
||||
} else if (request.contains(INTENSITY_MATH_CLOSE)) {
|
||||
mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),
|
||||
request.indexOf(INTENSITY_MATH_CLOSE));
|
||||
@@ -135,7 +171,7 @@ public class BrightnessDecode {
|
||||
|
||||
// Helper Method
|
||||
public static String calculateReplaceIntensityValue(String request, int theIntensity, Integer targetBri, Integer targetBriInc, boolean isHex) {
|
||||
return replaceIntensityValue(request, calculateIntensity(theIntensity, targetBri, targetBriInc), isHex);
|
||||
return replaceIntensityValue(request, theIntensity, calculateIntensity(theIntensity, targetBri, targetBriInc), isHex);
|
||||
}
|
||||
|
||||
// Apache Commons Conversion utils likes little endian too much
|
||||
|
||||
954
src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java
Normal file
954
src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java
Normal file
@@ -0,0 +1,954 @@
|
||||
package com.bwssystems.HABridge.hue;
|
||||
|
||||
/**
|
||||
* Convert between different color spaces supported.
|
||||
* RGB -> CMYK -> RGB
|
||||
* RGB -> YIQ -> RGB
|
||||
* RGB -> YCbCr -> RGB
|
||||
* RGB -> YUV -> RGB
|
||||
* RGB -> RGChromaticity
|
||||
* RGB -> HSV -> RGB
|
||||
* RGB -> YCC -> RGB
|
||||
* RGB -> YCoCg -> RGB
|
||||
* RGB -> XYZ -> RGB
|
||||
* RGB -> HunterLAB -> RGB
|
||||
* RGB -> HLS -> RGB
|
||||
* RGB -> CIE-LAB -> RGB
|
||||
* XYZ -> HunterLAB -> XYZ
|
||||
* XYZ -> CIE-LAB -> XYZ
|
||||
* @author Diego Catalano
|
||||
*/
|
||||
|
||||
|
||||
public class ColorConverter {
|
||||
|
||||
/**
|
||||
* Don't let anyone instantiate this class.
|
||||
*/
|
||||
private ColorConverter() {}
|
||||
|
||||
public static enum YCbCrColorSpace {ITU_BT_601,ITU_BT_709_HDTV};
|
||||
private final static double EPSILON = 0.00001;
|
||||
|
||||
// XYZ (Tristimulus) Reference values of a perfect reflecting diffuser
|
||||
|
||||
//2o Observer (CIE 1931)
|
||||
// X2, Y2, Z2
|
||||
public static float[] CIE2_A = {109.850f, 100f, 35.585f}; //Incandescent
|
||||
public static float[] CIE2_C = {98.074f, 100f, 118.232f};
|
||||
public static float[] CIE2_D50 = {96.422f, 100f, 82.521f};
|
||||
public static float[] CIE2_D55 = {95.682f, 100f, 92.149f};
|
||||
public static float[] CIE2_D65 = {95.047f, 100f, 108.883f}; //Daylight
|
||||
public static float[] CIE2_D75 = {94.972f, 100f, 122.638f};
|
||||
public static float[] CIE2_F2 = {99.187f, 100f, 67.395f}; //Fluorescent
|
||||
public static float[] CIE2_F7 = {95.044f, 100f, 108.755f};
|
||||
public static float[] CIE2_F11 = {100.966f, 100f, 64.370f};
|
||||
|
||||
//10o Observer (CIE 1964)
|
||||
// X2, Y2, Z2
|
||||
public static float[] CIE10_A = {111.144f, 100f, 35.200f}; //Incandescent
|
||||
public static float[] CIE10_C = {97.285f, 100f, 116.145f};
|
||||
public static float[] CIE10_D50 = {96.720f, 100f, 81.427f};
|
||||
public static float[] CIE10_D55 = {95.799f, 100f, 90.926f};
|
||||
public static float[] CIE10_D65 = {94.811f, 100f, 107.304f}; //Daylight
|
||||
public static float[] CIE10_D75 = {94.416f, 100f, 120.641f};
|
||||
public static float[] CIE10_F2 = {103.280f, 100f, 69.026f}; //Fluorescent
|
||||
public static float[] CIE10_F7 = {95.792f, 100f, 107.687f};
|
||||
public static float[] CIE10_F11 = {103.866f, 100f, 65.627f};
|
||||
|
||||
/**
|
||||
* RFB -> CMYK
|
||||
* @param red Values in the range [0..255].
|
||||
* @param green Values in the range [0..255].
|
||||
* @param blue Values in the range [0..255].
|
||||
* @return CMYK color space. Normalized.
|
||||
*/
|
||||
public static float[] RGBtoCMYK(int red, int green, int blue){
|
||||
float[] cmyk = new float[4];
|
||||
|
||||
float r = red / 255f;
|
||||
float g = green / 255f;
|
||||
float b = blue / 255f;
|
||||
|
||||
float k = 1.0f - Math.max(r, Math.max(g, b));
|
||||
float c = (1f-r-k) / (1f-k);
|
||||
float m = (1f-g-k) / (1f-k);
|
||||
float y = (1f-b-k) / (1f-k);
|
||||
|
||||
cmyk[0] = c;
|
||||
cmyk[1] = m;
|
||||
cmyk[2] = y;
|
||||
cmyk[3] = k;
|
||||
|
||||
return cmyk;
|
||||
}
|
||||
|
||||
/**
|
||||
* CMYK -> RGB
|
||||
* @param c Cyan.
|
||||
* @param m Magenta.
|
||||
* @param y Yellow.
|
||||
* @param k Black.
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] CMYKtoRGB(float c, float m, float y, float k){
|
||||
int[] rgb = new int[3];
|
||||
|
||||
rgb[0] = (int)(255 * (1-c) * (1-k));
|
||||
rgb[1] = (int)(255 * (1-m) * (1-k));
|
||||
rgb[2] = (int)(255 * (1-y) * (1-k));
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> YUV.
|
||||
* Y in the range [0..1].
|
||||
* U in the range [-0.5..0.5].
|
||||
* V in the range [-0.5..0.5].
|
||||
* @param red Values in the range [0..255].
|
||||
* @param green Values in the range [0..255].
|
||||
* @param blue Values in the range [0..255].
|
||||
* @return YUV color space.
|
||||
*/
|
||||
public static float[] RGBtoYUV(int red, int green, int blue){
|
||||
|
||||
float r = (float)red / 255;
|
||||
float g = (float)green / 255;
|
||||
float b = (float)blue / 255;
|
||||
|
||||
float[] yuv = new float[3];
|
||||
float y,u,v;
|
||||
|
||||
y = (float)(0.299 * r + 0.587 * g + 0.114 * b);
|
||||
u = (float)(-0.14713 * r - 0.28886 * g + 0.436 * b);
|
||||
v = (float)(0.615 * r - 0.51499 * g - 0.10001 * b);
|
||||
|
||||
yuv[0] = y;
|
||||
yuv[1] = u;
|
||||
yuv[2] = v;
|
||||
|
||||
return yuv;
|
||||
}
|
||||
|
||||
/**
|
||||
* YUV -> RGB.
|
||||
* @param y Luma. In the range [0..1].
|
||||
* @param u Chrominance. In the range [-0.5..0.5].
|
||||
* @param v Chrominance. In the range [-0.5..0.5].
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] YUVtoRGB(float y, float u, float v){
|
||||
int[] rgb = new int[3];
|
||||
float r,g,b;
|
||||
|
||||
r = (float)((y + 0.000 * u + 1.140 * v) * 255);
|
||||
g = (float)((y - 0.396 * u - 0.581 * v) * 255);
|
||||
b = (float)((y + 2.029 * u + 0.000 * v) * 255);
|
||||
|
||||
rgb[0] = (int)r;
|
||||
rgb[1] = (int)g;
|
||||
rgb[2] = (int)b;
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> YIQ.
|
||||
* @param red Values in the range [0..255].
|
||||
* @param green Values in the range [0..255].
|
||||
* @param blue Values in the range [0..255].
|
||||
* @return YIQ color space.
|
||||
*/
|
||||
public static float[] RGBtoYIQ(int red, int green, int blue){
|
||||
float[] yiq = new float[3];
|
||||
float y,i,q;
|
||||
|
||||
float r = (float)red / 255;
|
||||
float g = (float)green / 255;
|
||||
float b = (float)blue / 255;
|
||||
|
||||
y = (float)(0.299 * r + 0.587 * g + 0.114 * b);
|
||||
i = (float)(0.596 * r - 0.275 * g - 0.322 * b);
|
||||
q = (float)(0.212 * r - 0.523 * g + 0.311 * b);
|
||||
|
||||
yiq[0] = y;
|
||||
yiq[1] = i;
|
||||
yiq[2] = q;
|
||||
|
||||
return yiq;
|
||||
}
|
||||
|
||||
/**
|
||||
* YIQ -> RGB.
|
||||
* @param y Luma. Values in the range [0..1].
|
||||
* @param i In-phase. Values in the range [-0.5..0.5].
|
||||
* @param q Quadrature. Values in the range [-0.5..0.5].
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] YIQtoRGB(double y, double i, double q){
|
||||
int[] rgb = new int[3];
|
||||
int r,g,b;
|
||||
|
||||
r = (int)((y + 0.956 * i + 0.621 * q) * 255);
|
||||
g = (int)((y - 0.272 * i - 0.647 * q) * 255);
|
||||
b = (int)((y - 1.105 * i + 1.702 * q) * 255);
|
||||
|
||||
r = Math.max(0,Math.min(255,r));
|
||||
g = Math.max(0,Math.min(255,g));
|
||||
b = Math.max(0,Math.min(255,b));
|
||||
|
||||
rgb[0] = r;
|
||||
rgb[1] = g;
|
||||
rgb[2] = b;
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
public static float[] RGBtoYCbCr(int red, int green, int blue, YCbCrColorSpace colorSpace){
|
||||
|
||||
float r = (float)red / 255;
|
||||
float g = (float)green / 255;
|
||||
float b = (float)blue / 255;
|
||||
|
||||
float[] YCbCr = new float[3];
|
||||
float y,cb,cr;
|
||||
|
||||
if (colorSpace == YCbCrColorSpace.ITU_BT_601) {
|
||||
y = (float)(0.299 * r + 0.587 * g + 0.114 * b);
|
||||
cb = (float)(-0.169 * r - 0.331 * g + 0.500 * b);
|
||||
cr = (float)(0.500 * r - 0.419 * g - 0.081 * b);
|
||||
}
|
||||
else{
|
||||
y = (float)(0.2215 * r + 0.7154 * g + 0.0721 * b);
|
||||
cb = (float)(-0.1145 * r - 0.3855 * g + 0.5000 * b);
|
||||
cr = (float)(0.5016 * r - 0.4556 * g - 0.0459 * b);
|
||||
}
|
||||
|
||||
YCbCr[0] = (float)y;
|
||||
YCbCr[1] = (float)cb;
|
||||
YCbCr[2] = (float)cr;
|
||||
|
||||
return YCbCr;
|
||||
}
|
||||
|
||||
public static int[] YCbCrtoRGB(float y, float cb, float cr, YCbCrColorSpace colorSpace){
|
||||
int[] rgb = new int[3];
|
||||
float r,g,b;
|
||||
|
||||
if (colorSpace == YCbCrColorSpace.ITU_BT_601) {
|
||||
r = (float)(y + 0.000 * cb + 1.403 * cr) * 255;
|
||||
g = (float)(y - 0.344 * cb - 0.714 * cr) * 255;
|
||||
b = (float)(y + 1.773 * cb + 0.000 * cr) * 255;
|
||||
}
|
||||
else{
|
||||
r = (float)(y + 0.000 * cb + 1.5701 * cr) * 255;
|
||||
g = (float)(y - 0.1870 * cb - 0.4664 * cr) * 255;
|
||||
b = (float)(y + 1.8556 * cb + 0.000 * cr) * 255;
|
||||
}
|
||||
|
||||
rgb[0] = (int)r;
|
||||
rgb[1] = (int)g;
|
||||
rgb[2] = (int)b;
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rg-Chromaticity space is already known to remove ambiguities due to illumination or surface pose.
|
||||
* @see Neural Information Processing - Chi Sing Leung. p. 668
|
||||
* @param red Red coefficient.
|
||||
* @param green Green coefficient.
|
||||
* @param blue Blue coefficient.
|
||||
* @return Normalized RGChromaticity. Range[0..1].
|
||||
*/
|
||||
public static float[] RGChromaticity(int red, int green, int blue){
|
||||
double[] color = new double[5];
|
||||
|
||||
double sum = red + green + blue;
|
||||
|
||||
//red
|
||||
color[0] = red / sum;
|
||||
|
||||
//green
|
||||
color[1] = green / sum;
|
||||
|
||||
//blue
|
||||
color[2] = 1 - color[0] - color[1];
|
||||
|
||||
double rS = color[0] - 0.333;
|
||||
double gS = color[1] - 0.333;
|
||||
|
||||
//saturationBRGBtoHSV(int red, int green, int blue){
|
||||
float[] hsv = new float[3];
|
||||
float r = red / 255f;
|
||||
float g = green / 255f;
|
||||
float b = blue / 255f;
|
||||
|
||||
float max = Math.max(r, Math.max(g, b));
|
||||
float min = Math.min(r, Math.min(g, b));
|
||||
float delta = max - min;
|
||||
|
||||
// Hue
|
||||
if (max == min){
|
||||
hsv[0] = 0;
|
||||
}
|
||||
else if (max == r){
|
||||
hsv[0] = ((g - b) / delta) * 60f;
|
||||
}
|
||||
else if (max == g){
|
||||
hsv[0] = ((b - r) / delta + 2f) * 60f;
|
||||
}
|
||||
else if (max == b){
|
||||
hsv[0] = ((r - g) / delta + 4f) * 60f;
|
||||
}
|
||||
|
||||
// Saturation
|
||||
if (delta == 0)
|
||||
hsv[1] = 0;
|
||||
else
|
||||
hsv[1] = delta / max;
|
||||
|
||||
//Value
|
||||
hsv[2] = max;
|
||||
|
||||
return hsv;
|
||||
}
|
||||
|
||||
/**
|
||||
* HSV -> RGB.
|
||||
* @param hue Hue.
|
||||
* @param saturation Saturation. In the range[0..1].
|
||||
* @param value Value. In the range[0..1].
|
||||
* @return RGB color space. In the range[0..255].
|
||||
*/
|
||||
public static int[] HSVtoRGB(float hue, float saturation, float value){
|
||||
int[] rgb = new int[3];
|
||||
|
||||
float hi = (float)Math.floor(hue / 60.0) % 6;
|
||||
float f = (float)((hue / 60.0) - Math.floor(hue / 60.0));
|
||||
float p = (float)(value * (1.0 - saturation));
|
||||
float q = (float)(value * (1.0 - (f * saturation)));
|
||||
float t = (float)(value * (1.0 - ((1.0 - f) * saturation)));
|
||||
|
||||
if (hi == 0){
|
||||
rgb[0] = (int)(value * 255);
|
||||
rgb[1] = (int)(t * 255);
|
||||
rgb[2] = (int)(p * 255);
|
||||
}
|
||||
else if (hi == 1){
|
||||
rgb[0] = (int)(q * 255);
|
||||
rgb[1] = (int)(value * 255);
|
||||
rgb[2] = (int)(p * 255);
|
||||
}
|
||||
else if (hi == 2){
|
||||
rgb[0] = (int)(p * 255);
|
||||
rgb[1] = (int)(value * 255);
|
||||
rgb[2] = (int)(t * 255);
|
||||
}
|
||||
else if (hi == 3){
|
||||
rgb[0] = (int)(p * 255);
|
||||
rgb[1] = (int)(value * 255);
|
||||
rgb[2] = (int)(q * 255);
|
||||
}
|
||||
else if (hi == 4){
|
||||
rgb[0] = (int)(t * 255);
|
||||
rgb[1] = (int)(value * 255);
|
||||
rgb[2] = (int)(p * 255);
|
||||
}
|
||||
else if (hi == 5){
|
||||
rgb[0] = (int)(value * 255);
|
||||
rgb[1] = (int)(p * 255);
|
||||
rgb[2] = (int)(q * 255);
|
||||
}
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> YCC.
|
||||
* @param red Red coefficient. Values in the range [0..255].
|
||||
* @param green Green coefficient. Values in the range [0..255].
|
||||
* @param blue Blue coefficient. Values in the range [0..255].
|
||||
* @return YCC color space. In the range [0..1].
|
||||
*/
|
||||
public static float[] RGBtoYCC(int red, int green, int blue){
|
||||
float[] ycc = new float[3];
|
||||
|
||||
float r = red / 255f;
|
||||
float g = green / 255f;
|
||||
float b = blue / 255f;
|
||||
|
||||
float y = 0.213f * r + 0.419f * g + 0.081f * b;
|
||||
float c1 = -0.131f * r - 0.256f * g + 0.387f * b + 0.612f;
|
||||
float c2 = 0.373f * r - 0.312f * r - 0.061f * b + 0.537f;
|
||||
|
||||
ycc[0] = y;
|
||||
ycc[1] = c1;
|
||||
ycc[2] = c2;
|
||||
|
||||
return ycc;
|
||||
}
|
||||
|
||||
/**
|
||||
* YCC -> RGB.
|
||||
* @param y Y coefficient.
|
||||
* @param c1 C coefficient.
|
||||
* @param c2 C coefficient.
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] YCCtoRGB(float y, float c1, float c2){
|
||||
int[] rgb = new int[3];
|
||||
|
||||
float r = 0.981f * y + 1.315f * (c2 - 0.537f);
|
||||
float g = 0.981f * y - 0.311f * (c1 - 0.612f)- 0.669f * (c2 - 0.537f);
|
||||
float b = 0.981f * y + 1.601f * (c1 - 0.612f);
|
||||
|
||||
rgb[0] = (int)(r * 255f);
|
||||
rgb[1] = (int)(g * 255f);
|
||||
rgb[2] = (int)(b * 255f);
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> YCoCg.
|
||||
* @param red Red coefficient. Values in the range [0..255].
|
||||
* @param green Green coefficient. Values in the range [0..255].
|
||||
* @param blue Blue coefficient. Values in the range [0..255].
|
||||
* @return YCoCg color space.
|
||||
*/
|
||||
public static float[] RGBtoYCoCg(int red, int green, int blue){
|
||||
float[] yCoCg = new float[3];
|
||||
|
||||
float r = red / 255f;
|
||||
float g = green / 255f;
|
||||
float b = blue / 255f;
|
||||
|
||||
float y = r / 4f + g / 2f + b / 4f;
|
||||
float co = r / 2f - b / 2f;
|
||||
float cg = -r / 4f + g / 2f - b / 4f;
|
||||
|
||||
yCoCg[0] = y;
|
||||
yCoCg[1] = co;
|
||||
yCoCg[2] = cg;
|
||||
|
||||
return yCoCg;
|
||||
}
|
||||
|
||||
/**
|
||||
* YCoCg -> RGB.
|
||||
* @param y Pseudo luminance, or intensity.
|
||||
* @param co Orange chrominance.
|
||||
* @param cg Green chrominance.
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] YCoCgtoRGB(float y, float co, float cg){
|
||||
int[] rgb = new int[3];
|
||||
|
||||
float r = y + co - cg;
|
||||
float g = y + cg;
|
||||
float b = y - co - cg;
|
||||
|
||||
rgb[0] = (int)(r * 255f);
|
||||
rgb[1] = (int)(g * 255f);
|
||||
rgb[2] = (int)(b * 255f);
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> XYZ
|
||||
* @param red Red coefficient. Values in the range [0..255].
|
||||
* @param green Green coefficient. Values in the range [0..255].
|
||||
* @param blue Blue coefficient. Values in the range [0..255].
|
||||
* @return XYZ color space.
|
||||
*/
|
||||
public static float[] RGBtoXYZ(int red, int green, int blue){
|
||||
float[] xyz = new float[3];
|
||||
|
||||
float r = red / 255f;
|
||||
float g = green / 255f;
|
||||
float b = blue / 255f;
|
||||
|
||||
//R
|
||||
if ( r > 0.04045)
|
||||
r = (float)Math.pow(( ( r + 0.055f ) / 1.055f ), 2.4f);
|
||||
else
|
||||
r /= 12.92f;
|
||||
|
||||
//G
|
||||
if ( g > 0.04045)
|
||||
g = (float)Math.pow(( ( g + 0.055f ) / 1.055f ), 2.4f);
|
||||
else
|
||||
g /= 12.92f;
|
||||
|
||||
//B
|
||||
if ( b > 0.04045)
|
||||
b = (float)Math.pow(( ( b + 0.055f ) / 1.055f ), 2.4f);
|
||||
else
|
||||
b /= 12.92f;
|
||||
|
||||
r *= 100;
|
||||
g *= 100;
|
||||
b *= 100;
|
||||
|
||||
float x = 0.412453f * r + 0.35758f * g + 0.180423f * b;
|
||||
float y = 0.212671f * r + 0.71516f * g + 0.072169f * b;
|
||||
float z = 0.019334f * r + 0.119193f * g + 0.950227f * b;
|
||||
|
||||
xyz[0] = x;
|
||||
xyz[1] = y;
|
||||
xyz[2] = z;
|
||||
|
||||
return xyz;
|
||||
}
|
||||
|
||||
/**
|
||||
* XYZ -> RGB
|
||||
* @param x X coefficient.
|
||||
* @param y Y coefficient.
|
||||
* @param z Z coefficient.
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] XYZtoRGB(float x, float y, float z){
|
||||
int[] rgb = new int[3];
|
||||
|
||||
x /= 100;
|
||||
y /= 100;
|
||||
z /= 100;
|
||||
|
||||
float r = 3.240479f * x - 1.53715f * y - 0.498535f * z;
|
||||
float g = -0.969256f * x + 1.875991f * y + 0.041556f * z;
|
||||
float b = 0.055648f * x - 0.204043f * y + 1.057311f * z;
|
||||
|
||||
if ( r > 0.0031308 )
|
||||
r = 1.055f * ( (float)Math.pow(r, 0.4166f) ) - 0.055f;
|
||||
else
|
||||
r = 12.92f * r;
|
||||
|
||||
if ( g > 0.0031308 )
|
||||
g = 1.055f * ( (float)Math.pow(g, 0.4166f) ) - 0.055f;
|
||||
else
|
||||
g = 12.92f * g;
|
||||
|
||||
if ( b > 0.0031308 )
|
||||
b = 1.055f * ( (float)Math.pow(b, 0.4166f) ) - 0.055f;
|
||||
else
|
||||
b = 12.92f * b;
|
||||
|
||||
rgb[0] = (int)(r * 255);
|
||||
rgb[1] = (int)(g * 255);
|
||||
rgb[2] = (int)(b * 255);
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* XYZ -> HunterLAB
|
||||
* @param x X coefficient.
|
||||
* @param y Y coefficient.
|
||||
* @param z Z coefficient.
|
||||
* @return HunterLab coefficient.
|
||||
*/
|
||||
public static float[] XYZtoHunterLAB(float x, float y, float z){
|
||||
float[] hunter = new float[3];
|
||||
|
||||
|
||||
float sqrt = (float)Math.sqrt(y);
|
||||
|
||||
float l = 10 * sqrt;
|
||||
float a = 17.5f * (((1.02f * x) - y) / sqrt);
|
||||
float b = 7f * ((y - (0.847f * z)) / sqrt);
|
||||
|
||||
hunter[0] = l;
|
||||
hunter[1] = a;
|
||||
hunter[2] = b;
|
||||
|
||||
return hunter;
|
||||
}
|
||||
|
||||
/**
|
||||
* HunterLAB -> XYZ
|
||||
* @param l L coefficient.
|
||||
* @param a A coefficient.
|
||||
* @param b B coefficient.
|
||||
* @return XYZ color space.
|
||||
*/
|
||||
public static float[] HunterLABtoXYZ(float l, float a, float b){
|
||||
float[] xyz = new float[3];
|
||||
|
||||
|
||||
float tempY = l / 10f;
|
||||
float tempX = a / 17.5f * l / 10f;
|
||||
float tempZ = b / 7f * l / 10f;
|
||||
|
||||
float y = tempY * tempY;
|
||||
float x = (tempX + y) / 1.02f;
|
||||
float z = -(tempZ - y) / 0.847f;
|
||||
|
||||
xyz[0] = x;
|
||||
xyz[1] = y;
|
||||
xyz[2] = z;
|
||||
|
||||
return xyz;
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> HunterLAB.
|
||||
* @param red Red coefficient. Values in the range [0..255].
|
||||
* @param green Green coefficient. Values in the range [0..255].
|
||||
* @param blue Blue coefficient. Values in the range [0..255].
|
||||
* @return HunterLAB color space.
|
||||
*/
|
||||
public static float[] RGBtoHunterLAB(int red, int green, int blue){
|
||||
float[] xyz = RGBtoXYZ(red, green, blue);
|
||||
return XYZtoHunterLAB(xyz[0], xyz[1], xyz[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* HunterLAB -> RGB.
|
||||
* @param l L coefficient.
|
||||
* @param a A coefficient.
|
||||
* @param b B coefficient.
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] HunterLABtoRGB(float l, float a, float b){
|
||||
float[] xyz = HunterLABtoXYZ(l, a, b);
|
||||
return XYZtoRGB(xyz[0], xyz[1], xyz[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> HSL.
|
||||
* @param red Red coefficient. Values in the range [0..255].
|
||||
* @param green Green coefficient. Values in the range [0..255].
|
||||
* @param blue Blue coefficient. Values in the range [0..255].
|
||||
* @return HSL color space.
|
||||
*/
|
||||
public static float[] RGBtoHSL(int red, int green, int blue){
|
||||
float[] hsl = new float[3];
|
||||
|
||||
double r = red;
|
||||
double g = green;
|
||||
double b = blue;
|
||||
|
||||
double max = Math.max(r,Math.max(g,b));
|
||||
double min = Math.min(r,Math.min(g,b));
|
||||
// double delta = max - min;
|
||||
|
||||
//HSK
|
||||
Double h = 0d;
|
||||
Double s = 0d;
|
||||
Double l = 0d;
|
||||
|
||||
//saturation
|
||||
double cnt = (max + min) / 2d;
|
||||
if (cnt <= 127d) {
|
||||
s = ((max - min) / (max + min));
|
||||
}
|
||||
else {
|
||||
s = ((max - min) / (510d - max - min));
|
||||
}
|
||||
|
||||
//lightness
|
||||
l = ((max + min) / 2d) / 255d;
|
||||
|
||||
//hue
|
||||
if (Math.abs(max - min) <= EPSILON) {
|
||||
h = 0d;
|
||||
s = 0d;
|
||||
}
|
||||
else {
|
||||
double diff = max - min;
|
||||
|
||||
if (Math.abs(max - r) <= EPSILON) {
|
||||
h = 60d * (g - b) / diff;
|
||||
}
|
||||
else if (Math.abs(max - g) <= EPSILON) {
|
||||
h = 60d * (b - r) / diff + 120d;
|
||||
}
|
||||
else {
|
||||
h = 60d * (r - g) / diff + 240d;
|
||||
}
|
||||
|
||||
if (h < 0d) {
|
||||
h += 360d;
|
||||
}
|
||||
}
|
||||
|
||||
hsl[0] = h.floatValue();
|
||||
hsl[1] = s.floatValue();
|
||||
hsl[2] = l.floatValue();
|
||||
|
||||
return hsl;
|
||||
}
|
||||
|
||||
/**
|
||||
* HLS -> RGB.
|
||||
* @param hue Hue.
|
||||
* @param saturation Saturation.
|
||||
* @param luminance Luminance.
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] HSLtoRGB(float hue, float saturation, float luminance){
|
||||
int[] rgb = new int[3];
|
||||
float r = 0, g = 0, b = 0;
|
||||
|
||||
if ( saturation == 0 )
|
||||
{
|
||||
// gray values
|
||||
r = g = b = (int) ( luminance * 255 );
|
||||
}
|
||||
else
|
||||
{
|
||||
float v1, v2;
|
||||
float h = (float) hue / 360;
|
||||
|
||||
v2 = ( luminance < 0.5 ) ?
|
||||
( luminance * ( 1 + saturation ) ) :
|
||||
( ( luminance + saturation ) - ( luminance * saturation ) );
|
||||
v1 = 2 * luminance - v2;
|
||||
|
||||
r = (int) ( 255 * Hue_2_RGB( v1, v2, h + ( 1.0f / 3 ) ) );
|
||||
g = (int) ( 255 * Hue_2_RGB( v1, v2, h ) );
|
||||
b = (int) ( 255 * Hue_2_RGB( v1, v2, h - ( 1.0f / 3 ) ) );
|
||||
}
|
||||
|
||||
rgb[0] = (int)r;
|
||||
rgb[1] = (int)g;
|
||||
rgb[2] = (int)b;
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
private static float Hue_2_RGB( float v1, float v2, float vH ){
|
||||
if ( vH < 0 )
|
||||
vH += 1;
|
||||
if ( vH > 1 )
|
||||
vH -= 1;
|
||||
if ( ( 6 * vH ) < 1 )
|
||||
return ( v1 + ( v2 - v1 ) * 6 * vH );
|
||||
if ( ( 2 * vH ) < 1 )
|
||||
return v2;
|
||||
if ( ( 3 * vH ) < 2 )
|
||||
return ( v1 + ( v2 - v1 ) * ( ( 2.0f / 3 ) - vH ) * 6 );
|
||||
return v1;
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> CIE-LAB.
|
||||
* @param red Red coefficient. Values in the range [0..255].
|
||||
* @param green Green coefficient. Values in the range [0..255].
|
||||
* @param blue Blue coefficient. Values in the range [0..255].
|
||||
* @param tristimulus XYZ Tristimulus.
|
||||
* @return CIE-LAB color space.
|
||||
*/
|
||||
public static float[] RGBtoLAB(int red, int green, int blue, float[] tristimulus){
|
||||
float[] xyz = RGBtoXYZ(red, green, blue);
|
||||
float[] lab = XYZtoLAB(xyz[0], xyz[1], xyz[2], tristimulus);
|
||||
|
||||
return lab;
|
||||
}
|
||||
|
||||
/**
|
||||
* CIE-LAB -> RGB.
|
||||
* @param l L coefficient.
|
||||
* @param a A coefficient.
|
||||
* @param b B coefficient.
|
||||
* @param tristimulus XYZ Tristimulus.
|
||||
* @return RGB color space.
|
||||
*/
|
||||
public static int[] LABtoRGB(float l, float a, float b, float[] tristimulus){
|
||||
float[] xyz = LABtoXYZ(l, a, b, tristimulus);
|
||||
return XYZtoRGB(xyz[0], xyz[1], xyz[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* XYZ -> CIE-LAB.
|
||||
* @param x X coefficient.
|
||||
* @param y Y coefficient.
|
||||
* @param z Z coefficient.
|
||||
* @param tristimulus XYZ Tristimulus.
|
||||
* @return CIE-LAB color space.
|
||||
*/
|
||||
public static float[] XYZtoLAB(float x, float y, float z, float[] tristimulus){
|
||||
float[] lab = new float[3];
|
||||
|
||||
x /= tristimulus[0];
|
||||
y /= tristimulus[1];
|
||||
z /= tristimulus[2];
|
||||
|
||||
if (x > 0.008856)
|
||||
x = (float)Math.pow(x,0.33f);
|
||||
else
|
||||
x = (7.787f * x) + ( 0.1379310344827586f );
|
||||
|
||||
if (y > 0.008856)
|
||||
y = (float)Math.pow(y,0.33f);
|
||||
else
|
||||
y = (7.787f * y) + ( 0.1379310344827586f );
|
||||
|
||||
if (z > 0.008856)
|
||||
z = (float)Math.pow(z,0.33f);
|
||||
else
|
||||
z = (7.787f * z) + ( 0.1379310344827586f );
|
||||
|
||||
lab[0] = ( 116 * y ) - 16;
|
||||
lab[1] = 500 * ( x - y );
|
||||
lab[2] = 200 * ( y - z );
|
||||
|
||||
return lab;
|
||||
}
|
||||
|
||||
/**
|
||||
* CIE-LAB -> XYZ.
|
||||
* @param l L coefficient.
|
||||
* @param a A coefficient.
|
||||
* @param b B coefficient.
|
||||
* @param tristimulus XYZ Tristimulus.
|
||||
* @return XYZ color space.
|
||||
*/
|
||||
public static float[] LABtoXYZ(float l, float a, float b, float[] tristimulus){
|
||||
float[] xyz = new float[3];
|
||||
|
||||
float y = ( l + 16f ) / 116f;
|
||||
float x = a / 500f + y;
|
||||
float z = y - b / 200f;
|
||||
|
||||
//Y
|
||||
if ( Math.pow(y,3) > 0.008856 )
|
||||
y = (float)Math.pow(y,3);
|
||||
else
|
||||
y = (float)(( y - 16 / 116 ) / 7.787);
|
||||
|
||||
//X
|
||||
if ( Math.pow(x,3) > 0.008856 )
|
||||
x = (float)Math.pow(x,3);
|
||||
else
|
||||
x = (float)(( x - 16 / 116 ) / 7.787);
|
||||
|
||||
// Z
|
||||
if ( Math.pow(z,3) > 0.008856 )
|
||||
z = (float)Math.pow(z,3);
|
||||
else
|
||||
z = (float)(( z - 16 / 116 ) / 7.787);
|
||||
|
||||
xyz[0] = x * tristimulus[0];
|
||||
xyz[1] = y * tristimulus[1];
|
||||
xyz[2] = z * tristimulus[2];
|
||||
|
||||
return xyz;
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> C1C2C3.
|
||||
* @param r Red coefficient. Values in the range [0..255].
|
||||
* @param g Green coefficient. Values in the range [0..255].
|
||||
* @param b Blue coefficient. Values in the range [0..255].
|
||||
* @return C1C2C3 color space.
|
||||
*/
|
||||
public static float[] RGBtoC1C2C3(int r, int g, int b){
|
||||
|
||||
float[] c = new float[3];
|
||||
|
||||
c[0] = (float)Math.atan(r / Math.max(g, b));
|
||||
c[1] = (float)Math.atan(g / Math.max(r, b));
|
||||
c[2] = (float)Math.atan(b / Math.max(r, g));
|
||||
|
||||
return c;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> O1O2.
|
||||
* @param r Red coefficient. Values in the range [0..255].
|
||||
* @param g Green coefficient. Values in the range [0..255].
|
||||
* @param b Blue coefficient. Values in the range [0..255].
|
||||
* @return O1O2 color space.
|
||||
*/
|
||||
public static float[] RGBtoO1O2(int r, int g, int b){
|
||||
|
||||
float[] o = new float[2];
|
||||
|
||||
o[0] = (r - g) / 2f;
|
||||
o[1] = (r + g) / 4f - (b / 2f);
|
||||
|
||||
return o;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB -> Grayscale.
|
||||
* @param r Red coefficient. Values in the range [0..255].
|
||||
* @param g Green coefficient. Values in the range [0..255].
|
||||
* @param b Blue coefficient. Values in the range [0..255].
|
||||
* @return Grayscale color space.
|
||||
*/
|
||||
public static float RGBtoGrayscale(int r, int g, int b){
|
||||
|
||||
return r*0.2125f + g*0.7154f + b*0.0721f;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* XYZ -> Philips Hue XY
|
||||
* @param x X coefficient.
|
||||
* @param y Y coefficient.
|
||||
* @param z Z coefficient.
|
||||
* @return Hue xy array
|
||||
*/
|
||||
public static XYColorSpace XYZtoXY(float x, float y, float z){
|
||||
float[] xy = new float[2];
|
||||
|
||||
xy[0] = x / (x + y + z);
|
||||
xy[1] = y / (x + y + z);
|
||||
|
||||
XYColorSpace xyColor = new XYColorSpace();
|
||||
xyColor.setBrightness((int)Math.round(y * 254.0f));
|
||||
xyColor.setXy(xy);
|
||||
return xyColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Philips Hue XY -> XYZ
|
||||
* @param x X coefficient.
|
||||
* @param y Y coefficient.
|
||||
* @return XYZ array
|
||||
*/
|
||||
public static float[] XYtoXYZ(XYColorSpace xy){
|
||||
float[] xyz = new float[3];
|
||||
/* Old Way
|
||||
xyz[0] = (xy.getBrightnessAdjusted() / xy.getXy()[1]) * xy.getXy()[0];
|
||||
xyz[1] = xy.getBrightnessAdjusted();
|
||||
xyz[2] = (xy.getBrightnessAdjusted() / xy.getXy()[1]) * (1.0f - xy.getXy()[0] - xy.getXy()[1]);
|
||||
*/
|
||||
// New Way
|
||||
xyz[0] = xy.getXy()[0] * (xy.getBrightnessAdjusted() / xy.getXy()[1]) ;
|
||||
xyz[1] = xy.getBrightnessAdjusted();
|
||||
xyz[2] = (float) ((1.0 - xy.getXy()[0] - xy.getXy()[1]) * (xy.getBrightnessAdjusted() / xy.getXy()[1]));
|
||||
|
||||
return xyz;
|
||||
}
|
||||
|
||||
public static int[] normalizeRGB(int[] rgb) {
|
||||
int[] newRGB = new int[3];
|
||||
|
||||
newRGB[0] = assureBounds(rgb[0]);
|
||||
newRGB[1] = assureBounds(rgb[1]);
|
||||
newRGB[2] = assureBounds(rgb[2]);
|
||||
|
||||
|
||||
return newRGB;
|
||||
}
|
||||
|
||||
private static int assureBounds(int value) {
|
||||
if (value < 0.0) {
|
||||
value = 0;
|
||||
}
|
||||
if (value > 255.0) {
|
||||
value = 255;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,12 +4,12 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.awt.Color;
|
||||
// import java.awt.Color;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.bwssystems.HABridge.hue.ColorData;
|
||||
// import com.bwssystems.HABridge.hue.ColorData;
|
||||
|
||||
public class ColorDecode {
|
||||
private static final Logger log = LoggerFactory.getLogger(ColorDecode.class);
|
||||
@@ -20,170 +20,224 @@ public class ColorDecode {
|
||||
private static final String COLOR_GX = "${color.gx}";
|
||||
private static final String COLOR_BX = "${color.bx}";
|
||||
private static final String COLOR_RGBX = "${color.rgbx}";
|
||||
private static final String COLOR_HSL = "${color.hsl}";
|
||||
private static final String COLOR_HSB = "${color.hsb}";
|
||||
private static final String COLOR_H = "${color.h}";
|
||||
private static final String COLOR_S = "${color.s}";
|
||||
private static final String COLOR_XY = "${color.xy}";
|
||||
private static final String COLOR_BRI = "${colorbri}";
|
||||
private static final Pattern COLOR_MILIGHT = Pattern.compile("\\$\\{color.milight\\:([01234])\\}");
|
||||
|
||||
public static List<Integer> convertHSLtoRGB(HueSatBri hsl) {
|
||||
|
||||
/* This is supersceded by the next iteration function below this original one
|
||||
public static List<Integer> convertHSBtoRGBOrig(HueSatBri hsb) {
|
||||
List<Integer> rgb;
|
||||
float decimalBrightness = (float) 0.0;
|
||||
float var_1 = (float) 0.0;
|
||||
float var_2 = (float) 0.0;
|
||||
float h = (float) 0.0;
|
||||
float h2 = (float) 0.0;
|
||||
float s = (float) 0.0;
|
||||
double r = 0.0;
|
||||
double g = 0.0;
|
||||
double b = 0.0;
|
||||
Float hue = (Float)(hsb.getHue()*1.0f);
|
||||
Float saturation = (Float)(hsb.getSat()*1.0f);
|
||||
Float brightness = (Float)(hsb.getBri()*1.0f);
|
||||
log.info("Hue = " + hue + ", Sat = " + saturation + ", Bri = " + brightness);
|
||||
//Convert Hue into degrees for HSB
|
||||
// hue = hue / 182.04f;
|
||||
hue = (hue / 65535.0f) * 360.0f;
|
||||
//Bri and Sat must be values from 0-1 (~percentage)
|
||||
// ightness = brightness / 255.0f;
|
||||
// saturation = saturation / 255.0f;
|
||||
|
||||
if(hsl.getBri() > 0)
|
||||
decimalBrightness = (float) (hsl.getBri() / 255.0);
|
||||
brightness = brightness / 254.0f;
|
||||
saturation = saturation / 254.0f;
|
||||
|
||||
if(hsl.getHue() > 0) {
|
||||
h = ((float)hsl.getHue() / (float)65535.0);
|
||||
h2 = h + (float)0.5;
|
||||
if(h2 > 1.0) {
|
||||
h2 = h2 - (float)1.0;
|
||||
Float r = 0f;
|
||||
Float g = 0f;
|
||||
Float b = 0f;
|
||||
|
||||
if(brightness > 0.0f) {
|
||||
if (saturation == 0)
|
||||
{
|
||||
r = g = b = brightness;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the color wheel consists of 6 sectors.
|
||||
Float sectorPos = hue / 60.0f;
|
||||
int sectorNumber = (int)(Math.floor(sectorPos));
|
||||
// get the fractional part of the sector
|
||||
Float fractionalSector = sectorPos - sectorNumber;
|
||||
|
||||
// calculate values for the three axes of the color.
|
||||
Float p = brightness * (1.0f - saturation);
|
||||
Float q = brightness * (1.0f - (saturation * fractionalSector));
|
||||
Float t = brightness * (1.0f - (saturation * (1f - fractionalSector)));
|
||||
|
||||
// assign the fractional colors to r, g, and b based on the sector the angle is in.
|
||||
switch (sectorNumber)
|
||||
{
|
||||
case 0:
|
||||
r = brightness;
|
||||
g = t;
|
||||
b = p;
|
||||
break;
|
||||
case 1:
|
||||
r = q;
|
||||
g = brightness;
|
||||
b = p;
|
||||
break;
|
||||
case 2:
|
||||
r = p;
|
||||
g = brightness;
|
||||
b = t;
|
||||
break;
|
||||
case 3:
|
||||
r = p;
|
||||
g = q;
|
||||
b = brightness;
|
||||
break;
|
||||
case 4:
|
||||
r = t;
|
||||
g = p;
|
||||
b = brightness;
|
||||
break;
|
||||
case 5:
|
||||
r = brightness;
|
||||
g = p;
|
||||
b = q;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(hsl.getSat() > 0) {
|
||||
s = (float)(hsl.getSat() / 254.0);
|
||||
|
||||
//Check if any value is out of byte range
|
||||
if (r < 0f)
|
||||
{
|
||||
r = 0f;
|
||||
}
|
||||
|
||||
if (s == 0)
|
||||
{
|
||||
r = decimalBrightness * (float)255;
|
||||
g = decimalBrightness * (float)255;
|
||||
b = decimalBrightness * (float)255;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (decimalBrightness < 0.5)
|
||||
{
|
||||
var_2 = decimalBrightness * (1 + s);
|
||||
}
|
||||
else
|
||||
{
|
||||
var_2 = (decimalBrightness + s) - (s * decimalBrightness);
|
||||
};
|
||||
|
||||
var_1 = 2 * decimalBrightness - var_2;
|
||||
float onethird = (float)0.33333;
|
||||
float h2Plus = (h2 + onethird);
|
||||
float h2Minus = (h2 - onethird);
|
||||
log.debug("calculate HSL vars - var1: " + var_1 + ", var_2: " + var_2 + ", h2: " + h2 + ", h2 + 1/3: " + h2Plus + ", h2 - 1/3: " + h2Minus);
|
||||
r = 255 * hue_2_rgb(var_1, var_2, h2Plus);
|
||||
g = 255 * hue_2_rgb(var_1, var_2, h2);
|
||||
b = 255 * hue_2_rgb(var_1, var_2, h2Minus);
|
||||
};
|
||||
|
||||
if (g < 0f)
|
||||
{
|
||||
g = 0f;
|
||||
}
|
||||
if (b < 0f)
|
||||
{
|
||||
b = 0f;
|
||||
}
|
||||
|
||||
rgb = new ArrayList<Integer>();
|
||||
rgb.add((int) Math.round(r));
|
||||
rgb.add((int) Math.round(g));
|
||||
rgb.add((int) Math.round(b));
|
||||
|
||||
log.debug("Color change with HSL: " + hsl + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " "
|
||||
rgb.add((int)Math.round(r*255));
|
||||
rgb.add((int)Math.round(g*255));
|
||||
rgb.add((int)Math.round(b*255));
|
||||
log.info("Color change with HSB: " + hsb + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " "
|
||||
+ rgb.get(2));
|
||||
|
||||
int theRGB = Color.HSBtoRGB(hue, saturation, brightness);
|
||||
Color decodedRGB = new Color(theRGB);
|
||||
log.info("Color change with HSB using java Color: " + hsb + ". Resulting RGB Values: " + decodedRGB.getRed() + " " + decodedRGB.getGreen() + " "
|
||||
+ decodedRGB.getBlue());
|
||||
|
||||
return rgb;
|
||||
}
|
||||
*/
|
||||
public static List<Integer> convertHSBtoRGB(HueSatBri hsb) {
|
||||
List<Integer> rgb;
|
||||
Float hue = (Float)(hsb.getHue()*1.0f);
|
||||
Float saturation = (Float)(hsb.getSat()*1.0f);
|
||||
Float brightness = (Float)(hsb.getBri()*1.0f);
|
||||
log.info("Hue = " + hue + ", Sat = " + saturation + ", Bri = " + brightness);
|
||||
//Convert Hue into degrees for HSB
|
||||
// hue = hue / 182.04f;
|
||||
hue = (hue / 65535.0f);
|
||||
//Bri and Sat must be values from 0-1 (~percentage)
|
||||
// ightness = brightness / 255.0f;
|
||||
// saturation = saturation / 255.0f;
|
||||
|
||||
brightness = brightness / 254.0f;
|
||||
saturation = saturation / 254.0f;
|
||||
|
||||
Float r = 0f;
|
||||
Float g = 0f;
|
||||
Float b = 0f;
|
||||
Float temp2 = 0f;
|
||||
Float temp1 = 0f;
|
||||
|
||||
if(brightness > 0.0f) {
|
||||
if (saturation == 0)
|
||||
{
|
||||
r = g = b = brightness;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp2 = (brightness < 0.5f) ? brightness * (1.0f + saturation) : brightness + saturation - (brightness * saturation);
|
||||
temp1 = 2.0f * brightness - temp2;
|
||||
|
||||
r = GetColorComponent(temp1, temp2, hue + 1.0f/3.0f);
|
||||
g = GetColorComponent(temp1, temp2, hue);
|
||||
b = GetColorComponent(temp1, temp2, hue - 1.0f/3.0f);
|
||||
}
|
||||
}
|
||||
|
||||
//Check if any value is out of byte range
|
||||
if (r < 0f)
|
||||
{
|
||||
r = 0f;
|
||||
}
|
||||
if (g < 0f)
|
||||
{
|
||||
g = 0f;
|
||||
}
|
||||
if (b < 0f)
|
||||
{
|
||||
b = 0f;
|
||||
}
|
||||
|
||||
rgb = new ArrayList<Integer>();
|
||||
rgb.add((int)Math.round(r*255));
|
||||
rgb.add((int)Math.round(g*255));
|
||||
rgb.add((int)Math.round(b*255));
|
||||
log.debug("Color change with HSB New: " + hsb + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " "
|
||||
+ rgb.get(2));
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
public static float hue_2_rgb(float v1, float v2, float vh) {
|
||||
log.debug("hue_2_rgb vh: " + vh);
|
||||
if (vh < 0.0)
|
||||
private static Float GetColorComponent(Float temp1, Float temp2, Float temp3)
|
||||
{
|
||||
temp3 = MoveIntoRange(temp3);
|
||||
if (temp3 < 1.0f/6.0f)
|
||||
{
|
||||
vh = vh + (float)1;
|
||||
};
|
||||
return temp1 + (temp2 - temp1) * 6.0f * temp3;
|
||||
}
|
||||
|
||||
if (vh > 1.0)
|
||||
if (temp3 < 0.5f)
|
||||
{
|
||||
vh = vh - (float)1;
|
||||
};
|
||||
return temp2;
|
||||
}
|
||||
|
||||
if (((float)6.0 * vh) < 1.0)
|
||||
if (temp3 < 2.0f/3.0f)
|
||||
{
|
||||
return (v1 + (v2 - v1) * (float)6.0 * vh);
|
||||
};
|
||||
return temp1 + ((temp2 - temp1) * ((2.0f/3.0f) - temp3) * 6.0f);
|
||||
}
|
||||
|
||||
if (((float)2.0 * vh) < 1.0)
|
||||
{
|
||||
return (v2);
|
||||
};
|
||||
return temp1;
|
||||
}
|
||||
|
||||
if ((3.0 * vh) < 2.0)
|
||||
{
|
||||
return (v1 + (v2 - v1) * (((float)2.0 / (float)3.0 - vh) * (float)6.0));
|
||||
};
|
||||
|
||||
return (v1);
|
||||
private static Float MoveIntoRange(Float temp3)
|
||||
{
|
||||
if (temp3 < 0.0f) return temp3 + 1f;
|
||||
if (temp3 > 1.0f) return temp3 - 1f;
|
||||
return temp3;
|
||||
}
|
||||
|
||||
public static List<Integer> convertCIEtoRGB(List<Double> xy, int brightness) {
|
||||
List<Integer> rgb;
|
||||
double x = xy.get(0); // the given x value
|
||||
double y = xy.get(1); // the given y value
|
||||
double z = 1.0 - x - y;
|
||||
double Y = (double) brightness / (double) 254.00; // The given brightness value
|
||||
double X = (Y / y) * x;
|
||||
double Z = (Y / y) * z;
|
||||
|
||||
double r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
|
||||
double g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
|
||||
double b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
|
||||
|
||||
if (r > b && r > g && r > 1.0) {
|
||||
|
||||
g = g / r;
|
||||
b = b / r;
|
||||
r = 1.0;
|
||||
} else if (g > b && g > r && g > 1.0) {
|
||||
|
||||
r = r / g;
|
||||
b = b / g;
|
||||
g = 1.0;
|
||||
} else if (b > r && b > g && b > 1.0) {
|
||||
|
||||
r = r / b;
|
||||
g = g / b;
|
||||
b = 1.0;
|
||||
}
|
||||
|
||||
r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055;
|
||||
g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055;
|
||||
b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055;
|
||||
|
||||
if (r > b && r > g) {
|
||||
// red is biggest
|
||||
if (r > 1.0) {
|
||||
g = g / r;
|
||||
b = b / r;
|
||||
r = 1.0;
|
||||
}
|
||||
} else if (g > b && g > r) {
|
||||
// green is biggest
|
||||
if (g > 1.0) {
|
||||
r = r / g;
|
||||
b = b / g;
|
||||
g = 1.0;
|
||||
}
|
||||
} else if (b > r && b > g) {
|
||||
// blue is biggest
|
||||
if (b > 1.0) {
|
||||
r = r / b;
|
||||
g = g / b;
|
||||
b = 1.0;
|
||||
}
|
||||
}
|
||||
if (r < 0.0)
|
||||
r = 0;
|
||||
if (g < 0.0)
|
||||
g = 0;
|
||||
if (b < 0.0)
|
||||
b = 0;
|
||||
|
||||
XYColorSpace xyColor = new XYColorSpace();
|
||||
xyColor.setBrightness(brightness);
|
||||
float[] xyFloat = new float[2];
|
||||
xyFloat[0] = xy.get(0).floatValue();
|
||||
xyFloat[1] = xy.get(1).floatValue();
|
||||
xyColor.setXy(xyFloat);
|
||||
float[] xyz = ColorConverter.XYtoXYZ(xyColor);
|
||||
int[] rgbInt = ColorConverter.normalizeRGB(ColorConverter.XYZtoRGB(xyz[0], xyz[1], xyz[2]));
|
||||
rgb = new ArrayList<Integer>();
|
||||
rgb.add((int) Math.round(r * 255));
|
||||
rgb.add((int) Math.round(g * 255));
|
||||
rgb.add((int) Math.round(b * 255));
|
||||
log.debug("Color change with XY: " + x + " " + y + " Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1)
|
||||
rgb.add(rgbInt[0]);
|
||||
rgb.add(rgbInt[1]);
|
||||
rgb.add(rgbInt[2]);
|
||||
log.debug("Color change with XY: " + xy.get(0) + " " + xy.get(1) + " " + brightness + " Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1)
|
||||
+ " " + rgb.get(2));
|
||||
return rgb;
|
||||
}
|
||||
@@ -229,10 +283,10 @@ public class ColorDecode {
|
||||
|
||||
private static double assureBounds(double value) {
|
||||
if (value < 0.0) {
|
||||
value = 0;
|
||||
value = 0.0;
|
||||
}
|
||||
if (value > 255.0) {
|
||||
value = 255;
|
||||
value = 255.0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -253,7 +307,7 @@ public class ColorDecode {
|
||||
} else if (colorMode == ColorData.ColorMode.CT) {
|
||||
rgb = convertCTtoRGB((Integer) colorData.getData());
|
||||
} else if (colorMode == ColorData.ColorMode.HS) {
|
||||
rgb = convertHSLtoRGB((HueSatBri) colorData.getData());
|
||||
rgb = convertHSBtoRGB((HueSatBri) colorData.getData());
|
||||
}
|
||||
|
||||
while (notDone) {
|
||||
@@ -297,13 +351,67 @@ public class ColorDecode {
|
||||
notDone = true;
|
||||
}
|
||||
|
||||
if (request.contains(COLOR_HSL)) {
|
||||
float[] hsb = new float[3];
|
||||
Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb);
|
||||
float hue = hsb[0] * (float) 360.0;
|
||||
float sat = hsb[1] * (float) 100.0;
|
||||
float bright = hsb[2] * (float) 100.0;
|
||||
request = request.replace(COLOR_HSL, String.format("%f,%f,%f", hue, sat, bright));
|
||||
if (request.contains(COLOR_XY)) {
|
||||
if (colorMode == ColorData.ColorMode.XY) {
|
||||
List<Double> xyData = (List<Double>) colorData.getData();
|
||||
request = request.replace(COLOR_XY, String.format("%f,%f", xyData.get(0), xyData.get(1)));
|
||||
} else {
|
||||
float[] xyz = ColorConverter.RGBtoXYZ(rgb.get(0), rgb.get(1), rgb.get(2));
|
||||
XYColorSpace theXYcolor = ColorConverter.XYZtoXY(xyz[0], xyz[1], xyz[2]);
|
||||
request = request.replace(COLOR_XY, String.format("%f,%f",theXYcolor.getXy()[0], theXYcolor.getXy()[1]));
|
||||
}
|
||||
notDone = true;
|
||||
}
|
||||
|
||||
if (request.contains(COLOR_H)) {
|
||||
if (colorMode == ColorData.ColorMode.HS) {
|
||||
HueSatBri hslData = (HueSatBri) colorData.getData();
|
||||
request = request.replace(COLOR_H, String.format("%d", hslData.getHue()));
|
||||
} else {
|
||||
float[] hsb;
|
||||
hsb = ColorConverter.RGBtoHSL(rgb.get(0), rgb.get(1), rgb.get(2));
|
||||
float hue = hsb[0];
|
||||
request = request.replace(COLOR_H, String.format("%f", hue));
|
||||
}
|
||||
notDone = true;
|
||||
}
|
||||
|
||||
if (request.contains(COLOR_S)) {
|
||||
if (colorMode == ColorData.ColorMode.HS) {
|
||||
HueSatBri hslData = (HueSatBri) colorData.getData();
|
||||
request = request.replace(COLOR_S, String.format("%d", hslData.getSat()));
|
||||
} else {
|
||||
float[] hsb;
|
||||
hsb = ColorConverter.RGBtoHSL(rgb.get(0), rgb.get(1), rgb.get(2));
|
||||
float sat = hsb[1] * (float) 100.0;
|
||||
request = request.replace(COLOR_S, String.format("%f", sat));
|
||||
}
|
||||
notDone = true;
|
||||
}
|
||||
|
||||
if (request.contains(COLOR_BRI)) {
|
||||
if (colorMode == ColorData.ColorMode.HS) {
|
||||
HueSatBri hslData = (HueSatBri) colorData.getData();
|
||||
request = request.replace(COLOR_BRI, String.format("%d", hslData.getBri()));
|
||||
} else {
|
||||
request = request.replace(COLOR_BRI, String.format("%d", setIntensity));
|
||||
}
|
||||
notDone = true;
|
||||
}
|
||||
|
||||
if (request.contains(COLOR_HSB)) {
|
||||
if (colorMode == ColorData.ColorMode.HS) {
|
||||
HueSatBri hslData = (HueSatBri) colorData.getData();
|
||||
request = request.replace(COLOR_HSB,
|
||||
String.format("%d,%d,%d", hslData.getHue(), hslData.getSat(), hslData.getBri()));
|
||||
} else {
|
||||
float[] hsb = new float[3];
|
||||
hsb = ColorConverter.RGBtoHSL(rgb.get(0), rgb.get(1), rgb.get(2));
|
||||
float hue = hsb[0];
|
||||
float sat = hsb[1] * (float) 100.0;
|
||||
float bright = hsb[2] * (float) 100.0;
|
||||
request = request.replace(COLOR_HSB, String.format("%f,%f,%f", hue, sat, bright));
|
||||
}
|
||||
notDone = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -473,9 +473,9 @@ public class HueMulator {
|
||||
if (deviceState != null) {
|
||||
deviceState.setOn(stateChanges.isOn());
|
||||
if (!deviceState.isOn() && deviceState.getBri() == 254)
|
||||
deviceState.setBri(0);
|
||||
deviceState.setBri(1);
|
||||
if (!deviceState.isOn() && offState)
|
||||
deviceState.setBri(0);
|
||||
deviceState.setBri(1);
|
||||
}
|
||||
notFirstChange = true;
|
||||
}
|
||||
@@ -609,7 +609,7 @@ public class HueMulator {
|
||||
notFirstChange = true;
|
||||
}
|
||||
|
||||
if ((deviceState != null) && deviceState.isOn() && deviceState.getBri() <= 0)
|
||||
if ((deviceState != null) && deviceState.isOn() && deviceState.getBri() <= 1)
|
||||
deviceState.setBri(254);
|
||||
|
||||
// if((deviceState != null) && !deviceState.isOn() && (targetBri != null ||
|
||||
@@ -1221,12 +1221,44 @@ public class HueMulator {
|
||||
isOnRequest = true;
|
||||
}
|
||||
|
||||
if(device.isOnFirstDim()) {
|
||||
if(isDimRequest && !device.getDeviceState().isOn()) {
|
||||
isOnRequest = true;
|
||||
theStateChanges.setOn(true);
|
||||
// isDimRequest = false;
|
||||
// isColorRequest = false;
|
||||
} else if (isDimRequest && device.getDeviceState().isOn()) {
|
||||
if (device.getDeviceState().getBri() == theStateChanges.getBri()) {
|
||||
isOnRequest = true;
|
||||
theStateChanges.setOn(true);
|
||||
// isDimRequest = false;
|
||||
// isColorRequest = false;
|
||||
} else {
|
||||
isOnRequest = false;
|
||||
// isDimRequest = true;
|
||||
// isColorRequest = false;
|
||||
}
|
||||
}
|
||||
} else if (device.isOnWhenDimPresent()) {
|
||||
if (isDimRequest) {
|
||||
isOnRequest = true;
|
||||
theStateChanges.setOn(true);
|
||||
}
|
||||
} else if (device.isDimNoOn()) {
|
||||
if (isDimRequest && isOnRequest) {
|
||||
isOnRequest = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(isColorRequest && isDimRequest && !device.isDimOnColor()) {
|
||||
isDimRequest = false;
|
||||
}
|
||||
|
||||
/* Old code supperceded by the above block
|
||||
if (!device.isOnFirstDim() && device.isOnWhenDimPresent() && isDimRequest && !isOnRequest) {
|
||||
isOnRequest = true;
|
||||
theStateChanges.setOn(true);
|
||||
} else if (!device.isOnFirstDim() && !device.isOnWhenDimPresent() && isDimRequest) {
|
||||
// isOnRequest = false;
|
||||
}
|
||||
} else
|
||||
|
||||
if (device.isOnFirstDim() && isDimRequest && !device.getDeviceState().isOn()) {
|
||||
isOnRequest = true;
|
||||
@@ -1245,6 +1277,7 @@ public class HueMulator {
|
||||
isColorRequest = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (isOnRequest) {
|
||||
if (bridgeSettings.isTracestate())
|
||||
@@ -1416,8 +1449,8 @@ public class HueMulator {
|
||||
int bri = 0;
|
||||
if (targetBriInc != null) {
|
||||
bri = state.getBri() - targetBriInc;
|
||||
if (bri < 0)
|
||||
bri = 0;
|
||||
if (bri < 1)
|
||||
bri = 1;
|
||||
} else if (targetBri != null) {
|
||||
bri = targetBri;
|
||||
} else {
|
||||
@@ -1440,8 +1473,8 @@ public class HueMulator {
|
||||
int bri = 0;
|
||||
if (targetBriInc != null) {
|
||||
bri = state.getBri() - targetBriInc;
|
||||
if (bri < 0)
|
||||
bri = 0;
|
||||
if (bri < 1)
|
||||
bri = 1;
|
||||
} else if (targetBri != null) {
|
||||
bri = targetBri;
|
||||
} else {
|
||||
|
||||
26
src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java
Normal file
26
src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.bwssystems.HABridge.hue;
|
||||
|
||||
public class XYColorSpace {
|
||||
float[] xy;
|
||||
int brightness;
|
||||
|
||||
public float[] getXy() {
|
||||
return xy;
|
||||
}
|
||||
|
||||
public void setXy(float[] xy) {
|
||||
this.xy = xy;
|
||||
}
|
||||
|
||||
public int getBrightness() {
|
||||
return brightness;
|
||||
}
|
||||
|
||||
public float getBrightnessAdjusted() {
|
||||
return ((float) brightness / 254.0f) * 100f;
|
||||
}
|
||||
|
||||
public void setBrightness(int brightness) {
|
||||
this.brightness = brightness;
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import com.google.gson.Gson;
|
||||
|
||||
public class DomoticzHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(DomoticzHandler.class);
|
||||
private static final String GET_REQUEST = "/json.htm?type=";
|
||||
private static final String DEVICES_TYPE = "devices";
|
||||
private static final String GET_REQUEST = "/json.htm?type=command¶m=";
|
||||
private static final String DEVICES_TYPE = "getdevices";
|
||||
private static final String SCENES_TYPE = "scenes";
|
||||
private static final String FILTER_USED = "&used=true";
|
||||
private NamedIP domoticzAddress;
|
||||
|
||||
@@ -139,8 +139,7 @@ public class HassHome implements Home {
|
||||
hassCommand = aGsonHandler.fromJson(anItem.getItem(), HassCommand.class);
|
||||
else
|
||||
hassCommand = aGsonHandler.fromJson(anItem.getItem().getAsString().replaceAll("^\"|\"$", ""), HassCommand.class);
|
||||
hassCommand.setBri(BrightnessDecode.replaceIntensityValue(hassCommand.getBri(),
|
||||
BrightnessDecode.calculateIntensity(intensity, targetBri, targetBriInc), false));
|
||||
hassCommand.setBri(BrightnessDecode.calculateReplaceIntensityValue(hassCommand.getBri(), intensity, targetBri, targetBriInc, false));
|
||||
HomeAssistant homeAssistant = getHomeAssistant(hassCommand.getHassName());
|
||||
if (homeAssistant == null) {
|
||||
log.warn("Should not get here, no HomeAssistants available");
|
||||
|
||||
@@ -102,6 +102,10 @@ public class HomeAssistant {
|
||||
if (theAuthType == null) {
|
||||
try {
|
||||
theAuthType = new Gson().fromJson(hassAddress.getExtensions(), HassAuth.class);
|
||||
if(theAuthType == null) {
|
||||
theAuthType = new HassAuth();
|
||||
theAuthType.setLegacyauth(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not read hass auth - continuing with defaults.");
|
||||
theAuthType = new HassAuth();
|
||||
@@ -114,7 +118,7 @@ public class HomeAssistant {
|
||||
private NameValue[] getAuthHeader() {
|
||||
if (headers == null) {
|
||||
if (hassAddress.getPassword() != null && !hassAddress.getPassword().isEmpty()) {
|
||||
headers = new NameValue[1];
|
||||
headers = new NameValue[2];
|
||||
headers[0] = new NameValue();
|
||||
if (isLegacyAuth()) {
|
||||
headers[0].setName("x-ha-access");
|
||||
@@ -123,6 +127,9 @@ public class HomeAssistant {
|
||||
headers[0].setName("Authorization");
|
||||
headers[0].setValue("Bearer " + hassAddress.getPassword());
|
||||
}
|
||||
headers[1] = new NameValue();
|
||||
headers[1].setName("Accept-Encoding");
|
||||
headers[1].setValue("gzip");
|
||||
}
|
||||
} else if(hassAddress.getPassword() == null || hassAddress.getPassword().isEmpty()) {
|
||||
headers = null;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class HomeGenieInstance {
|
||||
|
||||
String theData = httpClient.doHttpRequest(aUrl, HttpPut.METHOD_NAME, "application/json", null, httpClient.addBasicAuthHeader(null, homegenieIP));
|
||||
log.debug("call Command return is: <<<{}>>>", theData);
|
||||
if (!theData.contains("OK"))
|
||||
if (theData.toLowerCase().contains("error"))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -61,12 +61,12 @@ public class HomeGenieInstance {
|
||||
if (hgModules != null && hgModules.length > 0) {
|
||||
deviceList = new ArrayList<Module>();
|
||||
for (int i = 0; i < hgModules.length; i++) {
|
||||
if (hgModules[i].isSwitch() || hgModules[i].isDimmer())
|
||||
if (hgModules[i].isModuleValid(homegenieIP.getExtensions()))
|
||||
deviceList.add(hgModules[i]);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Cannot get an devices for Homegenie {} Gson Parse Error.", homegenieIP.getName());
|
||||
log.warn("Cannot get devices for Homegenie {} Gson Parse Error.", homegenieIP.getName(), e);
|
||||
}
|
||||
}
|
||||
return deviceList;
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
|
||||
package com.bwssystems.HABridge.plugins.homegenie;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// import java.util.List;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class Module {
|
||||
private static final Logger log = LoggerFactory.getLogger(HomeGenieInstance.class);
|
||||
|
||||
@SerializedName("Name")
|
||||
@Expose
|
||||
@@ -23,10 +32,10 @@ public class Module {
|
||||
@Expose
|
||||
private String address;
|
||||
/*
|
||||
@SerializedName("Properties")
|
||||
@Expose
|
||||
private List<Property> properties = null;
|
||||
*/
|
||||
* @SerializedName("Properties")
|
||||
*
|
||||
* @Expose private List<Property> properties = null;
|
||||
*/
|
||||
@SerializedName("RoutingNode")
|
||||
@Expose
|
||||
private String routingNode;
|
||||
@@ -70,15 +79,13 @@ public class Module {
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
/*
|
||||
public List<Property> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(List<Property> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
*/
|
||||
/*
|
||||
* public List<Property> getProperties() { return properties; }
|
||||
*
|
||||
* public void setProperties(List<Property> properties) { this.properties =
|
||||
* properties; }
|
||||
*/
|
||||
public String getRoutingNode() {
|
||||
return routingNode;
|
||||
}
|
||||
@@ -95,10 +102,48 @@ public class Module {
|
||||
return isDeviceType("Dimmer");
|
||||
}
|
||||
|
||||
public boolean isLight() {
|
||||
return isDeviceType("Light");
|
||||
}
|
||||
|
||||
private boolean isDeviceType(String theType) {
|
||||
if(deviceType.equals(theType)) {
|
||||
if (deviceType.equals(theType)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isModuleValid(JsonObject theExtensions) {
|
||||
ModuleTypes moduleTypes = null;
|
||||
if (this.name == null || this.name.trim().isEmpty())
|
||||
return false;
|
||||
|
||||
if (isSwitch())
|
||||
return true;
|
||||
|
||||
if (isDimmer())
|
||||
return true;
|
||||
|
||||
if (isLight())
|
||||
return true;
|
||||
|
||||
if (theExtensions != null && theExtensions.isJsonObject() && theExtensions.get("moduleTypes").isJsonArray()) {
|
||||
try {
|
||||
moduleTypes = new Gson().fromJson(theExtensions, ModuleTypes.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not parse extensions for {} with {}", this.name, theExtensions);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (moduleTypes == null)
|
||||
return false;
|
||||
|
||||
for (ModuleType moduleType : moduleTypes.getModuleTypes()) {
|
||||
if (isDeviceType(moduleType.getModuleType()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.bwssystems.HABridge.plugins.homegenie;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class ModuleType {
|
||||
|
||||
@SerializedName("moduleType")
|
||||
@Expose
|
||||
private String moduleType;
|
||||
|
||||
public String getModuleType() {
|
||||
return moduleType;
|
||||
}
|
||||
|
||||
public void setModuleType(String moduleType) {
|
||||
this.moduleType = moduleType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bwssystems.HABridge.plugins.homegenie;
|
||||
|
||||
import java.util.List;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class ModuleTypes {
|
||||
|
||||
@SerializedName("moduleTypes")
|
||||
@Expose
|
||||
private List<ModuleType> moduleTypes = null;
|
||||
|
||||
public List<ModuleType> getModuleTypes() {
|
||||
return moduleTypes;
|
||||
}
|
||||
|
||||
public void setModuleTypes(List<ModuleType> moduleTypes) {
|
||||
this.moduleTypes = moduleTypes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,10 +41,10 @@ public class MozIotCommandDetail {
|
||||
public String getBody() {
|
||||
String theBody = "";
|
||||
|
||||
if(level != null && level != "") {
|
||||
if(level != null && !"".equals(level)) {
|
||||
theBody = "{\"level\":" + level + "}";
|
||||
}
|
||||
else if(color != null && color != "") {
|
||||
else if(color != null && !"".equals(color)) {
|
||||
theBody = "{\"color\":\"" + color + "\"}";
|
||||
} else {
|
||||
theBody = "{\"on\":" + on + "}";
|
||||
|
||||
@@ -19,15 +19,20 @@ public class MozIotInstance {
|
||||
private JWT moziotToken;
|
||||
private NamedIP mozIotIP;
|
||||
private NameValue[] headers;
|
||||
private HTTPHandler anHttpClient;
|
||||
|
||||
public MozIotInstance(NamedIP theNamedIp, HTTPHandler httpClient) {
|
||||
mozIotIP = theNamedIp;
|
||||
headers = null;
|
||||
gatewayLogin(httpClient);
|
||||
moziotToken = null;
|
||||
anHttpClient = httpClient;
|
||||
gatewayLogin();
|
||||
}
|
||||
|
||||
public Boolean callCommand(String deviceId, String aCommand, MozIotCommandDetail commandData, HTTPHandler httpClient) {
|
||||
log.debug("calling Mozilla IOT: {}:{}{}{}", mozIotIP.getIp(), mozIotIP.getPort(), aCommand, commandData.getBody());
|
||||
public Boolean callCommand(String deviceId, String aCommand, MozIotCommandDetail commandData,
|
||||
HTTPHandler httpClient) {
|
||||
log.debug("calling Mozilla IOT: {}:{}{}{}", mozIotIP.getIp(), mozIotIP.getPort(), aCommand,
|
||||
commandData.getBody());
|
||||
String aUrl = null;
|
||||
|
||||
if (mozIotIP.getSecure() != null && mozIotIP.getSecure())
|
||||
@@ -36,8 +41,9 @@ public class MozIotInstance {
|
||||
aUrl = "http://";
|
||||
headers = getAuthHeader();
|
||||
|
||||
aUrl = aUrl + mozIotIP.getIp() + ":" + mozIotIP.getPort() + "/things/" + deviceId + "/" + aCommand;
|
||||
String theData = httpClient.doHttpRequest(aUrl, HttpPut.METHOD_NAME, "application/json", commandData.getBody(), headers);
|
||||
aUrl = aUrl + mozIotIP.getIp() + ":" + mozIotIP.getPort() + "/things/" + deviceId + "/" + aCommand;
|
||||
String theData = httpClient.doHttpRequest(aUrl, HttpPut.METHOD_NAME, "application/json", commandData.getBody(),
|
||||
headers);
|
||||
log.debug("call Command return is: <<<{}>>>", theData);
|
||||
if (theData.contains("error") || theData.contains("ERROR") || theData.contains("Error"))
|
||||
return false;
|
||||
@@ -77,7 +83,10 @@ public class MozIotInstance {
|
||||
}
|
||||
|
||||
private NameValue[] getAuthHeader() {
|
||||
if (headers == null) {
|
||||
if (moziotToken == null)
|
||||
gatewayLogin();
|
||||
|
||||
if (headers == null && moziotToken != null) {
|
||||
headers = new NameValue[3];
|
||||
headers[0] = new NameValue();
|
||||
headers[0].setName("Authorization");
|
||||
@@ -88,13 +97,14 @@ public class MozIotInstance {
|
||||
headers[2] = new NameValue();
|
||||
headers[2].setName("Accept");
|
||||
headers[2].setValue("application/json");
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private void gatewayLogin(HTTPHandler httpClient) {
|
||||
private void gatewayLogin() {
|
||||
String aUrl = null;
|
||||
|
||||
moziotToken = null;
|
||||
if (mozIotIP.getSecure() != null && mozIotIP.getSecure())
|
||||
aUrl = "https://";
|
||||
else
|
||||
@@ -112,13 +122,15 @@ public class MozIotInstance {
|
||||
String commandData = "{\"email\": \"" + mozIotIP.getUsername() + "\", \"password\":\"" + mozIotIP.getPassword()
|
||||
+ "\"}";
|
||||
log.debug("The login body: {}", commandData);
|
||||
String theData = httpClient.doHttpRequest(aUrl, HttpPost.METHOD_NAME, "application/json", commandData, headers);
|
||||
String theData = anHttpClient.doHttpRequest(aUrl, HttpPost.METHOD_NAME, "application/json", commandData,
|
||||
headers);
|
||||
if (theData != null) {
|
||||
log.debug("GET Mozilla login - data: {}", theData);
|
||||
try {
|
||||
moziotToken = new Gson().fromJson(theData, JWT.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("Cannot get login for Mozilla IOT {} Gson Parse Error.", mozIotIP.getName());
|
||||
moziotToken = null;
|
||||
}
|
||||
} else {
|
||||
log.warn("Could not login {} error: <<<{}>>>", mozIotIP.getName(), theData);
|
||||
|
||||
@@ -7,9 +7,9 @@ import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class MozillaThing {
|
||||
|
||||
@SerializedName("name")
|
||||
@SerializedName("title")
|
||||
@Expose
|
||||
private String name;
|
||||
private String title;
|
||||
@SerializedName("type")
|
||||
@Expose
|
||||
private String type;
|
||||
@@ -47,14 +47,6 @@ public class MozillaThing {
|
||||
@Expose
|
||||
private Object iconHref;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
@@ -151,4 +143,12 @@ public class MozillaThing {
|
||||
this.iconHref = iconHref;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,86 +4,105 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.bwssystems.HABridge.BridgeControlDescriptor;
|
||||
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
|
||||
import com.bwssystems.HABridge.BridgeSettings;
|
||||
import com.bwssystems.HABridge.Configuration;
|
||||
import com.bwssystems.HABridge.api.hue.HueConstants;
|
||||
import com.bwssystems.HABridge.api.hue.HuePublicConfig;
|
||||
import com.bwssystems.HABridge.util.UDPDatagramSender;
|
||||
import com.bwssystems.HABridge.util.AddressUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
// import java.time.Instant;
|
||||
// import java.time.temporal.ChronoUnit;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.http.conn.util.*;
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
public class UpnpListener {
|
||||
private Logger log = LoggerFactory.getLogger(UpnpListener.class);
|
||||
private UDPDatagramSender theUDPDatagramSender;
|
||||
private MulticastSocket upnpMulticastSocket;
|
||||
private int httpServerPort;
|
||||
private String upnpConfigIP;
|
||||
// private boolean strict;
|
||||
private boolean upnpOriginal;
|
||||
private boolean upnpAdvanced;
|
||||
private boolean traceupnp;
|
||||
private boolean useUpnpIface;
|
||||
private BridgeControlDescriptor bridgeControl;
|
||||
private String bridgeId;
|
||||
private String bridgeSNUUID;
|
||||
private String httpType;
|
||||
private HuePublicConfig aHueConfig;
|
||||
private Integer theUpnpSendDelay;
|
||||
private String responseTemplateOriginal = "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/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n" + "USN: uuid:"
|
||||
+ HueConstants.UUID_PREFIX + "%s::urn:schemas-upnp-org:device:basic:1\r\n\r\n";
|
||||
|
||||
/* This is the minimum response needed, all others are for the advanced setting */
|
||||
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/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:"
|
||||
+ HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n";
|
||||
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n"
|
||||
+ "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
|
||||
|
||||
/* These next 2 templates are for the advanced upnp option */
|
||||
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/"
|
||||
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: uuid:" + HueConstants.UUID_PREFIX
|
||||
+ "%s\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%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/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n"
|
||||
+ "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
|
||||
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:"
|
||||
+ HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n";
|
||||
|
||||
private String notifyTemplate = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
|
||||
+ "LOCATION: http://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
|
||||
|
||||
/* These notify templates are for the advanced upnp option */
|
||||
private String notifyTemplate1 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
|
||||
+ "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n" + "NT: uuid:"
|
||||
+ HueConstants.UUID_PREFIX + "%s\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
|
||||
|
||||
private String notifyTemplate2 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
|
||||
+ "LOCATION: http://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
|
||||
+ "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n"
|
||||
+ "NT: upnp:rootdevice\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n";
|
||||
|
||||
private String notifyTemplate3 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
|
||||
+ "LOCATION: http://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
|
||||
+ "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n"
|
||||
+ "NT: urn:schemas-upnp-org:device:basic:1\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
|
||||
|
||||
public UpnpListener(BridgeSettingsDescriptor theSettings, BridgeControlDescriptor theControl,
|
||||
public UpnpListener(BridgeSettings theSettings, BridgeControlDescriptor theControl,
|
||||
UDPDatagramSender aUdpDatagramSender) throws IOException {
|
||||
super();
|
||||
theUDPDatagramSender = aUdpDatagramSender;
|
||||
upnpMulticastSocket = null;
|
||||
httpServerPort = Integer.valueOf(theSettings.getServerPort());
|
||||
upnpConfigIP = theSettings.getUpnpConfigAddress();
|
||||
httpServerPort = Integer.valueOf(theSettings.getBridgeSettingsDescriptor().getServerPort());
|
||||
upnpConfigIP = theSettings.getBridgeSettingsDescriptor().getUpnpConfigAddress();
|
||||
// strict = theSettings.isUpnpStrict();
|
||||
upnpOriginal = theSettings.isUpnporiginal();
|
||||
traceupnp = theSettings.isTraceupnp();
|
||||
useUpnpIface = theSettings.isUseupnpiface();
|
||||
theUpnpSendDelay = theSettings.getUpnpsenddelay();
|
||||
upnpOriginal = theSettings.getBridgeSettingsDescriptor().isUpnporiginal();
|
||||
upnpAdvanced = theSettings.getBridgeSettingsDescriptor().isUpnpadvanced();
|
||||
traceupnp = theSettings.getBridgeSettingsDescriptor().isTraceupnp();
|
||||
useUpnpIface = theSettings.getBridgeSettingsDescriptor().isUseupnpiface();
|
||||
theUpnpSendDelay = theSettings.getBridgeSettingsDescriptor().getUpnpsenddelay();
|
||||
bridgeControl = theControl;
|
||||
aHueConfig = HuePublicConfig.createConfig("temp", upnpConfigIP, HueConstants.HUB_VERSION,
|
||||
theSettings.getHubmac());
|
||||
theSettings.getBridgeSettingsDescriptor().getHubmac());
|
||||
bridgeId = aHueConfig.getBridgeid();
|
||||
bridgeSNUUID = aHueConfig.getSNUUIDFromMac();
|
||||
if (theSettings.getBridgeSecurity().isUseHttps()) {
|
||||
httpType = "https";
|
||||
} else {
|
||||
httpType = "http";
|
||||
}
|
||||
|
||||
try {
|
||||
if (useUpnpIface)
|
||||
upnpMulticastSocket = new MulticastSocket(
|
||||
new InetSocketAddress(upnpConfigIP, Configuration.UPNP_DISCOVERY_PORT));
|
||||
else
|
||||
// This commented out code does not work... leave for review
|
||||
// if (useUpnpIface)
|
||||
// upnpMulticastSocket = new MulticastSocket(
|
||||
// new InetSocketAddress(upnpConfigIP, Configuration.UPNP_DISCOVERY_PORT));
|
||||
// else
|
||||
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): "
|
||||
@@ -94,6 +113,11 @@ public class UpnpListener {
|
||||
}
|
||||
|
||||
public boolean startListening() {
|
||||
if (bridgeControl.isReinit() || bridgeControl.isStop()) {
|
||||
log.warn("UPNP Listener exiting as reinit or stop requested....");
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("UPNP Discovery Listener starting....");
|
||||
Enumeration<NetworkInterface> ifs = null;
|
||||
|
||||
@@ -106,6 +130,7 @@ public class UpnpListener {
|
||||
return false;
|
||||
}
|
||||
|
||||
InetAddress theUpnpAddress = null;
|
||||
while (ifs.hasMoreElements()) {
|
||||
NetworkInterface xface = ifs.nextElement();
|
||||
Enumeration<InetAddress> addrs = xface.getInetAddresses();
|
||||
@@ -120,12 +145,16 @@ public class UpnpListener {
|
||||
if (traceupnp)
|
||||
log.info("Traceupnp: Interface: " + name + " valid usable IP address: " + addr);
|
||||
IPsPerNic++;
|
||||
} else if (addr.getHostAddress().equals(upnpConfigIP)) {
|
||||
} else if (useUpnpIface && (addr.getHostAddress().equals(upnpConfigIP) || name.equals("lo"))) {
|
||||
if (traceupnp)
|
||||
log.info("Traceupnp: Interface: " + name + " matches upnp config address of IP address: "
|
||||
+ addr);
|
||||
IPsPerNic++;
|
||||
}
|
||||
|
||||
if (addr.getHostAddress().equals(upnpConfigIP)) {
|
||||
theUpnpAddress = addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("Checking " + name + " to our interface set");
|
||||
@@ -143,16 +172,44 @@ public class UpnpListener {
|
||||
}
|
||||
}
|
||||
|
||||
JmDNS jmdns = null;
|
||||
if (theUpnpAddress != null) {
|
||||
log.info("Create and run mDNS service.");
|
||||
try {
|
||||
// Create a JmDNS instance
|
||||
jmdns = JmDNS.create(theUpnpAddress, "Philips-hue");
|
||||
|
||||
// Register a service - Defined TXT keys: bridgeid, modelid
|
||||
final HashMap<String, String> values = new HashMap<String, String>();
|
||||
values.put("modelid", HueConstants.MODEL_ID);
|
||||
values.put("bridgeid", bridgeId);
|
||||
ServiceInfo serviceInfo = ServiceInfo.create("_hue._tcp.local.",
|
||||
"Philips Hue - " + bridgeId.substring(bridgeId.length() - 6), httpServerPort, 0, 0, values);
|
||||
jmdns.registerService(serviceInfo);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.warn("Could not start mDNS service for hue api. {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (bridgeControl.isReinit() || bridgeControl.isStop()) {
|
||||
log.warn("UPNP Listener exiting as reinit or stop requested....");
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("UPNP Discovery Listener running and ready....");
|
||||
boolean loopControl = true;
|
||||
boolean error = false;
|
||||
try {
|
||||
upnpMulticastSocket.setSoTimeout((int) Configuration.UPNP_NOTIFY_TIMEOUT);
|
||||
} catch (SocketException e1) {
|
||||
log.warn("Could not sent soTimeout on multi-cast socket");
|
||||
|
||||
if(upnpAdvanced) {
|
||||
try {
|
||||
upnpMulticastSocket.setSoTimeout((int) Configuration.UPNP_NOTIFY_TIMEOUT);
|
||||
} catch (SocketException e1) {
|
||||
log.warn("Could not sent soTimeout on multi-cast socket");
|
||||
}
|
||||
}
|
||||
Instant current, previous;
|
||||
previous = Instant.now();
|
||||
// Instant current, previous;
|
||||
// previous = Instant.now();
|
||||
while (loopControl) { // trigger shutdown here
|
||||
byte[] buf = new byte[1024];
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
||||
@@ -161,21 +218,31 @@ public class UpnpListener {
|
||||
if (isSSDPDiscovery(packet)) {
|
||||
try {
|
||||
sendUpnpResponse(packet);
|
||||
} catch (IOException e) {
|
||||
} catch (Exception 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);
|
||||
}
|
||||
}
|
||||
|
||||
current = Instant.now();
|
||||
if (ChronoUnit.MILLIS.between(previous, current) > Configuration.UPNP_NOTIFY_TIMEOUT) {
|
||||
sendUpnpNotify(socketAddress.getAddress());
|
||||
previous = Instant.now();
|
||||
}
|
||||
|
||||
/*
|
||||
* current = Instant.now(); if (ChronoUnit.MILLIS.between(previous, current) >
|
||||
* Configuration.UPNP_NOTIFY_TIMEOUT) { try {
|
||||
* sendUpnpNotify(socketAddress.getAddress()); } catch (IOException e) { log.
|
||||
* warn("UpnpListener encountered an error sending upnp notify packets. IP: " +
|
||||
* packet.getAddress().getHostAddress() + " with message: " + e.getMessage());
|
||||
* log.debug("UpnpListener send upnp notify exception: ", e); } previous =
|
||||
* Instant.now();
|
||||
*
|
||||
* }
|
||||
*/
|
||||
} catch (SocketTimeoutException e) {
|
||||
sendUpnpNotify(socketAddress.getAddress());
|
||||
try {
|
||||
sendUpnpNotify(socketAddress.getAddress());
|
||||
} catch (IOException en) {
|
||||
log.warn("UpnpListener encountered an error sending upnp notify packets. IP: "
|
||||
+ packet.getAddress().getHostAddress() + " with message: " + en.getMessage());
|
||||
log.debug("UpnpListener send upnp notify exception: ", en);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("UpnpListener encountered an error reading socket. Shutting down", e);
|
||||
error = true;
|
||||
@@ -190,6 +257,10 @@ public class UpnpListener {
|
||||
}
|
||||
}
|
||||
upnpMulticastSocket.close();
|
||||
if (jmdns != null) {
|
||||
// Unregister all services
|
||||
jmdns.unregisterAllServices();
|
||||
}
|
||||
if (bridgeControl.isReinit())
|
||||
log.info("UPNP Discovery Listener - ended, restart found");
|
||||
if (bridgeControl.isStop())
|
||||
@@ -207,6 +278,7 @@ public class UpnpListener {
|
||||
protected boolean isSSDPDiscovery(DatagramPacket packet) {
|
||||
// Only respond to discover request for strict upnp form
|
||||
String packetString = new String(packet.getData(), 0, packet.getLength());
|
||||
// log.info("Packet string <<<" + packetString + ">>>");
|
||||
if (packetString != null && packetString.startsWith("M-SEARCH * HTTP/1.1")
|
||||
&& packetString.contains("\"ssdp:discover\"")) {
|
||||
if ((packetString.contains("ST: urn:schemas-upnp-org:device:basic:1")
|
||||
@@ -236,66 +308,49 @@ public class UpnpListener {
|
||||
}
|
||||
|
||||
protected void sendUpnpResponse(DatagramPacket aPacket) throws IOException {
|
||||
SocketAddress requesterAddress = aPacket.getSocketAddress();
|
||||
// SocketAddress requesterAddress = aPacket.getSocketAddress();
|
||||
InetAddress requester = aPacket.getAddress();
|
||||
int sourcePort = aPacket.getPort();
|
||||
String discoveryResponse = null;
|
||||
// refactored suggestion by https://github.com/pvint
|
||||
String httpLocationAddress = getOutboundAddress(requesterAddress).getHostAddress();
|
||||
String httpLocationAddress = null;
|
||||
if (useUpnpIface) {
|
||||
httpLocationAddress = upnpConfigIP;
|
||||
} else {
|
||||
// refactored suggestion by https://github.com/pvint
|
||||
httpLocationAddress = AddressUtil.getOutboundAddress(requester.getHostAddress(), sourcePort).getHostAddress();
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
} catch (InterruptedException e) {
|
||||
// noop
|
||||
}
|
||||
|
||||
if (upnpOriginal) {
|
||||
discoveryResponse = String.format(responseTemplateOriginal, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template Original with response address: "
|
||||
+ httpLocationAddress + ":" + httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplateOriginal is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
} else {
|
||||
discoveryResponse = String.format(responseTemplateOriginal, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template Original with response address: "
|
||||
+ httpLocationAddress + ":" + httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplateOriginal is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
} catch (InterruptedException e) {
|
||||
// noop
|
||||
}
|
||||
discoveryResponse = String.format(responseTemplate1, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template 1 with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplate1 is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
discoveryResponse = String.format(responseTemplate1, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId,
|
||||
bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template 1 with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort);
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort
|
||||
+ " with discovery responseTemplate1 is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
|
||||
if(upnpAdvanced) {
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
} catch (InterruptedException e) {
|
||||
// noop
|
||||
}
|
||||
discoveryResponse = String.format(responseTemplate2, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID,
|
||||
bridgeSNUUID);
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId,
|
||||
bridgeSNUUID, bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template 2 with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
+ httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort);
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort
|
||||
+ " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
|
||||
@@ -305,12 +360,13 @@ public class UpnpListener {
|
||||
// noop
|
||||
}
|
||||
discoveryResponse = String.format(responseTemplate3, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId,
|
||||
bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template 3 with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
+ httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort);
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort
|
||||
+ " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
}
|
||||
@@ -318,13 +374,17 @@ public class UpnpListener {
|
||||
|
||||
private void sendUDPResponse(byte[] udpMessage, InetAddress requester, int sourcePort) throws IOException {
|
||||
log.debug("Sending response string: <<<" + new String(udpMessage) + ">>>");
|
||||
if (upnpMulticastSocket == null)
|
||||
throw new IOException("Socket not initialized");
|
||||
DatagramPacket response = new DatagramPacket(udpMessage, udpMessage.length, requester, sourcePort);
|
||||
upnpMulticastSocket.send(response);
|
||||
if (upnpOriginal) {
|
||||
theUDPDatagramSender.sendUDPResponse(udpMessage, requester, sourcePort);
|
||||
} else {
|
||||
if (upnpMulticastSocket == null)
|
||||
throw new IOException("Socket not initialized");
|
||||
DatagramPacket response = new DatagramPacket(udpMessage, udpMessage.length, requester, sourcePort);
|
||||
upnpMulticastSocket.send(response);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendUpnpNotify(InetAddress aSocketAddress) {
|
||||
protected void sendUpnpNotify(InetAddress aSocketAddress) throws IOException {
|
||||
String notifyData = null;
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
@@ -332,9 +392,14 @@ public class UpnpListener {
|
||||
// noop
|
||||
}
|
||||
|
||||
notifyData = String.format(notifyTemplate, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID, bridgeSNUUID);
|
||||
sendNotifyDatagram(notifyData, aSocketAddress, "notifyTemplate1");
|
||||
notifyData = String.format(notifyTemplate1, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpType, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID,
|
||||
bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: sendUpnpNotify notifyTemplate1");
|
||||
}
|
||||
log.debug("sendUpnpNotify notifyTemplate1 is <<<{}>>>", notifyData);
|
||||
sendUDPResponse(notifyData.getBytes(), aSocketAddress, Configuration.UPNP_DISCOVERY_PORT);
|
||||
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
@@ -343,8 +408,12 @@ public class UpnpListener {
|
||||
}
|
||||
|
||||
notifyData = String.format(notifyTemplate2, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
sendNotifyDatagram(notifyData, aSocketAddress, "notifyTemplate2");
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpType, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: sendUpnpNotify notifyTemplate2");
|
||||
}
|
||||
log.debug("sendUpnpNotify notifyTemplate2 is <<<{}>>>", notifyData);
|
||||
sendUDPResponse(notifyData.getBytes(), aSocketAddress, Configuration.UPNP_DISCOVERY_PORT);
|
||||
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
@@ -353,40 +422,11 @@ public class UpnpListener {
|
||||
}
|
||||
|
||||
notifyData = String.format(notifyTemplate3, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
sendNotifyDatagram(notifyData, aSocketAddress, "notifyTemplate3");
|
||||
}
|
||||
|
||||
public void sendNotifyDatagram(String notifyData, InetAddress aSocketAddress, String templateNumber) {
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpType, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: sendUpnpNotify {}", templateNumber);
|
||||
log.info("Traceupnp: sendUpnpNotify notifyTemplate3");
|
||||
}
|
||||
log.debug("sendUpnpNotify {} is <<<{}>>>", templateNumber, notifyData);
|
||||
DatagramPacket notifyPacket = new DatagramPacket(notifyData.getBytes(), notifyData.length(), aSocketAddress,
|
||||
Configuration.UPNP_DISCOVERY_PORT);
|
||||
try {
|
||||
upnpMulticastSocket.send(notifyPacket);
|
||||
} catch (IOException e1) {
|
||||
log.warn("UpnpListener encountered an error sending upnp {}. IP: {} with message: {}", templateNumber,
|
||||
notifyPacket.getAddress().getHostAddress(), e1.getMessage());
|
||||
log.debug("UpnpListener send {} exception: ", templateNumber, e1);
|
||||
}
|
||||
}
|
||||
|
||||
// added by https://github.com/pvint
|
||||
// Ruthlessly stolen from
|
||||
// https://stackoverflow.com/questions/22045165/java-datagrampacket-receive-how-to-determine-local-ip-interface
|
||||
// Try to get a source IP that makes sense for the requestor to contact for use
|
||||
// in the LOCATION header in replies
|
||||
private InetAddress getOutboundAddress(SocketAddress remoteAddress) throws SocketException {
|
||||
DatagramSocket sock = new DatagramSocket();
|
||||
// connect is needed to bind the socket and retrieve the local address
|
||||
// later (it would return 0.0.0.0 otherwise)
|
||||
sock.connect(remoteAddress);
|
||||
final InetAddress localAddress = sock.getLocalAddress();
|
||||
sock.disconnect();
|
||||
sock.close();
|
||||
sock = null;
|
||||
return localAddress;
|
||||
log.debug("sendUpnpNotify notifyTemplate3 is <<<{}>>>", notifyData);
|
||||
sendUDPResponse(notifyData.getBytes(), aSocketAddress, Configuration.UPNP_DISCOVERY_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,10 @@ import com.bwssystems.HABridge.BridgeSettings;
|
||||
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
|
||||
import com.bwssystems.HABridge.api.hue.HueConstants;
|
||||
import com.bwssystems.HABridge.api.hue.HuePublicConfig;
|
||||
import com.bwssystems.HABridge.util.ParseRoute;
|
||||
import com.bwssystems.HABridge.util.AddressUtil;
|
||||
|
||||
import static spark.Spark.get;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -24,7 +20,7 @@ public class UpnpSettingsResource {
|
||||
private BridgeSettingsDescriptor theSettings;
|
||||
private BridgeSettings bridgeSettings;
|
||||
|
||||
private String hueTemplate = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
|
||||
private String hueTemplate_pre = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
|
||||
+ "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
|
||||
+ "<specVersion>\n"
|
||||
+ "<major>1</major>\n"
|
||||
@@ -41,8 +37,9 @@ public class UpnpSettingsResource {
|
||||
+ "<modelNumber>" + HueConstants.MODEL_ID + "</modelNumber>\n"
|
||||
+ "<modelURL>http://www.meethue.com</modelURL>\n"
|
||||
+ "<serialNumber>%s</serialNumber>\n"
|
||||
+ "<UDN>uuid:" + HueConstants.UUID_PREFIX + "%s</UDN>\n"
|
||||
+ "<presentationURL>index.html</presentationURL>\n"
|
||||
+ "<UDN>uuid:" + HueConstants.UUID_PREFIX + "%s</UDN>\n";
|
||||
|
||||
private String hueTemplate_post = "<presentationURL>index.html</presentationURL>\n"
|
||||
+ "<iconList>\n"
|
||||
+ "<icon>\n"
|
||||
+ "<mimetype>image/png</mimetype>\n"
|
||||
@@ -58,9 +55,22 @@ public class UpnpSettingsResource {
|
||||
+ "<depth>24</depth>\n"
|
||||
+ "<url>hue_logo_3.png</url>\n"
|
||||
+ "</icon>\n"
|
||||
+ "</iconList>\n"
|
||||
+ "</device>\n"
|
||||
+ "</root>\n";
|
||||
+ "</iconList>\n";
|
||||
|
||||
private String hueTemplate_end = "</device>\n"
|
||||
+ "</root>\n";
|
||||
|
||||
/* not utilizing this section any more
|
||||
private String hueTemplate_mid_orig = "<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";
|
||||
*/
|
||||
|
||||
public UpnpSettingsResource(BridgeSettings theBridgeSettings) {
|
||||
super();
|
||||
@@ -80,7 +90,30 @@ public class UpnpSettingsResource {
|
||||
|
||||
String portNumber = Integer.toString(request.port());
|
||||
String filledTemplate = null;
|
||||
String httpLocationAddr = getOutboundAddress(request.ip(), request.port()).getHostAddress();
|
||||
String httpLocationAddr = null;
|
||||
String hueTemplate = null;
|
||||
if(theSettings.isUpnporiginal()) {
|
||||
httpLocationAddr = theSettings.getUpnpConfigAddress();
|
||||
hueTemplate = hueTemplate_pre + hueTemplate_end;
|
||||
} else if(!theSettings.isUpnpadvanced()) {
|
||||
if(theSettings.isUseupnpiface()) {
|
||||
httpLocationAddr = theSettings.getUpnpConfigAddress();
|
||||
} else {
|
||||
log.debug("Get Outbound address for ip:" + request.ip() + " and port:" + request.port());
|
||||
httpLocationAddr = AddressUtil.getOutboundAddress(request.ip(), request.port()).getHostAddress();
|
||||
}
|
||||
hueTemplate = hueTemplate_pre + hueTemplate_end;
|
||||
} else {
|
||||
|
||||
if(theSettings.isUseupnpiface()) {
|
||||
httpLocationAddr = theSettings.getUpnpConfigAddress();
|
||||
} else {
|
||||
log.debug("Get Outbound address for ip:" + request.ip() + " and port:" + request.port());
|
||||
httpLocationAddr = AddressUtil.getOutboundAddress(request.ip(), request.port()).getHostAddress();
|
||||
}
|
||||
hueTemplate = hueTemplate_pre + hueTemplate_post + hueTemplate_end;
|
||||
}
|
||||
|
||||
String bridgeIdMac = HuePublicConfig.createConfig("temp", httpLocationAddr, HueConstants.HUB_VERSION, theSettings.getHubmac()).getSNUUIDFromMac();
|
||||
filledTemplate = String.format(hueTemplate, httpLocationAddr, portNumber, httpLocationAddr, bridgeIdMac, bridgeIdMac);
|
||||
if(theSettings.isTraceupnp())
|
||||
@@ -115,28 +148,4 @@ public class UpnpSettingsResource {
|
||||
return "";
|
||||
} );
|
||||
}
|
||||
// added by https://github.com/pvint
|
||||
// Ruthlessly stolen from https://stackoverflow.com/questions/22045165/java-datagrampacket-receive-how-to-determine-local-ip-interface
|
||||
// Try to get a source IP that makes sense for the requester to contact for use in the LOCATION header in replies
|
||||
private InetAddress getOutboundAddress(String remoteAddress, int remotePort) {
|
||||
InetAddress localAddress = null;
|
||||
try {
|
||||
DatagramSocket sock = new DatagramSocket();
|
||||
// connect is needed to bind the socket and retrieve the local address
|
||||
// later (it would return 0.0.0.0 otherwise)
|
||||
sock.connect(new InetSocketAddress(remoteAddress, remotePort));
|
||||
localAddress = sock.getLocalAddress();
|
||||
sock.disconnect();
|
||||
sock.close();
|
||||
sock = null;
|
||||
} catch(Exception e) {
|
||||
ParseRoute theRoute = ParseRoute.getInstance();
|
||||
try {
|
||||
localAddress = InetAddress.getByName(theRoute.getLocalIPAddress());
|
||||
} catch(Exception e1) {}
|
||||
log.warn("Error <" + e.getMessage() + "> on determining interface to reply for <" + remoteAddress + ">. Using default route IP Address of " + localAddress.getHostAddress());
|
||||
}
|
||||
log.debug("getOutbountAddress returning IP Address of " + localAddress.getHostAddress());
|
||||
return localAddress;
|
||||
}
|
||||
}
|
||||
|
||||
62
src/main/java/com/bwssystems/HABridge/util/AddressUtil.java
Normal file
62
src/main/java/com/bwssystems/HABridge/util/AddressUtil.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package com.bwssystems.HABridge.util;
|
||||
|
||||
import java.net.*;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AddressUtil {
|
||||
final static Logger log = LoggerFactory.getLogger(AddressUtil.class);
|
||||
|
||||
// added by https://github.com/pvint
|
||||
// Ruthlessly stolen from
|
||||
// https://stackoverflow.com/questions/22045165/java-datagrampacket-receive-how-to-determine-local-ip-interface
|
||||
// Try to get a source IP that makes sense for the requester to contact for use
|
||||
// in the LOCATION header in replies
|
||||
public static InetAddress getOutboundAddress(String remoteAddress, int remotePort) {
|
||||
InetAddress localAddress = null;
|
||||
log.debug("Entering getOutboundAddress with args.");
|
||||
try {
|
||||
localAddress = getOutboundAddress(new InetSocketAddress(remoteAddress, remotePort));
|
||||
} catch (Exception e) {
|
||||
log.debug("getOutboundAddress(SocketAddress) Threw an Exception: " + e.getMessage());
|
||||
ParseRoute theRoute = ParseRoute.getInstance();
|
||||
try {
|
||||
localAddress = InetAddress.getByName(theRoute.getLocalIPAddress());
|
||||
log.warn("Error <" + e.getMessage() + "> on determining interface to reply for <" + remoteAddress
|
||||
+ ">. Using default route IP Address of " + localAddress.getHostAddress());
|
||||
} catch (Exception e1) {
|
||||
log.error("Cannot find address for parsed local ip address: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if(localAddress != null)
|
||||
log.debug("localAddress is IP Address of " + localAddress.getHostAddress());
|
||||
else
|
||||
log.debug("localAddress returning NULL");
|
||||
return localAddress;
|
||||
}
|
||||
|
||||
// added by https://github.com/pvint
|
||||
// Ruthlessly stolen from
|
||||
// https://stackoverflow.com/questions/22045165/java-datagrampacket-receive-how-to-determine-local-ip-interface
|
||||
// Try to get a source IP that makes sense for the requestor to contact for use
|
||||
// in the LOCATION header in replies
|
||||
public static InetAddress getOutboundAddress(SocketAddress remoteAddress) throws SocketException {
|
||||
DatagramSocket sock = new DatagramSocket();
|
||||
// connect is needed to bind the socket and retrieve the local address
|
||||
// later (it would return 0.0.0.0 otherwise)
|
||||
log.debug("Entering getOutboundAddress with socket arg.");
|
||||
sock.connect(remoteAddress);
|
||||
log.debug("getOutboundAddress(SocketAddress) getLocalAddress.");
|
||||
final InetAddress localAddress = sock.getLocalAddress();
|
||||
sock.disconnect();
|
||||
sock.close();
|
||||
sock = null;
|
||||
if(localAddress != null)
|
||||
log.debug("getOutbountAddress(SocketAddress) returning IP Address of " + localAddress.getHostAddress());
|
||||
else
|
||||
log.debug("getOutboundAddress(SocketAddress) returning NULL");
|
||||
return localAddress;
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/public/hue_logo_0.png
Normal file
BIN
src/main/resources/public/hue_logo_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
BIN
src/main/resources/public/hue_logo_3.png
Normal file
BIN
src/main/resources/public/hue_logo_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -366,13 +366,29 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng
|
||||
);
|
||||
};
|
||||
|
||||
this.changeSecuritySettings = function (useLinkButton, secureHueApi, execGarden) {
|
||||
this.changeSecuritySettings = function (useLinkButton, secureHueApi, execGarden, useHttps, keyfilePath, keyfilePassword) {
|
||||
if(useHttps) {
|
||||
if(!keyfilePassword || keyfilePassword.length == 0 || !keyfilePassword.trim()) {
|
||||
self.displayErrorMessage("Use HTTPS - ", "Key File Password cannot be empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!keyfilePath || keyfilePath.length == 0 || !keyfilePath.trim()) {
|
||||
self.displayErrorMessage("Use HTTPS - ", "Key File Path cannot be empty.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var newSecurityInfo = {};
|
||||
newSecurityInfo = {
|
||||
useLinkButton: useLinkButton,
|
||||
secureHueApi: secureHueApi,
|
||||
execGarden: execGarden
|
||||
execGarden: execGarden,
|
||||
useHttps: useHttps,
|
||||
keyfilePath: keyfilePath,
|
||||
keyfilePassword: keyfilePassword
|
||||
};
|
||||
|
||||
return $http.post(this.state.systemsbase + "/changesecurityinfo", newSecurityInfo).then(
|
||||
function (response) {
|
||||
self.state.securityInfo = response.data;
|
||||
@@ -463,7 +479,7 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng
|
||||
this.pushLinkButton = function () {
|
||||
return $http.put(this.state.systemsbase + "/presslinkbutton").then(
|
||||
function (response) {
|
||||
self.displayTimer("Link your device", 30000);
|
||||
self.displayTimer("Link your device", self.state.settings.linkbuttontimeout * 1000);
|
||||
},
|
||||
function (error) {
|
||||
if (error.status === 401)
|
||||
@@ -1531,26 +1547,42 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng
|
||||
};
|
||||
|
||||
this.toXY = function (red, green, blue) {
|
||||
//Gamma corrective
|
||||
red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
|
||||
green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
|
||||
blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
|
||||
var r = red / 255;
|
||||
var g = green / 255;
|
||||
var b = blue / 255;
|
||||
|
||||
//R
|
||||
if ( r > 0.04045)
|
||||
r = Math.pow(( ( r + 0.055 ) / 1.055 ), 2.4);
|
||||
else
|
||||
r /= 12.92;
|
||||
|
||||
//G
|
||||
if ( g > 0.04045)
|
||||
g = Math.pow(( ( g + 0.055 ) / 1.055 ), 2.4);
|
||||
else
|
||||
g /= 12.92;
|
||||
|
||||
//B
|
||||
if ( b > 0.04045)
|
||||
b = Math.pow(( ( b + 0.055 ) / 1.055 ), 2.4);
|
||||
else
|
||||
b /= 12.92;
|
||||
|
||||
r *= 100;
|
||||
g *= 100;
|
||||
b *= 100;
|
||||
|
||||
var x = 0.412453 * r + 0.35758 * g + 0.180423 * b;
|
||||
var y = 0.212671 * r + 0.71516 * g + 0.072169 * b;
|
||||
var z = 0.019334 * r + 0.119193 * g + 0.950227 * b;
|
||||
|
||||
var newX = x / (x + y + z);
|
||||
var newY = y / (x + y + z);
|
||||
var interBright = (y / 100) * 254;
|
||||
var newBright = Math.round(interBright);
|
||||
|
||||
//Apply wide gamut conversion D65
|
||||
var X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
|
||||
var Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
|
||||
var Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;
|
||||
|
||||
var fx = X / (X + Y + Z);
|
||||
var fy = Y / (X + Y + Z);
|
||||
if (isNaN(fx)) {
|
||||
fx = 0.0;
|
||||
}
|
||||
if (isNaN(fy)) {
|
||||
fy = 0.0;
|
||||
}
|
||||
|
||||
return [fx.toPrecision(4), fy.toPrecision(4)];
|
||||
return [newX.toPrecision(6), newY.toPrecision(6), newBright];
|
||||
};
|
||||
|
||||
this.testUrl = function (device, type, value, valueType) {
|
||||
@@ -1576,7 +1608,7 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng
|
||||
if (valueType === "color" && value) {
|
||||
if (addComma)
|
||||
testBody = testBody + ",";
|
||||
testBody = testBody + "\"xy\": [" + value[0] + "," + value[1] + "]";
|
||||
testBody = testBody + "\"xy\": [" + value[0] + "," + value[1] + "],\"bri\":" + value[2];
|
||||
}
|
||||
testBody = testBody + "}";
|
||||
if (testBody === "{}") {
|
||||
@@ -2071,12 +2103,43 @@ app.controller('SystemController', function ($scope, $location, bridgeService, n
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addHomeGenietoSettings = function (newhomegeniename, newhomegenieip, newhomegenieport, newhomegenieusername, newhomegeniepassword, newhomegeniewebhook, newhomegeniesecure) {
|
||||
$scope.addHomeGenietoSettings = function (newhomegeniename, newhomegenieip, newhomegenieport, newhomegenieusername, newhomegeniepassword, newhomegeniewebhook, newhomegeniesecure, newhomegenieothertypes) {
|
||||
if ($scope.bridge.settings.homegenieaddress === undefined || $scope.bridge.settings.homegenieaddress === null) {
|
||||
$scope.bridge.settings.homegenieaddress = {
|
||||
devices: []
|
||||
};
|
||||
}
|
||||
|
||||
var othertypes = [];
|
||||
var count = 0;
|
||||
var theModuleTypes = [];
|
||||
|
||||
if(newhomegenieothertypes && newhomegenieothertypes.trim() && newhomegenieothertypes.length > 0) {
|
||||
othertypes = newhomegenieothertypes.split(",");
|
||||
if (othertypes.length > 0) {
|
||||
for (var i = 0; i < othertypes.length; i++) {
|
||||
var aType = othertypes[i].trim();
|
||||
if (aType.length > 0) {
|
||||
var moduleType = {
|
||||
moduleType: aType
|
||||
};
|
||||
theModuleTypes.push(moduleType);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var theExtension;
|
||||
if (count == 0) {
|
||||
theExtension = undefined;
|
||||
} else {
|
||||
theExtension = {
|
||||
moduleTypes: theModuleTypes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var newhomegenie = {
|
||||
name: newhomegeniename,
|
||||
ip: newhomegenieip,
|
||||
@@ -2084,16 +2147,61 @@ app.controller('SystemController', function ($scope, $location, bridgeService, n
|
||||
username: newhomegenieusername,
|
||||
password: newhomegeniepassword,
|
||||
secure: newhomegeniesecure,
|
||||
webhook: newhomegeniewebhook
|
||||
webhook: newhomegeniewebhook,
|
||||
extensions: theExtension
|
||||
};
|
||||
$scope.bridge.settings.homegenieaddress.devices.push(newhomegenie);
|
||||
$scope.newhomegeniename = null;
|
||||
$scope.newhomegenieip = null;
|
||||
$scope.newhomegenieport = "8080";
|
||||
$scope.newhomegenieport = null;
|
||||
$scope.newhomegenieusername = null;
|
||||
$scope.newhomegeniepassword = null;
|
||||
$scope.newhomegeniewebhook = null;
|
||||
$scope.newhomegenieextensions = null;
|
||||
};
|
||||
|
||||
$scope.updateModuleTypes = function (theIndex, homegenieExtensions) {
|
||||
var othertypes = [];
|
||||
if(homegenieExtensions != undefined)
|
||||
othertypes = homegenieExtensions.split(",");
|
||||
var theModuleTypes = [];
|
||||
var count = 0;
|
||||
if (othertypes.length > 0) {
|
||||
for (var x = 0; x < othertypes.length; x++) {
|
||||
var aType = othertypes[x].trim();
|
||||
if (aType.length > 0) {
|
||||
var moduleType = {
|
||||
moduleType: aType
|
||||
};
|
||||
theModuleTypes.push(moduleType);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
var theExtension;
|
||||
if (count == 0) {
|
||||
theExtension = undefined;
|
||||
} else {
|
||||
theExtension = {
|
||||
moduleTypes: theModuleTypes
|
||||
}
|
||||
|
||||
}
|
||||
$scope.bridge.settings.homegenieaddress.devices[theIndex].extensions = theExtension;
|
||||
};
|
||||
|
||||
$scope.convertModuleTypes = function (theIndex) {
|
||||
|
||||
var displayExtension = "";
|
||||
if ($scope.bridge.settings.homegenieaddress.devices[theIndex].extensions != undefined && $scope.bridge.settings.homegenieaddress.devices[theIndex].extensions.moduleTypes != undefined) {
|
||||
for (var i = 0; i < $scope.bridge.settings.homegenieaddress.devices[theIndex].extensions.moduleTypes.length; i++) {
|
||||
displayExtension = displayExtension + $scope.bridge.settings.homegenieaddress.devices[theIndex].extensions.moduleTypes[i].moduleType;
|
||||
}
|
||||
}
|
||||
|
||||
return displayExtension;
|
||||
};
|
||||
|
||||
$scope.removeHomeGenietoSettings = function (homegeniename, homegenieip) {
|
||||
for (var i = $scope.bridge.settings.homegenieaddress.devices.length - 1; i >= 0; i--) {
|
||||
if ($scope.bridge.settings.homegenieaddress.devices[i].name === homegeniename && $scope.bridge.settings.homegenieaddress.devices[i].ip === homegenieip) {
|
||||
@@ -2215,13 +2323,17 @@ app.controller('SecurityDialogCtrl', function ($scope, bridgeService, ngDialog)
|
||||
$scope.useLinkButton = bridgeService.state.securityInfo.useLinkButton;
|
||||
$scope.execGarden = bridgeService.state.securityInfo.execGarden;
|
||||
$scope.isSecure = bridgeService.state.securityInfo.isSecure;
|
||||
$scope.useHttps = bridgeService.state.securityInfo.useHttps;
|
||||
$scope.keyfilePath = bridgeService.state.securityInfo.keyfilePath;
|
||||
$scope.keyfilePassword = bridgeService.state.securityInfo.keyfilePassword;
|
||||
|
||||
$scope.matched = false;
|
||||
$scope.addingUser = false;
|
||||
$scope.showPassword = $scope.isSecure;
|
||||
$scope.firstTime = true;
|
||||
|
||||
$scope.setSecurityInfo = function () {
|
||||
bridgeService.changeSecuritySettings($scope.useLinkButton, $scope.secureHueApi, $scope.execGarden);
|
||||
bridgeService.changeSecuritySettings($scope.useLinkButton, $scope.secureHueApi, $scope.execGarden, $scope.useHttps, $scope.keyfilePath, $scope.keyfilePassword);
|
||||
};
|
||||
|
||||
$scope.changePassword = function (password, password2) {
|
||||
@@ -2409,7 +2521,7 @@ app.controller('ViewingController', function ($scope, $location, bridgeService,
|
||||
bridgeService.editDevice(device);
|
||||
$location.path('/editdevice');
|
||||
};
|
||||
$scope.setStartupAction = function(device) {
|
||||
$scope.setStartupAction = function (device) {
|
||||
$scope.bridge.device = device;
|
||||
ngDialog.open({
|
||||
template: 'startupActionDialog',
|
||||
@@ -2426,7 +2538,7 @@ app.controller('ViewingController', function ($scope, $location, bridgeService,
|
||||
};
|
||||
|
||||
$scope.toggleLock = function (device) {
|
||||
if(device.lockDeviceId) {
|
||||
if (device.lockDeviceId) {
|
||||
device.lockDeviceId = false;
|
||||
} else {
|
||||
device.lockDeviceId = true;
|
||||
@@ -2567,9 +2679,9 @@ app.controller('StartupActionDialogCtrl', function ($scope, bridgeService, ngDia
|
||||
$scope.device = $scope.bridge.device;
|
||||
$scope.setDim = false;
|
||||
var components = [];
|
||||
if($scope.device.startupActions != undefined) {
|
||||
if ($scope.device.startupActions != undefined) {
|
||||
components = $scope.device.startupActions.split(":");
|
||||
if(components[1] != undefined && components[1].length > 0)
|
||||
if (components[1] != undefined && components[1].length > 0)
|
||||
$scope.setDim = true;
|
||||
} else {
|
||||
components = "::".split(":");
|
||||
@@ -2593,12 +2705,12 @@ app.controller('StartupActionDialogCtrl', function ($scope, bridgeService, ngDia
|
||||
console.log("Startup action set for device called: " + device.name);
|
||||
ngDialog.close('ngdialog1');
|
||||
var theValue = 1;
|
||||
if($scope.setDim) {
|
||||
if ($scope.setDim) {
|
||||
theValue = $scope.theState + ":" + $scope.slider.value + ":" + $scope.rgbPicker.color;
|
||||
} else {
|
||||
theValue = $scope.theState + "::" + $scope.rgbPicker.color;
|
||||
}
|
||||
if(theValue == "::")
|
||||
if (theValue == "::")
|
||||
theValue = "";
|
||||
device.startupActions = theValue;
|
||||
bridgeService.addDevice(device).then(
|
||||
@@ -3595,7 +3707,7 @@ app.controller('HassController', function ($scope, $location, bridgeService, ngD
|
||||
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
|
||||
offpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"off\"}";
|
||||
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, null, true, hassdevice.hassname + "-" + hassdevice.deviceState.entity_id, hassdevice.deviceState.entity_id, hassdevice.hassname, hassdevice.domain, "hassDevice", null, null);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, null, true, hassdevice.hassname + "-" + hassdevice.deviceState.entity_id, hassdevice.deviceState.entity_id, hassdevice.hassname, "switch", "hassDevice", null, null);
|
||||
$scope.device = bridgeService.state.device;
|
||||
if (!buildonly) {
|
||||
bridgeService.editNewDevice($scope.device);
|
||||
@@ -3725,7 +3837,7 @@ app.controller('HomeWizardController', function ($scope, $location, bridgeServic
|
||||
onpayload = "{\"deviceid\":\"" + homewizarddevice.id + "\",\"action\":\"on\"}";
|
||||
offpayload = "{\"deviceid\":\"" + homewizarddevice.id + "\",\"action\":\"off\"}";
|
||||
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, null, true, homewizarddevice.id, homewizarddevice.name, homewizarddevice.gateway, null, "homewizardDevice", null, null);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, null, true, homewizarddevice.id, homewizarddevice.name, homewizarddevice.gateway, "switch", "homewizardDevice", null, null);
|
||||
$scope.device = bridgeService.state.device;
|
||||
|
||||
if (!buildonly) {
|
||||
@@ -4019,7 +4131,7 @@ app.controller('LifxController', function ($scope, $location, bridgeService, ngD
|
||||
dimpayload = angular.toJson(lifxdevice);
|
||||
onpayload = angular.toJson(lifxdevice);
|
||||
offpayload = angular.toJson(lifxdevice);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, null, true, lifxdevice.name, lifxdevice.name, lifxdevice.name, null, "lifxDevice", null, null);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, null, true, lifxdevice.name, lifxdevice.name, lifxdevice.name, "switch", "lifxDevice", null, null);
|
||||
$scope.device = bridgeService.state.device;
|
||||
if (!buildonly) {
|
||||
bridgeService.editNewDevice($scope.device);
|
||||
@@ -4320,7 +4432,7 @@ app.controller('OpenHABController', function ($scope, $location, bridgeService,
|
||||
else
|
||||
colorpayload = null;
|
||||
}
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, openhabdevice.item.name + "-" + openhabdevice.name, openhabdevice.item.name, openhabdevice.name, openhabdevice.item.type, "openhabDevice", null, null);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, openhabdevice.item.name + "-" + openhabdevice.name, openhabdevice.item.name, openhabdevice.name, "switch", "openhabDevice", null, null);
|
||||
$scope.device = bridgeService.state.device;
|
||||
if (!buildonly) {
|
||||
bridgeService.editNewDevice($scope.device);
|
||||
@@ -4462,7 +4574,7 @@ app.controller('MozIotController', function ($scope, $location, bridgeService, n
|
||||
else if (coloraction !== undefined && coloraction !== null && coloraction !== '')
|
||||
colorpayload = "{\"url\":\"" + preCmd + "color\",\"command\":{\"color\":\"" + coloraction + "\"}}";
|
||||
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, moziotdevice.deviceDetail.name + "-" + moziotdevice.deviceDetail.type, moziotdevice.deviceDetail.name, moziotdevice.gatewayName, "Switch", "moziotDevice", null, null);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, moziotdevice.deviceDetail.title + "-" + moziotdevice.deviceDetail.type, moziotdevice.deviceDetail.title, moziotdevice.gatewayName, "switch", "moziotDevice", null, null);
|
||||
$scope.device = bridgeService.state.device;
|
||||
if (!buildonly) {
|
||||
bridgeService.editNewDevice($scope.device);
|
||||
@@ -4606,7 +4718,7 @@ app.controller('FhemController', function ($scope, $location, bridgeService, ngD
|
||||
colorpayload = null;
|
||||
onpayload = "{\"url\":\"http://" + fhemdevice.address + preCmd + "\",\"command\":\"on\"}";
|
||||
offpayload = "{\"url\":\"http://" + fhemdevice.address + preCmd + "\",\"command\":\"off\"}";
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, fhemdevice.item.Name + "-" + fhemdevice.name, fhemdevice.item.Name, fhemdevice.name, fhemdevice.item.type, "fhemDevice", null, null);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, fhemdevice.item.Name + "-" + fhemdevice.name, fhemdevice.item.Name, fhemdevice.name, "switch", "fhemDevice", null, null);
|
||||
$scope.device = bridgeService.state.device;
|
||||
if (!buildonly) {
|
||||
bridgeService.editNewDevice($scope.device);
|
||||
@@ -4765,7 +4877,7 @@ app.controller('BroadlinkController', function ($scope, $location, bridgeService
|
||||
else
|
||||
colorpayload = null;
|
||||
}
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, broadlinkdevice.id, broadlinkdevice.name, broadlinkdevice.id, broadlinkdevice.desc, "broadlinkDevice", null, null);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, broadlinkdevice.id, broadlinkdevice.name, broadlinkdevice.id, "switch", "broadlinkDevice", null, null);
|
||||
$scope.device = bridgeService.state.device;
|
||||
if (!buildonly) {
|
||||
bridgeService.editNewDevice($scope.device);
|
||||
@@ -4905,7 +5017,7 @@ app.controller('HomeGenieController', function ($scope, $location, bridgeService
|
||||
// else if (coloraction !== undefined && coloraction !== null && coloraction !== '')
|
||||
// colorpayload = "{\"url\":\"" + preCmd + "color\",\"command\":{\"color\":\"" + coloraction + "\"}}";
|
||||
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, homegeniedevice.deviceDetail.Name + "-" + homegeniedevice.deviceDetail.DeviceType, homegeniedevice.deviceDetail.Name, homegeniedevice.gatewayName, homegeniedevice.deviceDetail.DeviceType, "homegenieDevice", null, null);
|
||||
bridgeService.buildUrls(onpayload, dimpayload, offpayload, colorpayload, true, homegeniedevice.deviceDetail.Name + "-" + homegeniedevice.deviceDetail.DeviceType, homegeniedevice.deviceDetail.Name, homegeniedevice.gatewayName, "switch", "homegenieDevice", null, null);
|
||||
$scope.device = bridgeService.state.device;
|
||||
if (!buildonly) {
|
||||
bridgeService.editNewDevice($scope.device);
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<option value="button">Button</option>
|
||||
<option value="thermo">Thermo</option>
|
||||
<option value="passthru">Pass Thru</option>
|
||||
<option value="home">Home</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -64,7 +65,6 @@
|
||||
<table class="table table-bordered table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Row</th>
|
||||
<th sortable-header col="id" comparator-fn="comparatorUniqueId">ID</th>
|
||||
<th sortable-header col="name">Name</th>
|
||||
<th sortable-header col="description">Description</th>
|
||||
@@ -78,7 +78,6 @@
|
||||
</thead>
|
||||
<tr ng-repeat="device in bridge.devices | filterDevicesByRequester:bridge.state.filterDevicesByIpAddress:bridge.state.filterDevicesOnlyFiltered:bridge.state.filterDeviceType"
|
||||
row-id="{{device.id}}" ng-class="{info: bridge.viewDevId == device.id}">
|
||||
<td>{{$index+1}}</td>
|
||||
<td title="Locked: {{device.lockDeviceId}} - Click to Toggle" ng-click="toggleLock(device)">
|
||||
<b ng-if="device.lockDeviceId">{{device.id}}</b>
|
||||
<p ng-if="!device.lockDeviceId">{{device.id}}</p>
|
||||
|
||||
@@ -114,6 +114,18 @@
|
||||
ng-model="device.onFirstDim" ng-true-value=true
|
||||
ng-false-value=false> {{device.onFirstDim}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label>Dim only when On present (If dim is present and the on request is present, the on request will not be sent. This is overriden by the above settings.)</label></td>
|
||||
<td><input type="checkbox"
|
||||
ng-model="device.dimNoOn" ng-true-value=true
|
||||
ng-false-value=false> {{device.dimNoOn}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label>Send dim when color request present and dim present</label></td>
|
||||
<td><input type="checkbox"
|
||||
ng-model="device.dimOnColor" ng-true-value=true
|
||||
ng-false-value=false> {{device.dimOnColor}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label>Filter Address (comma separated list)</label></td>
|
||||
<td><input type="text" class="form-control" id="device-requester-addr"
|
||||
@@ -144,7 +156,8 @@
|
||||
<option value="button">Button</option>
|
||||
<option value="thermo">Thermo</option>
|
||||
<option value="passthru">Pass Thru</option>
|
||||
</select></td>
|
||||
<option value="home">Home</option>
|
||||
</select></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label>Map Type (Legacy)</label></td>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<li ng-if="bridge.showHomeWizard" role="presentation"><a href="#!/homewizarddevices">HomeWizard Devices</a></li>
|
||||
<li ng-if="bridge.showOpenHAB" role="presentation"><a href="#!/openhabdevices">OpenHAB Devices</a></li>
|
||||
<li ng-if="bridge.showFHEM" role="presentation"><a href="#!/fhemdevices">FHEM Devices</a></li>
|
||||
<li ng-if="bridge.showMozIot" role="presentation"><a href="#!/homegeniedevices">HomeGenie Devices</a></li>
|
||||
<li ng-if="bridge.showMozIot" role="presentation"><a href="#!/moziotdevices">Mozilla IOT Devices</a></li>
|
||||
<li ng-if="bridge.showBroadlink" role="presentation"><a href="#!/broadlinkdevices">Broadlink Devices</a></li>
|
||||
<li ng-if="bridge.showHomeGenie" role="presentation" class="active"><a href="#!/homegeniedevices">HomeGenie
|
||||
Devices</a></li>
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">HomeGenie show Device List
|
||||
<h2 class="panel-title">HomeGenie show device List
|
||||
({{bridge.homegeniedevices.length}})</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
@@ -96,7 +96,7 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">
|
||||
Already Configured OpenHAB Devices <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}}
|
||||
Already Configured HomeGenie Devices <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}}
|
||||
aria-hidden="true"></span></a></a>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -19,10 +19,9 @@
|
||||
<li ng-if="bridge.showHomeWizard" role="presentation"><a href="#!/homewizarddevices">HomeWizard Devices</a></li>
|
||||
<li ng-if="bridge.showOpenHAB" role="presentation"><a href="#!/openhabdevices">OpenHAB Devices</a></li>
|
||||
<li ng-if="bridge.showFHEM" role="presentation"><a href="#!/fhemdevices">FHEM Devices</a></li>
|
||||
<li ng-if="bridge.showHomeGenie" role="presentation"><a href="#!/homegeniedevices">HomeGenie Devices</a></li>
|
||||
<li ng-if="bridge.showMozIot" role="presentation" class="active"><a href="#!/moziotdevices">Mozilla IOT Devices</a>
|
||||
</li>
|
||||
<li ng-if="bridge.showMozIot" role="presentation" class="active"><a href="#!/moziotdevices">Mozilla IOT Devices</a></li>
|
||||
<li ng-if="bridge.showBroadlink" role="presentation"><a href="#!/broadlinkdevices">Broadlink Devices</a></li>
|
||||
<li ng-if="bridge.showHomeGenie" role="presentation"><a href="#!/homegeniedevices">HomeGenie Devices</a></li>
|
||||
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -72,10 +71,10 @@
|
||||
</thead>
|
||||
<tr ng-repeat="moziotdevice in bridge.moziotdevices">
|
||||
<td>{{$index+1}}</td>
|
||||
<td><input type="checkbox" name="bulk.devices[]" value="{{moziotdevice.deviceDetail.name}}"
|
||||
ng-checked="bulk.devices.indexOf(moziotdevice.deviceDetail.name) > -1"
|
||||
ng-click="toggleSelection(moziotdevice.deviceDetail.name)">
|
||||
{{moziotdevice.deviceDetail.name}}</td>
|
||||
<td><input type="checkbox" name="bulk.devices[]" value="{{moziotdevice.deviceDetail.title}}"
|
||||
ng-checked="bulk.devices.indexOf(moziotdevice.deviceDetail.title) > -1"
|
||||
ng-click="toggleSelection(moziotdevice.deviceDetail.title)">
|
||||
{{moziotdevice.deviceDetail.title}}</td>
|
||||
<td>{{moziotdevice.deviceDetail.type}}</td>
|
||||
<td>{{moziotdevice.gatewayName}}</td>
|
||||
<td>
|
||||
@@ -104,7 +103,7 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">
|
||||
Already Configured OpenHAB Devices <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}}
|
||||
Already Configured Mozilla IOT Devices <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}}
|
||||
aria-hidden="true"></span></a></a>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
<div class="form-container ngdialog-message" ng-controller="SecurityDialogCtrl">
|
||||
|
||||
<form name="securityForm" role="form">
|
||||
<legend class="form-label">Update Security Settings</legend>
|
||||
<legend class="form-label">Update Security Settings</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Use Link Button</label>
|
||||
<input type="checkbox"
|
||||
<label>Use HTTPS</label>
|
||||
<input type="checkbox"
|
||||
ng-model="useHttps" ng-true-value=true
|
||||
ng-false-value=false /> {{useHttps}}
|
||||
<div>
|
||||
<label>Keyfile Path</label>
|
||||
<input id="keyfilePath" name="keyfilePath" class="form-control"
|
||||
type="text" ng-model="keyfilePath" placeholder="the keyfile path"/>
|
||||
<label>Keyfile Password</label>
|
||||
<input id="keyfilePassword" name="keyfilePassword" class="form-control" type="password" ng-model="keyfilePassword"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Use Link Button</label>
|
||||
<input type="checkbox"
|
||||
ng-model="useLinkButton" ng-true-value=true
|
||||
ng-false-value=false /> {{useLinkButton}}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<label>Exec Garden</label>
|
||||
<input id="execGarden" name="execGarden" class="form-control"
|
||||
type="text" ng-model="execGarden" placeholder="execGarden path"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Use username/password for HUE Api</label>
|
||||
<input type="checkbox"
|
||||
ng-model="secureHueApi" ng-true-value=true
|
||||
|
||||
@@ -699,7 +699,7 @@
|
||||
<td><input type="checkbox" ng-model="newmoziotsecure" ng-true-value=true
|
||||
ng-false-value=false></td>
|
||||
<td><button class="btn btn-success" type="submit"
|
||||
ng-click="addMozIottoSettings(newmoziotname, newmoziotip, newmoziotport, newsmoziotusername, newmoziotpassword, newmoziotwebhook, newmoziotsecure)">Add</button>
|
||||
ng-click="addMozIottoSettings(newmoziotname, newmoziotip, newmoziotport, newmoziotusername, newmoziotpassword, newmoziotwebhook, newmoziotsecure)">Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -716,6 +716,7 @@
|
||||
<th>Port (opt)</th>
|
||||
<th>Username (opt)</th>
|
||||
<th>Password (opt)</th>
|
||||
<th>Other Types (opt)</th>
|
||||
<th>Use SSL</th>
|
||||
<th>Manage</th>
|
||||
</tr>
|
||||
@@ -732,6 +733,11 @@
|
||||
<td><input id="bridge-settings-next-homegenie-password" class="form-control"
|
||||
type="password" ng-model="homegenie.password" placeholder="HomeGenie password">
|
||||
</td>
|
||||
<td>Currently: {{ convertModuleTypes($index) }}
|
||||
<input id="bridge-settings-next-homegenie-othertypes" class="form-control" type="text"
|
||||
ng-model="anExtensions" placeholder="type1, type2, type3">
|
||||
<button class="btn btn-success" type="submit" ng-click="updateModuleTypes($index, anExtensions)">Update</button>
|
||||
</td>
|
||||
<td><input type="checkbox" ng-model="homegenie.secure" ng-true-value=true
|
||||
ng-false-value=false></td>
|
||||
<td><button class="btn btn-danger" type="submit"
|
||||
@@ -750,10 +756,12 @@
|
||||
<td><input id="bridge-settings-new-homegenie-password" class="form-control"
|
||||
type="password" ng-model="newhomegeniepassword"
|
||||
placeholder="HomeGenie password "></td>
|
||||
<td><input id="bridge-settings-new-homegenie-othertypes" class="form-control" type="text"
|
||||
ng-model="newhomegenieothertypes" placeholder="type1, type2, type3"></td>
|
||||
<td><input type="checkbox" ng-model="newhomegeniesecure" ng-true-value=true
|
||||
ng-false-value=false></td>
|
||||
<td><button class="btn btn-success" type="submit"
|
||||
ng-click="addHomeGenietoSettings(newhomegeniename, newhomegenieip, newhomegenieport, newshomegenieusername, newhomegeniepassword, newhomegeniewebhook, newhomegeniesecure)">Add</button>
|
||||
ng-click="addHomeGenietoSettings(newhomegeniename, newhomegenieip, newhomegenieport, newhomegenieusername, newhomegeniepassword, newhomegeniewebhook, newhomegeniesecure, newhomegenieothertypes)">Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -816,6 +824,16 @@
|
||||
<td><input type="checkbox" ng-model="bridge.settings.upnporiginal" ng-true-value=true
|
||||
ng-false-value=false> {{bridge.settings.upnporiginal}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UPNP Advanced (use multiple responses and notifies)</td>
|
||||
<td><input type="checkbox" ng-model="bridge.settings.upnpadvanced" ng-true-value=true
|
||||
ng-false-value=false> {{bridge.settings.upnpadvanced}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unique ID to use 9 Octets (Renumber after saving this setting)</td>
|
||||
<td><input type="checkbox" ng-model="bridge.settings.uidnineoctets" ng-true-value=true
|
||||
ng-false-value=false> {{bridge.settings.uidnineoctets}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Trace UPNP Calls</td>
|
||||
<td><input type="checkbox" ng-model="bridge.settings.traceupnp" ng-true-value=true
|
||||
@@ -831,6 +849,11 @@
|
||||
<td><input id="bridge-settings-upnpsenddelay" class="form-control" type="number"
|
||||
ng-model="bridge.settings.upnpsenddelay" min="100" max="15000"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Link Button Timeout (seconds)</td>
|
||||
<td><input id="bridge-settings-linkbuttontimeout" class="form-control" type="number"
|
||||
ng-model="bridge.settings.linkbuttontimeout" min="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ID Seed (start numbering from this value)</td>
|
||||
<td><input id="bridge-settings-seedid" class="form-control" type="number"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bwssystems.color.test;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -9,23 +10,143 @@ import org.junit.Test;
|
||||
|
||||
import com.bwssystems.HABridge.hue.ColorData;
|
||||
import com.bwssystems.HABridge.hue.ColorDecode;
|
||||
import com.bwssystems.HABridge.hue.HueSatBri;
|
||||
import com.bwssystems.HABridge.hue.XYColorSpace;
|
||||
import com.bwssystems.HABridge.hue.ColorConverter;
|
||||
|
||||
public class ConvertCIEColorTestCase {
|
||||
|
||||
@Test
|
||||
public void testColorConversion() {
|
||||
public void testColorConverterXYtoRGB() {
|
||||
ArrayList<Double> xy = new ArrayList<Double>(Arrays.asList(Double.parseDouble("0.20821628789344535"), Double.parseDouble("0.22503526273269103")));
|
||||
|
||||
XYColorSpace xyColor = new XYColorSpace();
|
||||
xyColor.setBrightness(56);
|
||||
float[] xyFloat = new float[2];
|
||||
xyFloat[0] = xy.get(0).floatValue();
|
||||
xyFloat[1] = xy.get(1).floatValue();
|
||||
xyColor.setXy(xyFloat);
|
||||
float[] xyz = ColorConverter.XYtoXYZ(xyColor);
|
||||
int[] rgb = ColorConverter.normalizeRGB(ColorConverter.XYZtoRGB(xyz[0], xyz[1], xyz[2]));
|
||||
List<Integer> rgbDecode = new ArrayList<Integer>();
|
||||
rgbDecode.add(0, rgb[0]);
|
||||
rgbDecode.add(1, rgb[1]);
|
||||
rgbDecode.add(2, rgb[2]);
|
||||
List<Integer> assertDecode = new ArrayList<Integer>();
|
||||
assertDecode.add(0, 60);
|
||||
assertDecode.add(1, 134);
|
||||
assertDecode.add(2, 196);
|
||||
Assert.assertEquals(rgbDecode, assertDecode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColorConverterRGBtoXY() {
|
||||
int red = 60;
|
||||
int green = 134;
|
||||
int blue = 196;
|
||||
|
||||
float[] xyz = ColorConverter.RGBtoXYZ(red, green, blue);
|
||||
XYColorSpace theColorSpace = ColorConverter.XYZtoXY(xyz[0], xyz[1], xyz[2]);
|
||||
List<Float> xyDecode = new ArrayList<Float>();
|
||||
xyDecode.add(0, theColorSpace.getXy()[0]);
|
||||
xyDecode.add(1, theColorSpace.getXy()[1]);
|
||||
List<Float> assertDecode = new ArrayList<Float>();
|
||||
assertDecode.add(0, 0.20821705f);
|
||||
assertDecode.add(1, 0.22506176f);
|
||||
Assert.assertEquals(xyDecode, assertDecode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColorConversionXYtoRGB1() {
|
||||
ArrayList<Double> xy = new ArrayList<Double>(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273")));
|
||||
|
||||
List<Integer> colorDecode = ColorDecode.convertCIEtoRGB(xy, 254);
|
||||
List<Integer> colorDecode = ColorDecode.convertCIEtoRGB(xy, 50);
|
||||
List<Integer> assertDecode = new ArrayList<Integer>();
|
||||
assertDecode.add(0, 255);
|
||||
assertDecode.add(1, 47);
|
||||
assertDecode.add(2, 43);
|
||||
assertDecode.add(1, 0);
|
||||
assertDecode.add(2, 5);
|
||||
Assert.assertEquals(colorDecode, assertDecode);
|
||||
|
||||
ColorData colorData = new ColorData(ColorData.ColorMode.XY, xy);
|
||||
int rgbIntVal = ColorDecode.getIntRGB(colorData, 254);
|
||||
Assert.assertEquals(rgbIntVal, 16723755);
|
||||
int rgbIntVal = ColorDecode.getIntRGB(colorData, 50);
|
||||
Assert.assertEquals(rgbIntVal, 16711685);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColorConversionXYtoRGB2() {
|
||||
ArrayList<Double> xy = new ArrayList<Double>(Arrays.asList(Double.parseDouble("0.32312"), Double.parseDouble("0.15539")));
|
||||
|
||||
List<Integer> colorDecode = ColorDecode.convertCIEtoRGB(xy, 59);
|
||||
List<Integer> assertDecode = new ArrayList<Integer>();
|
||||
assertDecode.add(0, 233);
|
||||
assertDecode.add(1, 0);
|
||||
assertDecode.add(2, 231);
|
||||
Assert.assertEquals(colorDecode, assertDecode);
|
||||
|
||||
ColorData colorData = new ColorData(ColorData.ColorMode.XY, xy);
|
||||
int rgbIntVal = ColorDecode.getIntRGB(colorData, 59);
|
||||
Assert.assertEquals(rgbIntVal, 15270119);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColorConversionXYtoRGB3() {
|
||||
ArrayList<Double> xy = new ArrayList<Double>(Arrays.asList(Double.parseDouble("0.20821628789344535"), Double.parseDouble("0.22503526273269103")));
|
||||
|
||||
List<Integer> colorDecode = ColorDecode.convertCIEtoRGB(xy, 56);
|
||||
List<Integer> assertDecode = new ArrayList<Integer>();
|
||||
assertDecode.add(0, 60);
|
||||
assertDecode.add(1, 134);
|
||||
assertDecode.add(2, 196);
|
||||
Assert.assertEquals(colorDecode, assertDecode);
|
||||
|
||||
// ColorData colorData = new ColorData(ColorData.ColorMode.XY, xy);
|
||||
// int rgbIntVal = ColorDecode.getIntRGB(colorData, 56);
|
||||
// Assert.assertEquals(rgbIntVal, 15270119);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColorConversionHSBtoRGB1() {
|
||||
HueSatBri hsb = new HueSatBri();
|
||||
hsb.setHue(37767);
|
||||
hsb.setSat(135);
|
||||
hsb.setBri(128);
|
||||
|
||||
List<Integer> colorDecode = ColorDecode.convertHSBtoRGB(hsb);
|
||||
List<Integer> assertDecode = new ArrayList<Integer>();
|
||||
assertDecode.add(0, 61);
|
||||
assertDecode.add(1, 134);
|
||||
assertDecode.add(2, 196);
|
||||
Assert.assertEquals(colorDecode, assertDecode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColorConverterRGBtoHSB() {
|
||||
int red = 61;
|
||||
int green = 134;
|
||||
int blue = 196;
|
||||
|
||||
float[] hsl = ColorConverter.RGBtoHSL(red, green, blue);
|
||||
List<Integer> hsbDecode = new ArrayList<Integer>();
|
||||
hsbDecode.add(0, (int) (hsl[0] / 360f * 65535f));
|
||||
hsbDecode.add(1, (int) (hsl[1] * 254f));
|
||||
hsbDecode.add(2, (int) (hsl[2] * 254f));
|
||||
List<Integer> assertDecode = new ArrayList<Integer>();
|
||||
assertDecode.add(0, 37783);
|
||||
assertDecode.add(1, 135);
|
||||
assertDecode.add(2, 127);
|
||||
Assert.assertEquals(hsbDecode, assertDecode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColorConversionCTtoRGB() {
|
||||
Integer ct = 500;
|
||||
|
||||
List<Integer> colorDecode = ColorDecode.convertCTtoRGB(ct);
|
||||
List<Integer> assertDecode = new ArrayList<Integer>();
|
||||
assertDecode.add(0, 255);
|
||||
assertDecode.add(1, 137);
|
||||
assertDecode.add(2, 14);
|
||||
Assert.assertEquals(colorDecode, assertDecode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user