mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[flicbutton] Initial contribution FlicButton Binding (#9234)
* [flicbutton] Initial contribution FlicButton Binding Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Add config parameter address for FlicButton thing Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Run spotless Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Code cleanup & docs improvement Signed-off-by: Patrick Fink <mail@pfink.de> * Apply suggestions from code review Co-authored-by: Fabian Wolter <github@fabian-wolter.de> * [flicbutton] Update LICENSE Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Apply suggestions from code review (2) & update to 3.1-SNAPSHOT Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Apply suggestions from code review (3) & fix offline status Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix 3rd party source for proper IDE integration Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Simplify config parsing Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Move everything to internal package Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Remove hyphens from port parameter docs example Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Change maintainer to openHAB project Signed-off-by: Patrick Fink <mail@pfink.de> * Apply docs suggestions + update to 3.2.0-SNAPSHOT Signed-off-by: Patrick Fink <mail@pfink.de> Co-authored-by: Matthew Skinner <matt@pcmus.com> * [flicbutton] Fix bridge offline & reconnect handling Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Close open socket on dispose Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Improve exception error message in ThingStatus Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix README title Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Improve exception error message in ThingStatus Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Style fixes Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Use trace log level for button clicks & status changes Signed-off-by: Patrick Fink <mail@pfink.de> * Apply doc improvements from code review Signed-off-by: Patrick Fink <mail@pfink.de> Co-authored-by: Matthew Skinner <matt@pcmus.com> * [flicbutton] Add binding to bom/openhab-addons Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Cleanup / remove guava leftover Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Remove online status description Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Improve flicd hostname label Signed-off-by: Patrick Fink <mail@pfink.de> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> * [flicbutton] Do not catch IllegalArgumentException anymore as its not neeed Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Use debug log level instead of info Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Update version and license Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix SAT warnings, e.g. add null handling annotations Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix SAT warnings (2) Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Concurrency refactoring & fixes Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Cancel initialization task also when already running Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Add javadoc and move FLIC_OPENHAB_EVENT_TRIGGER_MAP constant to constants class Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Use ThingStatusDetail.OFFLINE.GONE when Flic button was removed from bridge Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix FlicSimpleclientDiscoveryServiceImpl javadoc Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix required definition of thing types Signed-off-by: Patrick Fink <mail@pfink.de> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> Co-authored-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
parent
51bbd34cd7
commit
6c104e241a
@ -93,6 +93,7 @@
|
||||
/bundles/org.openhab.binding.exec/ @kgoderis
|
||||
/bundles/org.openhab.binding.feed/ @svilenvul
|
||||
/bundles/org.openhab.binding.feican/ @Hilbrand
|
||||
/bundles/org.openhab.binding.flicbutton/ @pfink
|
||||
/bundles/org.openhab.binding.fmiweather/ @ssalonen
|
||||
/bundles/org.openhab.binding.folderwatcher/ @goopilot
|
||||
/bundles/org.openhab.binding.folding/ @fa2k
|
||||
|
@ -456,6 +456,11 @@
|
||||
<artifactId>org.openhab.binding.feican</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.flicbutton</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.fmiweather</artifactId>
|
||||
|
21
bundles/org.openhab.binding.flicbutton/NOTICE
Normal file
21
bundles/org.openhab.binding.flicbutton/NOTICE
Normal file
@ -0,0 +1,21 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
fliclib-javaclient (files under src/3rdparty)
|
||||
|
||||
* License: CC0 1.0
|
||||
* Project: https://github.com/50ButtonsEach/fliclib-linux-hci
|
||||
* Source: https://github.com/50ButtonsEach/fliclib-linux-hci/tree/master/clientlib/java/lib/src/main/java/io/flic/fliclib/javaclient
|
130
bundles/org.openhab.binding.flicbutton/README.md
Normal file
130
bundles/org.openhab.binding.flicbutton/README.md
Normal file
@ -0,0 +1,130 @@
|
||||
# Flic Button Binding
|
||||
|
||||
openHAB binding for using [Flic Buttons](https://flic.io/)
|
||||
with a [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci) bridge.
|
||||
|
||||
Currently, although Flic Buttons are BLE devices, this binding only supports fliclib-linux-hci (flicd) as a bridge.
|
||||
The openHAB Bluetooth Bindings are not supported.
|
||||
Flicd requires a seperate Bluetooth adapter to work, so if you use this binding together with e.g. the
|
||||
[Bluez Binding](https://www.openhab.org/addons/bindings/bluetooth.bluez/),
|
||||
two physical Bluetooth adapters are required (one for Bluez and one for flicd).
|
||||
Be aware that flicd requires an initial internet connection for the verification of the buttons.
|
||||
After buttons are initially added to flicd, an internet connection is not required anymore.
|
||||
|
||||
## Supported Things
|
||||
|
||||
| Thing Type ID | Description |
|
||||
| --------------- | ------------------------- |
|
||||
| flicd-bridge | The bridge representing a running instance of [fliclib-linux-hci (flicd)](https://github.com/50ButtonsEach/fliclib-linux-hci) on the server. |
|
||||
| button | The Flic button (supports Flic 1 buttons as well as Flic 2 buttons) |
|
||||
|
||||
## Discovery
|
||||
|
||||
* There is no automatic discovery for flicd-bridge available.
|
||||
* After flicd-bridge is (manually) configured, buttons will be automatically discovered via background discovery as soon
|
||||
as they're added with [simpleclient](https://github.com/50ButtonsEach/fliclib-linux-hci).
|
||||
|
||||
If they're already attached to the flicd-bridge before configuring this binding, they can be discovered by triggering an
|
||||
active scan.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### flicd-bridge
|
||||
|
||||
Example for textual configuration:
|
||||
|
||||
```
|
||||
Bridge flicbutton:flicd-bridge:mybridge
|
||||
```
|
||||
|
||||
The default host is localhost:5551 (this should be sufficient if flicd is running with default settings on the same server as openHAB).
|
||||
If your flicd service is running somewhere else, specify it like this:
|
||||
|
||||
```
|
||||
Bridge flicbutton:flicd-bridge:mybridge [ hostname="<YOUR_HOSTNAME>", port=<YOUR_PORT>]
|
||||
```
|
||||
|
||||
If flicd is running on a remote host, please do not forget to start it with the parameter `-s <openHAB IP>`, otherwise it won't be accessible for openHAB (more details on [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci)).
|
||||
|
||||
### button
|
||||
|
||||
For the button, the only config parameter is the MAC address.
|
||||
Normally, no textual configuration is necessary as buttons are auto discovered as soon as the bridge is configured.
|
||||
If you want to use textual configuration anyway, you can do it like this:
|
||||
|
||||
```
|
||||
Bridge flicbutton:flicd-bridge:mybridge [ hostname="<YOUR_HOSTNAME>", port=<YOUR_PORT>] {
|
||||
Thing button myflic1 "<YOUR_LABEL>" [address ="<MAC_ADDRESS>"]
|
||||
Thing button myflic2 "<YOUR_LABEL>" [address ="<MAC_ADDRESS>"]
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
You can lookup the MAC addresses of your buttons within the inbox of the UI.
|
||||
You're free to choose any label you like for your button.
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel ID | Channel Type | Item Type | Description |
|
||||
| ------------------------- | ------------------------ | --------------------------| ------------------------------ |
|
||||
| rawbutton | [System Trigger Channel](https://www.openhab.org/docs/developer/bindings/thing-xml.html#system-trigger-channel-types) `system.rawbutton` | Depends on the [Trigger Profile](https://www.openhab.org/docs/configuration/items.html#profiles) used | Raw Button channel that triggers `PRESSED` and `RELEASED` events, allows to use openHAB profiles or own implementations via rules to detect e.g. double clicks, long presses etc. |
|
||||
| button | [System Trigger Channel](https://www.openhab.org/docs/developer/bindings/thing-xml.html#system-trigger-channel-types) `system.button` | Depends on the [Trigger Profile](https://www.openhab.org/docs/configuration/items.html#profiles) used | Button channel that is using Flic's implementation for detecting long, short or double clicks. Triggers `SHORT_PRESSED`,`DOUBLE_PRESSED` and `LONG_PRESSED` events. |
|
||||
| battery-level | [System State Channel](https://www.openhab.org/docs/developer/bindings/thing-xml.html#system-state-channel-types) `system.battery-level` | Number | Represents the battery level as a percentage (0-100%).
|
||||
## Example
|
||||
|
||||
### Initial setup
|
||||
|
||||
1. Setup and run flicd as described in [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci).
|
||||
Please consider that you need a separate Bluetooth adapter. Shared usage with other Bluetooth services (e.g. Bluez)
|
||||
is not possible.
|
||||
1. Connect your buttons to flicd using the simpleclient as described in
|
||||
[fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci). Flicd has to run in background the whole
|
||||
time, simpleclient can be killed after you successfully test the button connects.
|
||||
1. Add a flicd-bridge via the UI or textual configuration. Please consider that flicd only accepts connections from
|
||||
localhost by default, to enable remote connections from openHAB you have to use the `--server-addr` parameter as
|
||||
described in [fliclib-linux-hci](https://github.com/50ButtonsEach/fliclib-linux-hci).
|
||||
1. When the bridge is online, buttons newly added via simpleclient will automatically get discovered via background
|
||||
discovery. To discover buttons that were set up before the binding was setup, please run an active scan.
|
||||
|
||||
### Configuration Example using Profiles
|
||||
|
||||
[Profiles](https://www.openhab.org/docs/configuration/items.html#profiles) are the recommended way to use this binding.
|
||||
|
||||
demo.things:
|
||||
|
||||
```
|
||||
Bridge flicbutton:flicd-bridge:local-flicd {
|
||||
Thing button flic_livingroom "Yellow Button Living Room" [address = "60:13:B3:02:18:BD"]
|
||||
Thing button flic_kitchen "Black Button Kitchen" [address = "B5:7E:59:78:86:9F"]
|
||||
}
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```
|
||||
Dimmer Light_LivingRoom { channel="milight:rgbLed:milight2:4:ledbrightness", channel="flicbutton:button:local-flicd:flic_livingroom:rawbutton" [profile="rawbutton-toggle-switch"], channel="flicbutton:button:local-flicd:flic_kitchen:rawbutton" [profile="rawbutton-toggle-switch"] } // We have a combined kitchen / livingroom, so we control the living room lights with switches from the living room and from the kitchen
|
||||
Switch Light_Kitchen { channel="hue:group:1:kitchen-bulbs:switch", channel="flicbutton:button:local-flicd:flic_kitchen:rawbutton" [profile="rawbutton-toggle-switch"] }
|
||||
```
|
||||
|
||||
### Configuration Example using Rules
|
||||
|
||||
It's also possible to setup [Rules](https://www.openhab.org/docs/configuration/rules-dsl.html).
|
||||
The following rules help to initially test your setup as they'll trigger log messages on incoming events.
|
||||
|
||||
```
|
||||
rule "Button rule using the button channel"
|
||||
|
||||
when
|
||||
Channel "flicbutton:button:local-flicd:flic_livingroom:button" triggered SHORT_PRESSED
|
||||
then
|
||||
logInfo("Flic", "Flic 'short pressed' triggered")
|
||||
end
|
||||
|
||||
rule "Button rule directly using the rawbutton channel"
|
||||
|
||||
when
|
||||
Channel "flicbutton:button:local-flicd:flic_livingroom:rawbutton" triggered
|
||||
then
|
||||
logInfo("Flic", "Flic pressed: " + receivedEvent.event)
|
||||
end
|
||||
```
|
38
bundles/org.openhab.binding.flicbutton/pom.xml
Normal file
38
bundles/org.openhab.binding.flicbutton/pom.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.flicbutton</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: FlicButton Binding</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>src/3rdparty/java</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
121
bundles/org.openhab.binding.flicbutton/src/3rdparty/LICENSE
vendored
Normal file
121
bundles/org.openhab.binding.flicbutton/src/3rdparty/LICENSE
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
@ -0,0 +1,45 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Battery status listener.
|
||||
*
|
||||
* Add this listener to a {@link FlicClient} by executing {@link FlicClient#addBatteryStatusListener(BatteryStatusListener)}.
|
||||
*/
|
||||
public class BatteryStatusListener {
|
||||
private static AtomicInteger nextId = new AtomicInteger();
|
||||
int listenerId = nextId.getAndIncrement();
|
||||
|
||||
private Bdaddr bdaddr;
|
||||
Callbacks callbacks;
|
||||
|
||||
public BatteryStatusListener(Bdaddr bdaddr, Callbacks callbacks) {
|
||||
if (bdaddr == null) {
|
||||
throw new IllegalArgumentException("bdaddr is null");
|
||||
}
|
||||
if (callbacks == null) {
|
||||
throw new IllegalArgumentException("callbacks is null");
|
||||
}
|
||||
this.bdaddr = bdaddr;
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
public Bdaddr getBdaddr() {
|
||||
return bdaddr;
|
||||
}
|
||||
|
||||
public abstract static class Callbacks {
|
||||
/**
|
||||
* This will be called when the battery status has been updated.
|
||||
* It will also be called immediately after the battery status listener has been created.
|
||||
* If the button stays connected, this method will be called approximately every three hours.
|
||||
*
|
||||
* @param bdaddr Bluetooth device address
|
||||
* @param batteryPercentage A number between 0 and 100 for the battery level. Will be -1 if unknown.
|
||||
* @param timestamp Standard UNIX timestamp, in seconds, for the event.
|
||||
*/
|
||||
public abstract void onBatteryStatus(Bdaddr bdaddr, int batteryPercentage, long timestamp) throws IOException;
|
||||
}
|
||||
}
|
68
bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Bdaddr.java
vendored
Normal file
68
bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Bdaddr.java
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Bluetooth address.
|
||||
*/
|
||||
public class Bdaddr {
|
||||
private byte[] bytes;
|
||||
|
||||
/**
|
||||
* Creates a Bdaddr given the bluetooth address in string format.
|
||||
*
|
||||
* @param addr address of the format xx:xx:xx:xx:xx:xx
|
||||
*/
|
||||
public Bdaddr(String addr) {
|
||||
bytes = new byte[6];
|
||||
bytes[5] = (byte)Integer.parseInt(addr.substring(0, 2), 16);
|
||||
bytes[4] = (byte)Integer.parseInt(addr.substring(3, 5), 16);
|
||||
bytes[3] = (byte)Integer.parseInt(addr.substring(6, 8), 16);
|
||||
bytes[2] = (byte)Integer.parseInt(addr.substring(9, 11), 16);
|
||||
bytes[1] = (byte)Integer.parseInt(addr.substring(12, 14), 16);
|
||||
bytes[0] = (byte)Integer.parseInt(addr.substring(15, 17), 16);
|
||||
}
|
||||
|
||||
Bdaddr(InputStream stream) throws IOException {
|
||||
bytes = new byte[6];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
bytes[i] = (byte)stream.read();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] getBytes() {
|
||||
return bytes.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a string representing the bluetooth address.
|
||||
*
|
||||
* @return A string of the bdaddr
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%02x:%02x:%02x:%02x:%02x:%02x", bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (bytes[0] & 0xff) ^ ((bytes[1] & 0xff) << 8) ^ ((bytes[2] & 0xff) << 16) ^ ((bytes[3] & 0xff) << 24) ^ (bytes[4] & 0xff) ^ ((bytes[5] & 0xff) << 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof Bdaddr)) {
|
||||
return false;
|
||||
}
|
||||
Bdaddr other = (Bdaddr)obj;
|
||||
return Arrays.equals(bytes, other.bytes);
|
||||
}
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import io.flic.fliclib.javaclient.enums.*;
|
||||
|
||||
/**
|
||||
* Button connection channel.
|
||||
*
|
||||
* Add this button connection channel to a {@link FlicClient} by executing {@link FlicClient#addConnectionChannel(ButtonConnectionChannel)}.
|
||||
* You may only have this connection channel added to one {@link FlicClient} at a time.
|
||||
*/
|
||||
public class ButtonConnectionChannel {
|
||||
private static AtomicInteger nextId = new AtomicInteger();
|
||||
int connId = nextId.getAndIncrement();
|
||||
|
||||
FlicClient client;
|
||||
|
||||
private Bdaddr bdaddr;
|
||||
private LatencyMode latencyMode;
|
||||
private short autoDisconnectTime;
|
||||
Callbacks callbacks;
|
||||
|
||||
final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* Create a connection channel using the specified parameters.
|
||||
*
|
||||
* Add this button connection channel to a {@link FlicClient} by executing {@link FlicClient#addConnectionChannel(ButtonConnectionChannel)}.
|
||||
*
|
||||
* @param bdaddr
|
||||
* @param latencyMode
|
||||
* @param autoDisconnectTime Number of seconds (0 - 511) until disconnect if no button event happens. 512 disables this feature.
|
||||
* @param callbacks
|
||||
*/
|
||||
public ButtonConnectionChannel(Bdaddr bdaddr, LatencyMode latencyMode, short autoDisconnectTime, Callbacks callbacks) {
|
||||
if (bdaddr == null) {
|
||||
throw new IllegalArgumentException("bdaddr is null");
|
||||
}
|
||||
if (latencyMode == null) {
|
||||
throw new IllegalArgumentException("latencyMode is null");
|
||||
}
|
||||
if (callbacks == null) {
|
||||
throw new IllegalArgumentException("callbacks is null");
|
||||
}
|
||||
this.bdaddr = bdaddr;
|
||||
this.latencyMode = latencyMode;
|
||||
this.autoDisconnectTime = autoDisconnectTime;
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connection channel using the specified parameters.
|
||||
*
|
||||
* Add this button connection channel to a {@link FlicClient} by executing {@link FlicClient#addConnectionChannel(ButtonConnectionChannel)}.
|
||||
*
|
||||
* @param bdaddr
|
||||
* @param callbacks
|
||||
*/
|
||||
public ButtonConnectionChannel(Bdaddr bdaddr, Callbacks callbacks) {
|
||||
this(bdaddr, LatencyMode.NormalLatency, (short)0x1ff, callbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link FlicClient} for this {@link ButtonConnectionChannel}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public FlicClient getFlicClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public Bdaddr getBdaddr() {
|
||||
return bdaddr;
|
||||
}
|
||||
|
||||
public LatencyMode getLatencyMode() {
|
||||
return latencyMode;
|
||||
}
|
||||
|
||||
public short getAutoDisconnectTime() {
|
||||
return autoDisconnectTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies new latency mode parameter.
|
||||
*
|
||||
* @param latencyMode
|
||||
*/
|
||||
public void setLatencyMode(LatencyMode latencyMode) throws IOException {
|
||||
if (latencyMode == null) {
|
||||
throw new IllegalArgumentException("latencyMode is null");
|
||||
}
|
||||
synchronized (lock) {
|
||||
this.latencyMode = latencyMode;
|
||||
|
||||
FlicClient cl = client;
|
||||
if (cl != null) {
|
||||
CmdChangeModeParameters pkt = new CmdChangeModeParameters();
|
||||
pkt.connId = connId;
|
||||
pkt.latencyMode = latencyMode;
|
||||
pkt.autoDisconnectTime = autoDisconnectTime;
|
||||
cl.sendPacket(pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies new auto disconnect time parameter.
|
||||
*
|
||||
* @param autoDisconnectTime Number of seconds (0 - 511) until disconnect if no button event happens. 512 disables this feature.
|
||||
*/
|
||||
public void setAutoDisconnectTime(short autoDisconnectTime) throws IOException {
|
||||
if (latencyMode == null) {
|
||||
throw new IllegalArgumentException("latencyMode is null");
|
||||
}
|
||||
synchronized (lock) {
|
||||
this.autoDisconnectTime = autoDisconnectTime;
|
||||
|
||||
FlicClient cl = client;
|
||||
if (cl != null) {
|
||||
CmdChangeModeParameters pkt = new CmdChangeModeParameters();
|
||||
pkt.connId = connId;
|
||||
pkt.latencyMode = latencyMode;
|
||||
pkt.autoDisconnectTime = autoDisconnectTime;
|
||||
cl.sendPacket(pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User callbacks for incoming events.
|
||||
*
|
||||
* See the protocol specification for further details.
|
||||
*/
|
||||
public abstract static class Callbacks {
|
||||
|
||||
/**
|
||||
* Called when the server has received the create connection channel command.
|
||||
*
|
||||
* If createConnectionChannelError is {@link CreateConnectionChannelError#NoError}, other events will arrive until {@link #onRemoved} is received.
|
||||
* There will be no {@link #onRemoved} if an error occurred.
|
||||
*
|
||||
* @param channel
|
||||
* @param createConnectionChannelError
|
||||
* @param connectionStatus
|
||||
* @throws IOException
|
||||
*/
|
||||
public void onCreateConnectionChannelResponse(ButtonConnectionChannel channel, CreateConnectionChannelError createConnectionChannelError, ConnectionStatus connectionStatus) throws IOException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the connection channel has been removed.
|
||||
*
|
||||
* Check the removedReason to find out why. From this point, the connection channel can be re-added again if you wish.
|
||||
*
|
||||
* @param channel
|
||||
* @param removedReason
|
||||
* @throws IOException
|
||||
*/
|
||||
public void onRemoved(ButtonConnectionChannel channel, RemovedReason removedReason) throws IOException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the connection status changes.
|
||||
*
|
||||
* @param channel
|
||||
* @param connectionStatus
|
||||
* @param disconnectReason Only valid if connectionStatus is {@link ConnectionStatus#Disconnected}
|
||||
* @throws IOException
|
||||
*/
|
||||
public void onConnectionStatusChanged(ButtonConnectionChannel channel, ConnectionStatus connectionStatus, DisconnectReason disconnectReason) throws IOException {
|
||||
}
|
||||
|
||||
public void onButtonUpOrDown(ButtonConnectionChannel channel, ClickType clickType, boolean wasQueued, int timeDiff) throws IOException {
|
||||
}
|
||||
|
||||
public void onButtonClickOrHold(ButtonConnectionChannel channel, ClickType clickType, boolean wasQueued, int timeDiff) throws IOException {
|
||||
}
|
||||
|
||||
public void onButtonSingleOrDoubleClick(ButtonConnectionChannel channel, ClickType clickType, boolean wasQueued, int timeDiff) throws IOException {
|
||||
}
|
||||
|
||||
public void onButtonSingleOrDoubleClickOrHold(ButtonConnectionChannel channel, ClickType clickType, boolean wasQueued, int timeDiff) throws IOException {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Button scanner class.
|
||||
*
|
||||
* Inherit this class and override the {@link #onAdvertisementPacket(Bdaddr, String, int, boolean, boolean)} method.
|
||||
* Then add this button scanner to a {@link FlicClient} using {@link FlicClient#addScanner(ButtonScanner)} to start it.
|
||||
*/
|
||||
public abstract class ButtonScanner {
|
||||
private static AtomicInteger nextId = new AtomicInteger();
|
||||
int scanId = nextId.getAndIncrement();
|
||||
|
||||
/**
|
||||
* This will be called for every received advertisement packet from a Flic button.
|
||||
*
|
||||
* @param bdaddr Bluetooth address
|
||||
* @param name Advertising name
|
||||
* @param rssi RSSI value in dBm
|
||||
* @param isPrivate The button is private and won't accept new connections from non-bonded clients
|
||||
* @param alreadyVerified The server has already verified this button, which means you can connect to it even if it's private
|
||||
* @param alreadyConnectedToThisDevice The button is already connected to this device
|
||||
* @param alreadyConnectedToOtherDevice The button is already connected to another device
|
||||
*/
|
||||
public abstract void onAdvertisementPacket(Bdaddr bdaddr, String name, int rssi, boolean isPrivate, boolean alreadyVerified, boolean alreadyConnectedToThisDevice, boolean alreadyConnectedToOtherDevice) throws IOException;
|
||||
}
|
630
bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/FlicClient.java
vendored
Normal file
630
bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/FlicClient.java
vendored
Normal file
@ -0,0 +1,630 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import io.flic.fliclib.javaclient.enums.CreateConnectionChannelError;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
||||
/**
|
||||
* Implements a FlicClient over a TCP Socket.
|
||||
*
|
||||
* When this class is constructed, a socket connection is established.
|
||||
*
|
||||
* You may then send commands to the server and set timers.
|
||||
*
|
||||
* Once you are ready with the initialization you must call the {@link #handleEvents()} method which is a main loop that never exits, unless the socket is closed.
|
||||
*
|
||||
* For a more detailed description of all commands, events and enums, check the protocol specification.
|
||||
*/
|
||||
public class FlicClient {
|
||||
private Socket socket;
|
||||
private InputStream socketInputStream;
|
||||
private OutputStream socketOutputStream;
|
||||
|
||||
private ConcurrentHashMap<Integer, ButtonScanner> scanners = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<Integer, ButtonConnectionChannel> connectionChannels = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<Integer, ScanWizard> scanWizards = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<Integer, BatteryStatusListener> batteryStatusListeners = new ConcurrentHashMap<>();
|
||||
private ConcurrentLinkedQueue<GetInfoResponseCallback> getInfoResponseCallbackQueue = new ConcurrentLinkedQueue<>();
|
||||
private ArrayDeque<GetButtonInfoResponseCallback> getButtonInfoResponseCallbackQueue = new ArrayDeque<>();
|
||||
|
||||
private volatile GeneralCallbacks generalCallbacks = new GeneralCallbacks();
|
||||
|
||||
private ConcurrentSkipListMap<Long, TimerTask> timers = new ConcurrentSkipListMap<>();
|
||||
|
||||
private Thread handleEventsThread;
|
||||
|
||||
/**
|
||||
* Create a FlicClient and connect to the specified hostName and TCP port
|
||||
*
|
||||
* @param hostName
|
||||
* @param port
|
||||
* @throws UnknownHostException
|
||||
* @throws IOException
|
||||
*/
|
||||
public FlicClient(String hostName, int port) throws UnknownHostException, IOException {
|
||||
socket = new Socket(hostName, port);
|
||||
socket.setKeepAlive(true);
|
||||
socketInputStream = socket.getInputStream();
|
||||
socketOutputStream = socket.getOutputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a FlicClient and connect to the specified hostName using the default TCP port
|
||||
*
|
||||
* @param hostName
|
||||
* @throws UnknownHostException
|
||||
* @throws IOException
|
||||
*/
|
||||
public FlicClient(String hostName) throws UnknownHostException, IOException {
|
||||
this(hostName, 5551);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the socket.
|
||||
*
|
||||
* From this point any use of this FlicClient is illegal.
|
||||
* The {@link #handleEvents()} will return as soon as the closing is done.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
runOnHandleEventsThread(new TimerTask() {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set general callbacks to be called upon receiving some specific events.
|
||||
*
|
||||
* @param callbacks
|
||||
*/
|
||||
public void setGeneralCallbacks(GeneralCallbacks callbacks) {
|
||||
if (callbacks == null) {
|
||||
callbacks = new GeneralCallbacks();
|
||||
}
|
||||
generalCallbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info about the current state of the server.
|
||||
*
|
||||
* The server will send back its information directly and the callback will be called once the response arrives.
|
||||
*
|
||||
* @param callback
|
||||
* @throws IOException
|
||||
*/
|
||||
public void getInfo(GetInfoResponseCallback callback) throws IOException {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback is null");
|
||||
}
|
||||
getInfoResponseCallbackQueue.add(callback);
|
||||
|
||||
CmdGetInfo pkt = new CmdGetInfo();
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button info for a verified button.
|
||||
*
|
||||
* The server will send back its information directly and the callback will be called once the response arrives.
|
||||
* Responses will arrive in the same order as requested.
|
||||
*
|
||||
* If the button isn't verified, the data sent to callback will be null.
|
||||
*
|
||||
* @param bdaddr The bluetooth address.
|
||||
* @param callback Callback for the response.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void getButtonInfo(final Bdaddr bdaddr, final GetButtonInfoResponseCallback callback) throws IOException {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback is null");
|
||||
}
|
||||
// Run on events thread to ensure ordering if multiple requests are issued at the same time
|
||||
runOnHandleEventsThread(new TimerTask() {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
getButtonInfoResponseCallbackQueue.add(callback);
|
||||
|
||||
CmdGetButtonInfo pkt = new CmdGetButtonInfo();
|
||||
pkt.bdaddr = bdaddr;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a scanner.
|
||||
*
|
||||
* The scan will start directly once the scanner is added.
|
||||
*
|
||||
* @param buttonScanner
|
||||
* @throws IOException
|
||||
*/
|
||||
public void addScanner(ButtonScanner buttonScanner) throws IOException {
|
||||
if (buttonScanner == null) {
|
||||
throw new IllegalArgumentException("buttonScanner is null");
|
||||
}
|
||||
if (scanners.putIfAbsent(buttonScanner.scanId, buttonScanner) != null) {
|
||||
throw new IllegalArgumentException("Button scanner already added");
|
||||
}
|
||||
|
||||
CmdCreateScanner pkt = new CmdCreateScanner();
|
||||
pkt.scanId = buttonScanner.scanId;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a scanner.
|
||||
*
|
||||
* @param buttonScanner The same scanner that was used in {@link #addScanner(ButtonScanner)}
|
||||
* @throws IOException
|
||||
*/
|
||||
public void removeScanner(ButtonScanner buttonScanner) throws IOException {
|
||||
if (buttonScanner == null) {
|
||||
throw new IllegalArgumentException("buttonScanner is null");
|
||||
}
|
||||
if (scanners.remove(buttonScanner.scanId) == null) {
|
||||
throw new IllegalArgumentException("Button scanner was never added");
|
||||
}
|
||||
|
||||
CmdRemoveScanner pkt = new CmdRemoveScanner();
|
||||
pkt.scanId = buttonScanner.scanId;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a scan wizard.
|
||||
*
|
||||
* The scan wizard will start directly once the scan wizard is added.
|
||||
*
|
||||
* @param scanWizard
|
||||
* @throws IOException
|
||||
*/
|
||||
public void addScanWizard(ScanWizard scanWizard) throws IOException {
|
||||
if (scanWizard == null) {
|
||||
throw new IllegalArgumentException("scanWizard is null");
|
||||
}
|
||||
if (scanWizards.putIfAbsent(scanWizard.scanWizardId, scanWizard) != null) {
|
||||
throw new IllegalArgumentException("Scan wizard already added");
|
||||
}
|
||||
|
||||
CmdCreateScanWizard pkt = new CmdCreateScanWizard();
|
||||
pkt.scanWizardId = scanWizard.scanWizardId;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a scan wizard.
|
||||
*
|
||||
* This will cancel an ongoing scan wizard.
|
||||
*
|
||||
* If cancelled due to this request, the result of the scan wizard will be WizardCancelledByUser.
|
||||
*
|
||||
* @param scanWizard The same scan wizard that was used in {@link #addScanWizard(ScanWizard)}
|
||||
* @throws IOException
|
||||
*/
|
||||
public void cancelScanWizard(ScanWizard scanWizard) throws IOException {
|
||||
if (scanWizard == null) {
|
||||
throw new IllegalArgumentException("scanWizard is null");
|
||||
}
|
||||
|
||||
CmdCancelScanWizard pkt = new CmdCancelScanWizard();
|
||||
pkt.scanWizardId = scanWizard.scanWizardId;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a connection channel to a specific Flic button.
|
||||
*
|
||||
* This will start listening for a specific Flic button's connection and button events.
|
||||
* Make sure the Flic is either in public mode (by holding it down for 7 seconds) or already verified before calling this method.
|
||||
*
|
||||
* The {@link ButtonConnectionChannel.Callbacks#onCreateConnectionChannelResponse}
|
||||
* method will be called after this command has been received by the server.
|
||||
*
|
||||
* You may have as many connection channels as you wish for a specific Flic Button.
|
||||
*
|
||||
* @param channel
|
||||
* @throws IOException
|
||||
*/
|
||||
public void addConnectionChannel(ButtonConnectionChannel channel) throws IOException {
|
||||
if (channel == null) {
|
||||
throw new IllegalArgumentException("channel is null");
|
||||
}
|
||||
if (connectionChannels.putIfAbsent(channel.connId, channel) != null) {
|
||||
throw new IllegalArgumentException("Connection channel already added");
|
||||
}
|
||||
|
||||
synchronized (channel.lock) {
|
||||
channel.client = this;
|
||||
|
||||
CmdCreateConnectionChannel pkt = new CmdCreateConnectionChannel();
|
||||
pkt.connId = channel.connId;
|
||||
pkt.bdaddr = channel.getBdaddr();
|
||||
pkt.latencyMode = channel.getLatencyMode();
|
||||
pkt.autoDisconnectTime = channel.getAutoDisconnectTime();
|
||||
sendPacket(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a connection channel.
|
||||
*
|
||||
* This will stop listening for new events for a specific connection channel that has previously been added.
|
||||
* Note: The effect of this command will take place at the time the {@link ButtonConnectionChannel.Callbacks#onRemoved} event arrives.
|
||||
*
|
||||
* @param channel
|
||||
* @throws IOException
|
||||
*/
|
||||
public void removeConnectionChannel(ButtonConnectionChannel channel) throws IOException {
|
||||
if (channel == null) {
|
||||
throw new IllegalArgumentException("channel is null");
|
||||
}
|
||||
|
||||
CmdRemoveConnectionChannel pkt = new CmdRemoveConnectionChannel();
|
||||
pkt.connId = channel.connId;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force disconnection or cancel pending connection of a specific Flic button.
|
||||
*
|
||||
* This removes all connection channels for all clients connected to the server for this specific Flic button.
|
||||
*
|
||||
* @param bdaddr
|
||||
* @throws IOException
|
||||
*/
|
||||
public void forceDisconnect(Bdaddr bdaddr) throws IOException {
|
||||
if (bdaddr == null) {
|
||||
throw new IllegalArgumentException("bdaddr is null");
|
||||
}
|
||||
|
||||
CmdForceDisconnect pkt = new CmdForceDisconnect();
|
||||
pkt.bdaddr = bdaddr;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a button.
|
||||
*
|
||||
* @param bdaddr
|
||||
* @throws IOException
|
||||
*/
|
||||
public void deleteButton(Bdaddr bdaddr) throws IOException {
|
||||
if (bdaddr == null) {
|
||||
throw new IllegalArgumentException("bdaddr is null");
|
||||
}
|
||||
|
||||
CmdDeleteButton pkt = new CmdDeleteButton();
|
||||
pkt.bdaddr = bdaddr;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a battery status listener.
|
||||
*
|
||||
* @param listener
|
||||
* @throws IOException
|
||||
*/
|
||||
public void addBatteryStatusListener(BatteryStatusListener listener) throws IOException {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener is null");
|
||||
}
|
||||
if (batteryStatusListeners.putIfAbsent(listener.listenerId, listener) != null) {
|
||||
throw new IllegalArgumentException("Battery status listener already added");
|
||||
}
|
||||
|
||||
CmdCreateBatteryStatusListener pkt = new CmdCreateBatteryStatusListener();
|
||||
pkt.listenerId = listener.listenerId;
|
||||
pkt.bdaddr = listener.getBdaddr();
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a battery status listener
|
||||
*
|
||||
* @param listener
|
||||
* @throws IOException
|
||||
*/
|
||||
public void removeBatteryStatusListener(BatteryStatusListener listener) throws IOException {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("buttonScanner is null");
|
||||
}
|
||||
if (batteryStatusListeners.remove(listener.listenerId) == null) {
|
||||
throw new IllegalArgumentException("Battery status listener was never added");
|
||||
}
|
||||
|
||||
CmdRemoveBatteryStatusListener pkt = new CmdRemoveBatteryStatusListener();
|
||||
pkt.listenerId = listener.listenerId;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
void sendPacket(CommandPacket packet) throws IOException {
|
||||
byte[] bytes = packet.construct();
|
||||
synchronized (socketOutputStream) {
|
||||
socketOutputStream.write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a timer.
|
||||
*
|
||||
* This timer task will run after the specified timeoutMillis on the thread that handles the events.
|
||||
*
|
||||
* @param timeoutMillis
|
||||
* @param timerTask
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setTimer(int timeoutMillis, TimerTask timerTask) throws IOException {
|
||||
long pointInTime = System.nanoTime() + timeoutMillis * 1000000L;
|
||||
while (timers.putIfAbsent(pointInTime, timerTask) != null) {
|
||||
pointInTime++;
|
||||
}
|
||||
if (handleEventsThread != Thread.currentThread()) {
|
||||
CmdPing pkt = new CmdPing();
|
||||
pkt.pingId = 0;
|
||||
sendPacket(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task on the thread that handles the events.
|
||||
*
|
||||
* @param task
|
||||
* @throws IOException
|
||||
*/
|
||||
public void runOnHandleEventsThread(TimerTask task) throws IOException {
|
||||
if (handleEventsThread == Thread.currentThread()) {
|
||||
task.run();
|
||||
} else {
|
||||
setTimer(0, task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the main loop for this client.
|
||||
*
|
||||
* This method will not return until the socket has been closed.
|
||||
* Once it has returned, any use of this FlicClient is illegal.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleEvents() throws IOException {
|
||||
handleEventsThread = Thread.currentThread();
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
Map.Entry<Long, TimerTask> firstTimer = timers.firstEntry();
|
||||
long timeout = 0;
|
||||
if (firstTimer != null) {
|
||||
timeout = firstTimer.getKey() - System.nanoTime();
|
||||
if (timeout <= 0) {
|
||||
timers.remove(firstTimer.getKey(), firstTimer.getValue());
|
||||
firstTimer.getValue().run();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (socket.isClosed()) {
|
||||
break;
|
||||
}
|
||||
|
||||
int len0;
|
||||
socket.setSoTimeout((int)(timeout / 1000000));
|
||||
try {
|
||||
len0 = socketInputStream.read();
|
||||
} catch (SocketTimeoutException e) {
|
||||
continue;
|
||||
}
|
||||
int len1 = socketInputStream.read();
|
||||
int len = len0 | (len1 << 8);
|
||||
if ((len >> 16) == -1) {
|
||||
break;
|
||||
}
|
||||
if (len == 0) {
|
||||
continue;
|
||||
}
|
||||
byte[] pkt = new byte[len];
|
||||
|
||||
int pos = 0;
|
||||
while (pos < len) {
|
||||
int nbytes = socketInputStream.read(pkt, pos, len - pos);
|
||||
if (nbytes == -1) {
|
||||
break;
|
||||
}
|
||||
pos += nbytes;
|
||||
}
|
||||
if (len == 1) {
|
||||
continue;
|
||||
}
|
||||
dispatchPacket(pkt);
|
||||
}
|
||||
socket.close();
|
||||
}
|
||||
|
||||
private void dispatchPacket(byte[] packet) throws IOException {
|
||||
int opcode = packet[0];
|
||||
switch (opcode) {
|
||||
case EventPacket.EVT_ADVERTISEMENT_PACKET_OPCODE: {
|
||||
EvtAdvertisementPacket pkt = new EvtAdvertisementPacket();
|
||||
pkt.parse(packet);
|
||||
ButtonScanner scanner = scanners.get(pkt.scanId);
|
||||
if (scanner != null) {
|
||||
scanner.onAdvertisementPacket(pkt.addr, pkt.name, pkt.rssi, pkt.isPrivate, pkt.alreadyVerified, pkt.alreadyConnectedToThisDevice, pkt.alreadyConnectedToOtherDevice);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_CREATE_CONNECTION_CHANNEL_RESPONSE_OPCODE: {
|
||||
EvtCreateConnectionChannelResponse pkt = new EvtCreateConnectionChannelResponse();
|
||||
pkt.parse(packet);
|
||||
ButtonConnectionChannel channel = connectionChannels.get(pkt.connId);
|
||||
if (channel != null) {
|
||||
if (pkt.connectionChannelError != CreateConnectionChannelError.NoError) {
|
||||
connectionChannels.remove(channel.connId);
|
||||
}
|
||||
channel.callbacks.onCreateConnectionChannelResponse(channel, pkt.connectionChannelError, pkt.connectionStatus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_CONNECTION_STATUS_CHANGED_OPCODE: {
|
||||
EvtConnectionStatusChanged pkt = new EvtConnectionStatusChanged();
|
||||
pkt.parse(packet);
|
||||
ButtonConnectionChannel channel = connectionChannels.get(pkt.connId);
|
||||
if (channel != null) {
|
||||
channel.callbacks.onConnectionStatusChanged(channel, pkt.connectionStatus, pkt.disconnectReason);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_CONNECTION_CHANNEL_REMOVED_OPCODE: {
|
||||
EvtConnectionChannelRemoved pkt = new EvtConnectionChannelRemoved();
|
||||
pkt.parse(packet);
|
||||
ButtonConnectionChannel channel = connectionChannels.get(pkt.connId);
|
||||
if (channel != null) {
|
||||
connectionChannels.remove(channel.connId);
|
||||
channel.callbacks.onRemoved(channel, pkt.removedReason);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_BUTTON_UP_OR_DOWN_OPCODE:
|
||||
case EventPacket.EVT_BUTTON_CLICK_OR_HOLD_OPCODE:
|
||||
case EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE:
|
||||
case EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE: {
|
||||
EvtButtonEvent pkt = new EvtButtonEvent();
|
||||
pkt.parse(packet);
|
||||
ButtonConnectionChannel channel = connectionChannels.get(pkt.connId);
|
||||
if (channel != null) {
|
||||
if (opcode == EventPacket.EVT_BUTTON_UP_OR_DOWN_OPCODE) {
|
||||
channel.callbacks.onButtonUpOrDown(channel, pkt.clickType, pkt.wasQueued, pkt.timeDiff);
|
||||
} else if (opcode == EventPacket.EVT_BUTTON_CLICK_OR_HOLD_OPCODE) {
|
||||
channel.callbacks.onButtonClickOrHold(channel, pkt.clickType, pkt.wasQueued, pkt.timeDiff);
|
||||
} else if (opcode == EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE) {
|
||||
channel.callbacks.onButtonSingleOrDoubleClick(channel, pkt.clickType, pkt.wasQueued, pkt.timeDiff);
|
||||
} else if (opcode == EventPacket.EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE) {
|
||||
channel.callbacks.onButtonSingleOrDoubleClickOrHold(channel, pkt.clickType, pkt.wasQueued, pkt.timeDiff);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_NEW_VERIFIED_BUTTON_OPCODE: {
|
||||
EvtNewVerifiedButton pkt = new EvtNewVerifiedButton();
|
||||
pkt.parse(packet);
|
||||
GeneralCallbacks gc = generalCallbacks;
|
||||
if (gc != null) {
|
||||
gc.onNewVerifiedButton(pkt.bdaddr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_GET_INFO_RESPONSE_OPCODE: {
|
||||
EvtGetInfoResponse pkt = new EvtGetInfoResponse();
|
||||
pkt.parse(packet);
|
||||
getInfoResponseCallbackQueue.remove().onGetInfoResponse(pkt.bluetoothControllerState, pkt.myBdAddr, pkt.myBdAddrType, pkt.maxPendingConnections, pkt.maxConcurrentlyConnectedButtons, pkt.currentPendingConnections, pkt.currentlyNoSpaceForNewConnections, pkt.bdAddrOfVerifiedButtons);
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_NO_SPACE_FOR_NEW_CONNECTION_OPCODE: {
|
||||
EvtNoSpaceForNewConnection pkt = new EvtNoSpaceForNewConnection();
|
||||
pkt.parse(packet);
|
||||
GeneralCallbacks gc = generalCallbacks;
|
||||
if (gc != null) {
|
||||
gc.onNoSpaceForNewConnection(pkt.maxConcurrentlyConnectedButtons);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_GOT_SPACE_FOR_NEW_CONNECTION_OPCODE: {
|
||||
EvtGotSpaceForNewConnection pkt = new EvtGotSpaceForNewConnection();
|
||||
pkt.parse(packet);
|
||||
GeneralCallbacks gc = generalCallbacks;
|
||||
if (gc != null) {
|
||||
gc.onGotSpaceForNewConnection(pkt.maxConcurrentlyConnectedButtons);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_BLUETOOTH_CONTROLLER_STATE_CHANGE_OPCODE: {
|
||||
EvtBluetoothControllerStateChange pkt = new EvtBluetoothControllerStateChange();
|
||||
pkt.parse(packet);
|
||||
GeneralCallbacks gc = generalCallbacks;
|
||||
if (gc != null) {
|
||||
gc.onBluetoothControllerStateChange(pkt.state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_GET_BUTTON_INFO_RESPONSE_OPCODE: {
|
||||
EvtGetButtonInfoResponse pkt = new EvtGetButtonInfoResponse();
|
||||
pkt.parse(packet);
|
||||
getButtonInfoResponseCallbackQueue.remove().onGetButtonInfoResponse(pkt.bdaddr, pkt.uuid, pkt.color, pkt.serialNumber);
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_SCAN_WIZARD_FOUND_PRIVATE_BUTTON_OPCODE: {
|
||||
EvtScanWizardFoundPrivateButton pkt = new EvtScanWizardFoundPrivateButton();
|
||||
pkt.parse(packet);
|
||||
ScanWizard wizard = scanWizards.get(pkt.scanWizardId);
|
||||
if (wizard != null) {
|
||||
wizard.onFoundPrivateButton();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_SCAN_WIZARD_FOUND_PUBLIC_BUTTON_OPCODE: {
|
||||
EvtScanWizardFoundPublicButton pkt = new EvtScanWizardFoundPublicButton();
|
||||
pkt.parse(packet);
|
||||
ScanWizard wizard = scanWizards.get(pkt.scanWizardId);
|
||||
if (wizard != null) {
|
||||
wizard.bdaddr = pkt.addr;
|
||||
wizard.name = pkt.name;
|
||||
wizard.onFoundPublicButton(wizard.bdaddr, wizard.name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_SCAN_WIZARD_BUTTON_CONNECTED_OPCODE: {
|
||||
EvtScanWizardButtonConnected pkt = new EvtScanWizardButtonConnected();
|
||||
pkt.parse(packet);
|
||||
ScanWizard wizard = scanWizards.get(pkt.scanWizardId);
|
||||
if (wizard != null) {
|
||||
wizard.onButtonConnected(wizard.bdaddr, wizard.name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_SCAN_WIZARD_COMPLETED_OPCODE: {
|
||||
EvtScanWizardCompleted pkt = new EvtScanWizardCompleted();
|
||||
pkt.parse(packet);
|
||||
ScanWizard wizard = scanWizards.get(pkt.scanWizardId);
|
||||
scanWizards.remove(pkt.scanWizardId);
|
||||
if (wizard != null) {
|
||||
Bdaddr bdaddr = wizard.bdaddr;
|
||||
String name = wizard.name;
|
||||
wizard.bdaddr = null;
|
||||
wizard.name = null;
|
||||
wizard.onCompleted(pkt.result, bdaddr, name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_BUTTON_DELETED_OPCODE: {
|
||||
EvtButtonDeleted pkt = new EvtButtonDeleted();
|
||||
pkt.parse(packet);
|
||||
GeneralCallbacks gc = generalCallbacks;
|
||||
if (gc != null) {
|
||||
gc.onButtonDeleted(pkt.bdaddr, pkt.deletedByThisClient);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EventPacket.EVT_BATTERY_STATUS_OPCODE: {
|
||||
EvtBatteryStatus pkt = new EvtBatteryStatus();
|
||||
pkt.parse(packet);
|
||||
BatteryStatusListener listener = batteryStatusListeners.get(pkt.listenerId);
|
||||
if (listener != null) {
|
||||
listener.callbacks.onBatteryStatus(listener.getBdaddr(), pkt.batteryPercentage, pkt.timestamp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import io.flic.fliclib.javaclient.enums.BluetoothControllerState;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* GeneralCallbacks.
|
||||
*
|
||||
* See the protocol specification for further details.
|
||||
*/
|
||||
public class GeneralCallbacks {
|
||||
public void onNewVerifiedButton(Bdaddr bdaddr) throws IOException {
|
||||
|
||||
}
|
||||
public void onNoSpaceForNewConnection(int maxConcurrentlyConnectedButtons) throws IOException {
|
||||
|
||||
}
|
||||
public void onGotSpaceForNewConnection(int maxConcurrentlyConnectedButtons) throws IOException {
|
||||
|
||||
}
|
||||
public void onBluetoothControllerStateChange(BluetoothControllerState state) throws IOException {
|
||||
|
||||
}
|
||||
public void onButtonDeleted(Bdaddr bdaddr, boolean deletedByThisClient) throws IOException {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
/**
|
||||
* GetButtonInfoResponseCallback.
|
||||
*
|
||||
* Used in {@link FlicClient#getButtonInfo(Bdaddr, GetButtonInfoResponseCallback)}.
|
||||
*/
|
||||
public abstract class GetButtonInfoResponseCallback {
|
||||
/**
|
||||
* Called upon response.
|
||||
*
|
||||
* @param bdaddr Bluetooth address
|
||||
* @param uuid Uuid of button, might be null if unknown
|
||||
* @param color Color of button, might be null if unknown
|
||||
* @param serialNumber Serial number of the button, will be null if the button is not found
|
||||
*/
|
||||
public abstract void onGetButtonInfoResponse(Bdaddr bdaddr, String uuid, String color, String serialNumber);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import io.flic.fliclib.javaclient.enums.BdAddrType;
|
||||
import io.flic.fliclib.javaclient.enums.BluetoothControllerState;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* GetInfoResponseCallback.
|
||||
*
|
||||
* Used in {@link FlicClient#getInfo(GetInfoResponseCallback)}.
|
||||
*/
|
||||
public abstract class GetInfoResponseCallback {
|
||||
public abstract void onGetInfoResponse(BluetoothControllerState bluetoothControllerState, Bdaddr myBdAddr,
|
||||
BdAddrType myBdAddrType, int maxPendingConnections,
|
||||
int maxConcurrentlyConnectedButtons, int currentPendingConnections,
|
||||
boolean currentlyNoSpaceForNewConnection,
|
||||
Bdaddr[] verifiedButtons) throws IOException;
|
||||
}
|
455
bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Packets.java
vendored
Normal file
455
bundles/org.openhab.binding.flicbutton/src/3rdparty/java/io/flic/fliclib/javaclient/Packets.java
vendored
Normal file
@ -0,0 +1,455 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import io.flic.fliclib.javaclient.enums.*;
|
||||
|
||||
/**
|
||||
* Flic Protocol Packets
|
||||
*/
|
||||
|
||||
abstract class CommandPacket {
|
||||
protected int opcode;
|
||||
|
||||
public final byte[] construct() {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
try {
|
||||
write(stream);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
byte[] res = new byte[3 + stream.size()];
|
||||
res[0] = (byte)(1 + stream.size());
|
||||
res[1] = (byte)((1 + stream.size()) >> 8);
|
||||
res[2] = (byte)opcode;
|
||||
System.arraycopy(stream.toByteArray(), 0, res, 3, stream.size());
|
||||
return res;
|
||||
}
|
||||
|
||||
abstract protected void write(OutputStream stream) throws IOException;
|
||||
}
|
||||
|
||||
class CmdGetInfo extends CommandPacket {
|
||||
@Override
|
||||
protected void write(OutputStream stream) {
|
||||
opcode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class CmdCreateScanner extends CommandPacket {
|
||||
public int scanId;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 1;
|
||||
StreamUtils.writeInt32(stream, scanId);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdRemoveScanner extends CommandPacket {
|
||||
public int scanId;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 2;
|
||||
StreamUtils.writeInt32(stream, scanId);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdCreateConnectionChannel extends CommandPacket {
|
||||
public int connId;
|
||||
public Bdaddr bdaddr;
|
||||
public LatencyMode latencyMode;
|
||||
public short autoDisconnectTime;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 3;
|
||||
StreamUtils.writeInt32(stream, connId);
|
||||
StreamUtils.writeBdaddr(stream, bdaddr);
|
||||
StreamUtils.writeEnum(stream, latencyMode);
|
||||
StreamUtils.writeInt16(stream, autoDisconnectTime);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdRemoveConnectionChannel extends CommandPacket {
|
||||
public int connId;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 4;
|
||||
StreamUtils.writeInt32(stream, connId);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdForceDisconnect extends CommandPacket {
|
||||
public Bdaddr bdaddr;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 5;
|
||||
StreamUtils.writeBdaddr(stream, bdaddr);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdChangeModeParameters extends CommandPacket {
|
||||
public int connId;
|
||||
public LatencyMode latencyMode;
|
||||
public short autoDisconnectTime;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 6;
|
||||
StreamUtils.writeInt32(stream, connId);
|
||||
StreamUtils.writeEnum(stream, latencyMode);
|
||||
StreamUtils.writeInt16(stream, autoDisconnectTime);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdPing extends CommandPacket {
|
||||
public int pingId;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 7;
|
||||
StreamUtils.writeInt32(stream, pingId);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdGetButtonInfo extends CommandPacket {
|
||||
public Bdaddr bdaddr;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 8;
|
||||
StreamUtils.writeBdaddr(stream, bdaddr);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdCreateScanWizard extends CommandPacket {
|
||||
public int scanWizardId;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 9;
|
||||
StreamUtils.writeInt32(stream, scanWizardId);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdCancelScanWizard extends CommandPacket {
|
||||
public int scanWizardId;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 10;
|
||||
StreamUtils.writeInt32(stream, scanWizardId);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdDeleteButton extends CommandPacket {
|
||||
public Bdaddr bdaddr;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 11;
|
||||
StreamUtils.writeBdaddr(stream, bdaddr);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdCreateBatteryStatusListener extends CommandPacket {
|
||||
public int listenerId;
|
||||
public Bdaddr bdaddr;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 12;
|
||||
StreamUtils.writeInt32(stream, listenerId);
|
||||
StreamUtils.writeBdaddr(stream, bdaddr);
|
||||
}
|
||||
}
|
||||
|
||||
class CmdRemoveBatteryStatusListener extends CommandPacket {
|
||||
public int listenerId;
|
||||
|
||||
@Override
|
||||
protected void write(OutputStream stream) throws IOException {
|
||||
opcode = 13;
|
||||
StreamUtils.writeInt32(stream, listenerId);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class EventPacket {
|
||||
public static final int EVT_ADVERTISEMENT_PACKET_OPCODE = 0;
|
||||
public static final int EVT_CREATE_CONNECTION_CHANNEL_RESPONSE_OPCODE = 1;
|
||||
public static final int EVT_CONNECTION_STATUS_CHANGED_OPCODE = 2;
|
||||
public static final int EVT_CONNECTION_CHANNEL_REMOVED_OPCODE = 3;
|
||||
public static final int EVT_BUTTON_UP_OR_DOWN_OPCODE = 4;
|
||||
public static final int EVT_BUTTON_CLICK_OR_HOLD_OPCODE = 5;
|
||||
public static final int EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OPCODE = 6;
|
||||
public static final int EVT_BUTTON_SINGLE_OR_DOUBLE_CLICK_OR_HOLD_OPCODE = 7;
|
||||
public static final int EVT_NEW_VERIFIED_BUTTON_OPCODE = 8;
|
||||
public static final int EVT_GET_INFO_RESPONSE_OPCODE = 9;
|
||||
public static final int EVT_NO_SPACE_FOR_NEW_CONNECTION_OPCODE = 10;
|
||||
public static final int EVT_GOT_SPACE_FOR_NEW_CONNECTION_OPCODE = 11;
|
||||
public static final int EVT_BLUETOOTH_CONTROLLER_STATE_CHANGE_OPCODE = 12;
|
||||
public static final int EVT_PING_RESPONSE_OPCODE = 13;
|
||||
public static final int EVT_GET_BUTTON_INFO_RESPONSE_OPCODE = 14;
|
||||
public static final int EVT_SCAN_WIZARD_FOUND_PRIVATE_BUTTON_OPCODE = 15;
|
||||
public static final int EVT_SCAN_WIZARD_FOUND_PUBLIC_BUTTON_OPCODE = 16;
|
||||
public static final int EVT_SCAN_WIZARD_BUTTON_CONNECTED_OPCODE = 17;
|
||||
public static final int EVT_SCAN_WIZARD_COMPLETED_OPCODE = 18;
|
||||
public static final int EVT_BUTTON_DELETED_OPCODE = 19;
|
||||
public static final int EVT_BATTERY_STATUS_OPCODE = 20;
|
||||
|
||||
public void parse(byte[] arr) {
|
||||
InputStream stream = new ByteArrayInputStream(arr);
|
||||
try {
|
||||
stream.skip(1);
|
||||
parseInternal(stream);
|
||||
} catch(IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected void parseInternal(InputStream stream) throws IOException;
|
||||
}
|
||||
|
||||
class EvtAdvertisementPacket extends EventPacket {
|
||||
public int scanId;
|
||||
public Bdaddr addr;
|
||||
public String name;
|
||||
public int rssi;
|
||||
public boolean isPrivate;
|
||||
public boolean alreadyVerified;
|
||||
public boolean alreadyConnectedToThisDevice;
|
||||
public boolean alreadyConnectedToOtherDevice;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
scanId = StreamUtils.getInt32(stream);
|
||||
addr = StreamUtils.getBdaddr(stream);
|
||||
name = StreamUtils.getString(stream, 16);
|
||||
rssi = StreamUtils.getInt8(stream);
|
||||
isPrivate = StreamUtils.getBoolean(stream);
|
||||
alreadyVerified = StreamUtils.getBoolean(stream);
|
||||
alreadyConnectedToThisDevice = StreamUtils.getBoolean(stream);
|
||||
alreadyConnectedToOtherDevice = StreamUtils.getBoolean(stream);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtCreateConnectionChannelResponse extends EventPacket {
|
||||
public int connId;
|
||||
public CreateConnectionChannelError connectionChannelError;
|
||||
public ConnectionStatus connectionStatus;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
connId = StreamUtils.getInt32(stream);
|
||||
connectionChannelError = CreateConnectionChannelError.values()[StreamUtils.getUInt8(stream)];
|
||||
connectionStatus = ConnectionStatus.values()[StreamUtils.getUInt8(stream)];
|
||||
}
|
||||
}
|
||||
|
||||
class EvtConnectionStatusChanged extends EventPacket {
|
||||
public int connId;
|
||||
public ConnectionStatus connectionStatus;
|
||||
public DisconnectReason disconnectReason;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
connId = StreamUtils.getInt32(stream);
|
||||
connectionStatus = ConnectionStatus.values()[StreamUtils.getUInt8(stream)];
|
||||
disconnectReason = DisconnectReason.values()[StreamUtils.getUInt8(stream)];
|
||||
}
|
||||
}
|
||||
|
||||
class EvtConnectionChannelRemoved extends EventPacket {
|
||||
public int connId;
|
||||
public RemovedReason removedReason;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
connId = StreamUtils.getInt32(stream);
|
||||
removedReason = RemovedReason.values()[StreamUtils.getUInt8(stream)];
|
||||
}
|
||||
}
|
||||
|
||||
class EvtButtonEvent extends EventPacket {
|
||||
public int connId;
|
||||
public ClickType clickType;
|
||||
public boolean wasQueued;
|
||||
public int timeDiff;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
connId = StreamUtils.getInt32(stream);
|
||||
clickType = ClickType.values()[StreamUtils.getUInt8(stream)];
|
||||
wasQueued = StreamUtils.getBoolean(stream);
|
||||
timeDiff = StreamUtils.getInt32(stream);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtNewVerifiedButton extends EventPacket {
|
||||
public Bdaddr bdaddr;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
bdaddr = StreamUtils.getBdaddr(stream);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtGetInfoResponse extends EventPacket {
|
||||
public BluetoothControllerState bluetoothControllerState;
|
||||
public Bdaddr myBdAddr;
|
||||
public BdAddrType myBdAddrType;
|
||||
public int maxPendingConnections;
|
||||
public int maxConcurrentlyConnectedButtons;
|
||||
public int currentPendingConnections;
|
||||
public boolean currentlyNoSpaceForNewConnections;
|
||||
public Bdaddr[] bdAddrOfVerifiedButtons;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
bluetoothControllerState = BluetoothControllerState.values()[StreamUtils.getUInt8(stream)];
|
||||
myBdAddr = StreamUtils.getBdaddr(stream);
|
||||
myBdAddrType = BdAddrType.values()[StreamUtils.getUInt8(stream)];
|
||||
maxPendingConnections = StreamUtils.getUInt8(stream);
|
||||
maxConcurrentlyConnectedButtons = StreamUtils.getInt16(stream);
|
||||
currentPendingConnections = StreamUtils.getUInt8(stream);
|
||||
currentlyNoSpaceForNewConnections = StreamUtils.getBoolean(stream);
|
||||
int nbVerifiedButtons = StreamUtils.getUInt16(stream);
|
||||
bdAddrOfVerifiedButtons = new Bdaddr[nbVerifiedButtons];
|
||||
for (int i = 0; i < nbVerifiedButtons; i++) {
|
||||
bdAddrOfVerifiedButtons[i] = StreamUtils.getBdaddr(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EvtNoSpaceForNewConnection extends EventPacket {
|
||||
public int maxConcurrentlyConnectedButtons;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
maxConcurrentlyConnectedButtons = StreamUtils.getUInt8(stream);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtGotSpaceForNewConnection extends EventPacket {
|
||||
public int maxConcurrentlyConnectedButtons;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
maxConcurrentlyConnectedButtons = StreamUtils.getUInt8(stream);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtBluetoothControllerStateChange extends EventPacket {
|
||||
public BluetoothControllerState state;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
state = BluetoothControllerState.values()[StreamUtils.getUInt8(stream)];
|
||||
}
|
||||
}
|
||||
|
||||
class EvtGetButtonInfoResponse extends EventPacket {
|
||||
public Bdaddr bdaddr;
|
||||
public String uuid;
|
||||
public String color;
|
||||
public String serialNumber;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
bdaddr = StreamUtils.getBdaddr(stream);
|
||||
byte[] uuidBytes = StreamUtils.getByteArr(stream, 16);
|
||||
StringBuilder sb = new StringBuilder(32);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
sb.append(String.format("%02x", uuidBytes[i]));
|
||||
}
|
||||
uuid = sb.toString();
|
||||
if (uuid.equals("00000000000000000000000000000000")) {
|
||||
uuid = null;
|
||||
}
|
||||
color = StreamUtils.getString(stream, 16);
|
||||
if (color.isEmpty()) {
|
||||
color = null;
|
||||
}
|
||||
serialNumber = StreamUtils.getString(stream, 16);
|
||||
if (serialNumber.isEmpty()) {
|
||||
serialNumber = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EvtScanWizardFoundPrivateButton extends EventPacket {
|
||||
public int scanWizardId;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
scanWizardId = StreamUtils.getInt32(stream);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtScanWizardFoundPublicButton extends EventPacket {
|
||||
public int scanWizardId;
|
||||
public Bdaddr addr;
|
||||
public String name;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
scanWizardId = StreamUtils.getInt32(stream);
|
||||
addr = StreamUtils.getBdaddr(stream);
|
||||
int nameLen = StreamUtils.getUInt8(stream);
|
||||
byte[] bytes = new byte[nameLen];
|
||||
for (int i = 0; i < nameLen; i++) {
|
||||
bytes[i] = (byte)stream.read();
|
||||
}
|
||||
for (int i = nameLen; i < 16; i++) {
|
||||
stream.skip(1);
|
||||
}
|
||||
name = new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtScanWizardButtonConnected extends EventPacket {
|
||||
public int scanWizardId;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
scanWizardId = StreamUtils.getInt32(stream);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtScanWizardCompleted extends EventPacket {
|
||||
public int scanWizardId;
|
||||
public ScanWizardResult result;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
scanWizardId = StreamUtils.getInt32(stream);
|
||||
result = ScanWizardResult.values()[StreamUtils.getUInt8(stream)];
|
||||
}
|
||||
}
|
||||
|
||||
class EvtButtonDeleted extends EventPacket {
|
||||
public Bdaddr bdaddr;
|
||||
public boolean deletedByThisClient;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
bdaddr = StreamUtils.getBdaddr(stream);
|
||||
deletedByThisClient = StreamUtils.getBoolean(stream);
|
||||
}
|
||||
}
|
||||
|
||||
class EvtBatteryStatus extends EventPacket {
|
||||
public int listenerId;
|
||||
public int batteryPercentage;
|
||||
public long timestamp;
|
||||
|
||||
@Override
|
||||
protected void parseInternal(InputStream stream) throws IOException {
|
||||
listenerId = StreamUtils.getInt32(stream);
|
||||
batteryPercentage = StreamUtils.getInt8(stream);
|
||||
timestamp = StreamUtils.getInt64(stream);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import io.flic.fliclib.javaclient.enums.ScanWizardResult;
|
||||
|
||||
/**
|
||||
* Scan wizard class.
|
||||
*
|
||||
* This class will scan for a new button and pair it automatically.
|
||||
* There are internal timeouts that make sure operations don't take too long time.
|
||||
*
|
||||
* Inherit this class and override the methods.
|
||||
* Then add this scan wizard to a {@link FlicClient} using {@link FlicClient#addScanWizard(ScanWizard)} to start it.
|
||||
* You can cancel by calling {@link FlicClient#cancelScanWizard(ScanWizard)}.
|
||||
*/
|
||||
public abstract class ScanWizard {
|
||||
private static AtomicInteger nextId = new AtomicInteger();
|
||||
int scanWizardId = nextId.getAndIncrement();
|
||||
Bdaddr bdaddr;
|
||||
String name;
|
||||
|
||||
/**
|
||||
* This will be called once if a private button is found.
|
||||
*
|
||||
* Tell the user to hold down the button for 7 seconds in order to make it public.
|
||||
*
|
||||
*/
|
||||
public abstract void onFoundPrivateButton() throws IOException;
|
||||
|
||||
/**
|
||||
* This will be called once a public button is found.
|
||||
*
|
||||
* Now a connection attempt will be made to the device in order to pair and verify it.
|
||||
*
|
||||
* @param bdaddr Bluetooth Device Address
|
||||
* @param name Advertising name
|
||||
*/
|
||||
public abstract void onFoundPublicButton(Bdaddr bdaddr, String name) throws IOException;
|
||||
|
||||
/**
|
||||
* This will be called once the bluetooth connection has been established.
|
||||
*
|
||||
* Now a pair attempt will be made.
|
||||
*
|
||||
* @param bdaddr Bluetooth Device Address
|
||||
* @param name Advertising name
|
||||
*/
|
||||
public abstract void onButtonConnected(Bdaddr bdaddr, String name) throws IOException;
|
||||
|
||||
/**
|
||||
* Scan wizard completed.
|
||||
*
|
||||
* If the result is success, you can now create a connection channel to the button.
|
||||
*
|
||||
* The ScanWizard is now detached from the FlicClient and can now be recycled.
|
||||
*
|
||||
* @param result Result of the scan wizard
|
||||
* @param bdaddr Bluetooth Device Address or null, depending on if {@link #onFoundPublicButton} has been called or not
|
||||
* @param name Advertising name or null, depending on if {@link #onFoundPublicButton} has been called or not
|
||||
*/
|
||||
public abstract void onCompleted(ScanWizardResult result, Bdaddr bdaddr, String name) throws IOException;
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
class StreamUtils {
|
||||
public static boolean getBoolean(InputStream stream) throws IOException {
|
||||
return stream.read() != 0;
|
||||
}
|
||||
public static int getUInt8(InputStream stream) throws IOException {
|
||||
return stream.read();
|
||||
}
|
||||
|
||||
public static int getInt8(InputStream stream) throws IOException {
|
||||
return (byte)stream.read();
|
||||
}
|
||||
|
||||
public static int getUInt16(InputStream stream) throws IOException {
|
||||
return stream.read() | (stream.read() << 8);
|
||||
}
|
||||
|
||||
public static int getInt16(InputStream stream) throws IOException {
|
||||
return (short)getUInt16(stream);
|
||||
}
|
||||
|
||||
public static int getInt32(InputStream stream) throws IOException {
|
||||
return stream.read() | (stream.read() << 8) | (stream.read() << 16) | (stream.read() << 24);
|
||||
}
|
||||
|
||||
public static long getInt64(InputStream stream) throws IOException {
|
||||
return (getInt32(stream) & 0xffffffffL) | ((long)getInt32(stream) << 32);
|
||||
}
|
||||
|
||||
public static Bdaddr getBdaddr(InputStream stream) throws IOException {
|
||||
return new Bdaddr(stream);
|
||||
}
|
||||
|
||||
public static byte[] getByteArr(InputStream stream, int len) throws IOException {
|
||||
byte[] arr = new byte[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
arr[i] = (byte)stream.read();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static String getString(InputStream stream, int maxlen) throws IOException {
|
||||
int len = getInt8(stream);
|
||||
byte[] arr = new byte[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
arr[i] = (byte)stream.read();
|
||||
}
|
||||
for (int i = len; i < maxlen; i++) {
|
||||
stream.skip(1);
|
||||
}
|
||||
return new String(arr, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static void writeEnum(OutputStream stream, Enum<?> enumValue) throws IOException {
|
||||
stream.write(enumValue.ordinal());
|
||||
}
|
||||
|
||||
public static void writeInt8(OutputStream stream, int v) throws IOException {
|
||||
stream.write(v);
|
||||
}
|
||||
|
||||
public static void writeInt16(OutputStream stream, int v) throws IOException {
|
||||
stream.write(v & 0xff);
|
||||
stream.write(v >> 8);
|
||||
}
|
||||
|
||||
public static void writeInt32(OutputStream stream, int v) throws IOException {
|
||||
writeInt16(stream, v);
|
||||
writeInt16(stream, v >> 16);
|
||||
}
|
||||
|
||||
public static void writeBdaddr(OutputStream stream, Bdaddr addr) throws IOException {
|
||||
stream.write(addr.getBytes());
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package io.flic.fliclib.javaclient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* TimerTask.
|
||||
*
|
||||
* Use this interface instead of {@link Runnable} to avoid having to deal with IOExceptions.
|
||||
* Invocations of the run method on this interface from the {@link FlicClient} will propagate IOExceptions to the caller of {@link FlicClient#handleEvents()}.
|
||||
*
|
||||
*/
|
||||
public interface TimerTask {
|
||||
void run() throws IOException;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
/**
|
||||
* Created by Emil on 2016-05-03.
|
||||
*/
|
||||
public enum BdAddrType {
|
||||
PublicBdAddrType,
|
||||
RandomBdAddrType
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
/**
|
||||
* Created by Emil on 2016-05-03.
|
||||
*/
|
||||
public enum BluetoothControllerState {
|
||||
Detached,
|
||||
Resetting,
|
||||
Attached
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
/**
|
||||
* Created by Emil on 2016-05-03.
|
||||
*/
|
||||
public enum ClickType {
|
||||
ButtonDown,
|
||||
ButtonUp,
|
||||
ButtonClick,
|
||||
ButtonSingleClick,
|
||||
ButtonDoubleClick,
|
||||
ButtonHold
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
/**
|
||||
* Created by Emil on 2016-05-03.
|
||||
*/
|
||||
public enum ConnectionStatus {
|
||||
Disconnected,
|
||||
Connected,
|
||||
Ready
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
/**
|
||||
* Created by Emil on 2016-05-03.
|
||||
*/
|
||||
public enum CreateConnectionChannelError {
|
||||
NoError,
|
||||
MaxPendingConnectionsReached
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
/**
|
||||
* Created by Emil on 2016-05-03.
|
||||
*/
|
||||
public enum DisconnectReason {
|
||||
Unspecified,
|
||||
ConnectionEstablishmentFailed,
|
||||
TimedOut,
|
||||
BondingKeysMismatch
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
/**
|
||||
* Created by Emil on 2016-05-03.
|
||||
*/
|
||||
public enum LatencyMode {
|
||||
NormalLatency,
|
||||
LowLatency,
|
||||
HighLatency
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
/**
|
||||
* Created by Emil on 2016-05-03.
|
||||
*/
|
||||
public enum RemovedReason {
|
||||
RemovedByThisClient,
|
||||
ForceDisconnectedByThisClient,
|
||||
ForceDisconnectedByOtherClient,
|
||||
|
||||
ButtonIsPrivate,
|
||||
VerifyTimeout,
|
||||
InternetBackendError,
|
||||
InvalidData,
|
||||
|
||||
CouldntLoadDevice,
|
||||
|
||||
DeletedByThisClient,
|
||||
DeletedByOtherClient,
|
||||
ButtonBelongsToOtherPartner,
|
||||
DeletedFromButton
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package io.flic.fliclib.javaclient.enums;
|
||||
|
||||
public enum ScanWizardResult {
|
||||
WizardSuccess,
|
||||
WizardCancelledByUser,
|
||||
WizardFailedTimeout,
|
||||
WizardButtonIsPrivate,
|
||||
WizardBluetoothUnavailable,
|
||||
WizardInternetBackendError,
|
||||
WizardInvalidData,
|
||||
WizardButtonBelongsToOtherPartner,
|
||||
WizardButtonAlreadyConnectedToOtherDevice
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.flicbutton-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-flicbutton" description="FlicButton Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.flicbutton/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.CommonTriggerEvents;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link FlicButtonBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "flicbutton";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "flicd-bridge");
|
||||
public static final ThingTypeUID FLICBUTTON_THING_TYPE = new ThingTypeUID(BINDING_ID, "button");
|
||||
|
||||
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections.singleton(BRIDGE_THING_TYPE);
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(FLICBUTTON_THING_TYPE);
|
||||
|
||||
// List of all configuration options
|
||||
public static final String CONFIG_HOST_NAME = "hostname";
|
||||
public static final String CONFIG_PORT = "port";
|
||||
public static final String CONFIG_ADDRESS = "address";
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_ID_RAWBUTTON_EVENTS = "rawbutton";
|
||||
public static final String CHANNEL_ID_BUTTON_EVENTS = "button";
|
||||
public static final String CHANNEL_ID_BATTERY_LEVEL = "battery-level";
|
||||
|
||||
// Other stuff
|
||||
public static final int BUTTON_OFFLINE_GRACE_PERIOD_SECONDS = 60;
|
||||
|
||||
public static final Map<String, String> FLIC_OPENHAB_TRIGGER_EVENT_MAP = Collections
|
||||
.unmodifiableMap(new HashMap<String, String>() {
|
||||
{
|
||||
put("ButtonSingleClick", CommonTriggerEvents.SHORT_PRESSED);
|
||||
put("ButtonDoubleClick", CommonTriggerEvents.DOUBLE_PRESSED);
|
||||
put("ButtonHold", CommonTriggerEvents.LONG_PRESSED);
|
||||
put("ButtonDown", CommonTriggerEvents.PRESSED);
|
||||
put("ButtonUp", CommonTriggerEvents.RELEASED);
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.flicbutton.internal.discovery.FlicButtonDiscoveryService;
|
||||
import org.openhab.binding.flicbutton.internal.discovery.FlicSimpleclientDiscoveryServiceImpl;
|
||||
import org.openhab.binding.flicbutton.internal.handler.FlicButtonHandler;
|
||||
import org.openhab.binding.flicbutton.internal.handler.FlicDaemonBridgeHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link FlicButtonHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.flicbutton")
|
||||
@NonNullByDefault
|
||||
public class FlicButtonHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.concat(FlicButtonBindingConstants.BRIDGE_THING_TYPES_UIDS.stream(),
|
||||
FlicButtonBindingConstants.SUPPORTED_THING_TYPES_UIDS.stream())
|
||||
.collect(Collectors.toSet());
|
||||
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(FlicButtonBindingConstants.FLICBUTTON_THING_TYPE)) {
|
||||
return new FlicButtonHandler(thing);
|
||||
} else if (thingTypeUID.equals(FlicButtonBindingConstants.BRIDGE_THING_TYPE)) {
|
||||
FlicButtonDiscoveryService discoveryService = new FlicSimpleclientDiscoveryServiceImpl(thing.getUID());
|
||||
FlicDaemonBridgeHandler bridgeHandler = new FlicDaemonBridgeHandler((Bridge) thing, discoveryService);
|
||||
registerDiscoveryService(discoveryService, thing.getUID());
|
||||
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof FlicDaemonBridgeHandler) {
|
||||
unregisterDiscoveryService(thingHandler.getThing().getUID());
|
||||
}
|
||||
super.removeHandler(thingHandler);
|
||||
}
|
||||
|
||||
private synchronized void registerDiscoveryService(FlicButtonDiscoveryService discoveryService,
|
||||
ThingUID bridgeUID) {
|
||||
this.discoveryServiceRegs.put(bridgeUID, getBundleContext().registerService(DiscoveryService.class.getName(),
|
||||
discoveryService, new Hashtable<String, Object>()));
|
||||
}
|
||||
|
||||
private synchronized void unregisterDiscoveryService(ThingUID bridgeUID) {
|
||||
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.get(bridgeUID);
|
||||
if (serviceReg != null) {
|
||||
FlicButtonDiscoveryService service = (FlicButtonDiscoveryService) getBundleContext()
|
||||
.getService(serviceReg.getReference());
|
||||
if (service != null) {
|
||||
service.deactivate();
|
||||
}
|
||||
serviceReg.unregister();
|
||||
discoveryServiceRegs.remove(bridgeUID);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.discovery;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
import io.flic.fliclib.javaclient.FlicClient;
|
||||
|
||||
/**
|
||||
* A {@link DiscoveryService} for Flic buttons.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface FlicButtonDiscoveryService extends DiscoveryService {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bdaddr Bluetooth address of the discovered Flic button
|
||||
* @return UID that was created by the discovery service
|
||||
*/
|
||||
public ThingUID flicButtonDiscovered(Bdaddr bdaddr);
|
||||
|
||||
public void activate(FlicClient client);
|
||||
|
||||
public void deactivate();
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.discovery;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants;
|
||||
import org.openhab.binding.flicbutton.internal.util.FlicButtonUtils;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
import io.flic.fliclib.javaclient.FlicClient;
|
||||
import io.flic.fliclib.javaclient.GeneralCallbacks;
|
||||
import io.flic.fliclib.javaclient.GetInfoResponseCallback;
|
||||
import io.flic.fliclib.javaclient.enums.BdAddrType;
|
||||
import io.flic.fliclib.javaclient.enums.BluetoothControllerState;
|
||||
|
||||
/**
|
||||
* For each configured flicd service, there is a {@link FlicSimpleclientDiscoveryServiceImpl} which will be initialized
|
||||
* by {@link org.openhab.binding.flicbutton.internal.FlicButtonHandlerFactory}.
|
||||
*
|
||||
* It can scan for Flic Buttons already that are already added to fliclib-linux-hci ("verified" buttons), *
|
||||
* but it does not support adding and verify new buttons on it's own.
|
||||
* New buttons have to be added (verified) e.g. via simpleclient by Shortcut Labs.
|
||||
* Background discovery listens for new buttons that are getting verified.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicSimpleclientDiscoveryServiceImpl extends AbstractDiscoveryService
|
||||
implements FlicButtonDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(FlicSimpleclientDiscoveryServiceImpl.class);
|
||||
|
||||
private boolean activated = false;
|
||||
private ThingUID bridgeUID;
|
||||
private @Nullable FlicClient flicClient;
|
||||
|
||||
public FlicSimpleclientDiscoveryServiceImpl(ThingUID bridgeUID) {
|
||||
super(FlicButtonBindingConstants.SUPPORTED_THING_TYPES_UIDS, 2, true);
|
||||
this.bridgeUID = bridgeUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate(FlicClient flicClient) {
|
||||
this.flicClient = flicClient;
|
||||
activated = true;
|
||||
super.activate(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
activated = false;
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
try {
|
||||
if (activated) {
|
||||
discoverVerifiedButtons();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error occured during button discovery", e);
|
||||
if (this.scanListener != null) {
|
||||
scanListener.onErrorOccurred(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void discoverVerifiedButtons() throws IOException {
|
||||
flicClient.getInfo(new GetInfoResponseCallback() {
|
||||
@Override
|
||||
public void onGetInfoResponse(@Nullable BluetoothControllerState bluetoothControllerState,
|
||||
@Nullable Bdaddr myBdAddr, @Nullable BdAddrType myBdAddrType, int maxPendingConnections,
|
||||
int maxConcurrentlyConnectedButtons, int currentPendingConnections,
|
||||
boolean currentlyNoSpaceForNewConnection, Bdaddr @Nullable [] verifiedButtons) throws IOException {
|
||||
for (final @Nullable Bdaddr bdaddr : verifiedButtons) {
|
||||
if (bdaddr != null) {
|
||||
flicButtonDiscovered((@NonNull Bdaddr) bdaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
super.startBackgroundDiscovery();
|
||||
flicClient.setGeneralCallbacks(new GeneralCallbacks() {
|
||||
@Override
|
||||
public void onNewVerifiedButton(@Nullable Bdaddr bdaddr) throws IOException {
|
||||
logger.debug("A new Flic button was added by an external flicd client: {}", bdaddr);
|
||||
if (bdaddr != null) {
|
||||
flicButtonDiscovered((@NonNull Bdaddr) bdaddr);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
super.stopBackgroundDiscovery();
|
||||
if (flicClient != null) {
|
||||
flicClient.setGeneralCallbacks(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID flicButtonDiscovered(Bdaddr bdaddr) {
|
||||
logger.debug("Flic Button {} discovered!", bdaddr);
|
||||
ThingUID flicButtonUID = FlicButtonUtils.getThingUIDFromBdAddr(bdaddr, bridgeUID);
|
||||
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(flicButtonUID).withBridge(bridgeUID)
|
||||
.withLabel("Flic Button " + bdaddr.toString().replace(":", ""))
|
||||
.withProperty(FlicButtonBindingConstants.CONFIG_ADDRESS, bdaddr.toString())
|
||||
.withRepresentationProperty(FlicButtonBindingConstants.CONFIG_ADDRESS).build();
|
||||
this.thingDiscovered(discoveryResult);
|
||||
return flicButtonUID;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
|
||||
/**
|
||||
* The {@link ChildThingHandler} class is an abstract class for handlers that are dependent from a parent
|
||||
* {@link BridgeHandler}.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
* @param <BridgeHandlerType> The bridge type this child handler depends on
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class ChildThingHandler<BridgeHandlerType extends BridgeHandler> extends BaseThingHandler {
|
||||
private static final Collection<ThingStatus> DEFAULT_TOLERATED_BRIDGE_STATUSES = Collections
|
||||
.singleton(ThingStatus.ONLINE);
|
||||
protected boolean bridgeValid = false;
|
||||
protected @Nullable BridgeHandlerType bridgeHandler;
|
||||
|
||||
public ChildThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
setStatusBasedOnBridge();
|
||||
if (getBridge() != null) {
|
||||
linkBridge();
|
||||
}
|
||||
}
|
||||
|
||||
protected void linkBridge() {
|
||||
try {
|
||||
BridgeHandler bridgeHandlerUncasted = getBridge().getHandler();
|
||||
bridgeHandler = (BridgeHandlerType) bridgeHandlerUncasted;
|
||||
} catch (ClassCastException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge Type is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
protected void setStatusBasedOnBridge() {
|
||||
setStatusBasedOnBridge(DEFAULT_TOLERATED_BRIDGE_STATUSES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
this.setStatusBasedOnBridge();
|
||||
}
|
||||
|
||||
protected void setStatusBasedOnBridge(Collection<ThingStatus> toleratedBridgeStatuses) {
|
||||
if (getBridge() != null) {
|
||||
if (toleratedBridgeStatuses.contains(getBridge().getStatus())) {
|
||||
bridgeValid = true;
|
||||
} else {
|
||||
bridgeValid = false;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
"Bridge in unsupported status: " + getBridge().getStatus());
|
||||
}
|
||||
} else {
|
||||
bridgeValid = false;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Bridge missing.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import io.flic.fliclib.javaclient.BatteryStatusListener;
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
|
||||
/**
|
||||
* Each {@link FlicButtonBatteryLevelListener} object listens to the battery status of a specific Flic button
|
||||
* and calls updates the {@link FlicButtonHandler} accordingly.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonBatteryLevelListener extends BatteryStatusListener.Callbacks {
|
||||
|
||||
private final FlicButtonHandler thingHandler;
|
||||
|
||||
FlicButtonBatteryLevelListener(FlicButtonHandler thingHandler) {
|
||||
this.thingHandler = thingHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatteryStatus(@Nullable Bdaddr bdaddr, int i, long l) throws IOException {
|
||||
thingHandler.updateBatteryChannel(i);
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.flic.fliclib.javaclient.ButtonConnectionChannel;
|
||||
import io.flic.fliclib.javaclient.enums.ClickType;
|
||||
import io.flic.fliclib.javaclient.enums.ConnectionStatus;
|
||||
import io.flic.fliclib.javaclient.enums.CreateConnectionChannelError;
|
||||
import io.flic.fliclib.javaclient.enums.DisconnectReason;
|
||||
import io.flic.fliclib.javaclient.enums.RemovedReason;
|
||||
|
||||
/**
|
||||
* Each {@link FlicButtonEventListener} object listens to events of a specific Flic button and calls the
|
||||
* associated {@link FlicButtonHandler} back accordingly.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonEventListener extends ButtonConnectionChannel.Callbacks {
|
||||
private final Logger logger = LoggerFactory.getLogger(FlicButtonEventListener.class);
|
||||
|
||||
private final FlicButtonHandler thingHandler;
|
||||
private final Semaphore channelResponseSemaphore = new Semaphore(0);
|
||||
|
||||
FlicButtonEventListener(FlicButtonHandler thingHandler) {
|
||||
this.thingHandler = thingHandler;
|
||||
}
|
||||
|
||||
public Semaphore getChannelResponseSemaphore() {
|
||||
return channelResponseSemaphore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onCreateConnectionChannelResponse(@Nullable ButtonConnectionChannel channel,
|
||||
@Nullable CreateConnectionChannelError createConnectionChannelError,
|
||||
@Nullable ConnectionStatus connectionStatus) {
|
||||
logger.debug("Create response {}: {}, {}", channel.getBdaddr(), createConnectionChannelError, connectionStatus);
|
||||
// Handling does not differ from Status change, so redirect
|
||||
if (connectionStatus != null) {
|
||||
thingHandler.initializeStatus((@NonNull ConnectionStatus) connectionStatus);
|
||||
channelResponseSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(@Nullable ButtonConnectionChannel channel, @Nullable RemovedReason removedReason) {
|
||||
thingHandler.flicButtonRemoved();
|
||||
logger.debug("Button {} removed. ThingStatus updated to OFFLINE. Reason: {}", channel.getBdaddr(),
|
||||
removedReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionStatusChanged(@Nullable ButtonConnectionChannel channel,
|
||||
@Nullable ConnectionStatus connectionStatus, @Nullable DisconnectReason disconnectReason) {
|
||||
logger.trace("New status for {}: {}", channel.getBdaddr(),
|
||||
connectionStatus + (connectionStatus == ConnectionStatus.Disconnected ? ", " + disconnectReason : ""));
|
||||
if (connectionStatus != null) {
|
||||
thingHandler.connectionStatusChanged((@NonNull ConnectionStatus) connectionStatus, disconnectReason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonUpOrDown(@Nullable ButtonConnectionChannel channel, @Nullable ClickType clickType,
|
||||
boolean wasQueued, int timeDiff) throws IOException {
|
||||
if (channel != null && clickType != null) {
|
||||
logger.trace("{} {}", channel.getBdaddr(), clickType.name());
|
||||
String commonTriggerEvent = FlicButtonBindingConstants.FLIC_OPENHAB_TRIGGER_EVENT_MAP.get(clickType.name());
|
||||
if (commonTriggerEvent != null) {
|
||||
thingHandler.fireTriggerEvent(commonTriggerEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonSingleOrDoubleClickOrHold(@Nullable ButtonConnectionChannel channel,
|
||||
@Nullable ClickType clickType, boolean wasQueued, int timeDiff) throws IOException {
|
||||
// Handling does not differ from up/down events, so redirect
|
||||
if (channel != null && clickType != null) {
|
||||
onButtonUpOrDown((@NonNull ButtonConnectionChannel) channel, (@NonNull ClickType) clickType, wasQueued,
|
||||
timeDiff);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import static org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.CommonTriggerEvents;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.flic.fliclib.javaclient.BatteryStatusListener;
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
import io.flic.fliclib.javaclient.ButtonConnectionChannel;
|
||||
import io.flic.fliclib.javaclient.enums.ConnectionStatus;
|
||||
import io.flic.fliclib.javaclient.enums.DisconnectReason;
|
||||
|
||||
/**
|
||||
* The {@link FlicButtonHandler} is responsible for initializing the online status of Flic Buttons
|
||||
* and trigger channel events when they're used.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonHandler extends ChildThingHandler<FlicDaemonBridgeHandler> {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(FlicButtonHandler.class);
|
||||
private @Nullable ScheduledFuture<?> delayedDisconnectTask;
|
||||
private @Nullable Future<?> initializationTask;
|
||||
private @Nullable DisconnectReason latestDisconnectReason;
|
||||
private @Nullable ButtonConnectionChannel eventConnection;
|
||||
private @Nullable Bdaddr bdaddr;
|
||||
private @Nullable BatteryStatusListener batteryConnection;
|
||||
|
||||
public FlicButtonHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
public @Nullable Bdaddr getBdaddr() {
|
||||
return bdaddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Pure sensor -> no commands have to be handled
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
bdaddr = new Bdaddr((String) this.getThing().getConfiguration().get(CONFIG_ADDRESS));
|
||||
if (bridgeValid) {
|
||||
initializationTask = scheduler.submit(this::initializeThing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
super.bridgeStatusChanged(bridgeStatusInfo);
|
||||
if (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE && bridgeValid) {
|
||||
dispose();
|
||||
initializationTask = scheduler.submit(this::initializeThing);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeThing() {
|
||||
try {
|
||||
initializeBatteryListener();
|
||||
initializeEventListener();
|
||||
// EventListener calls initializeStatus() before releasing so that ThingStatus should be set at this point
|
||||
if (this.getThing().getStatus().equals(ThingStatus.INITIALIZING)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Got no response by eventListener");
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Connection setup failed: {}" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBatteryListener() throws IOException {
|
||||
FlicButtonBatteryLevelListener batteryListener = new FlicButtonBatteryLevelListener(this);
|
||||
BatteryStatusListener batteryConnection = new BatteryStatusListener(getBdaddr(), batteryListener);
|
||||
bridgeHandler.getFlicClient().addBatteryStatusListener(batteryConnection);
|
||||
this.batteryConnection = batteryConnection;
|
||||
}
|
||||
|
||||
public void initializeEventListener() throws IOException, InterruptedException {
|
||||
FlicButtonEventListener eventListener = new FlicButtonEventListener(this);
|
||||
ButtonConnectionChannel eventConnection = new ButtonConnectionChannel(getBdaddr(), eventListener);
|
||||
bridgeHandler.getFlicClient().addConnectionChannel(eventConnection);
|
||||
this.eventConnection = eventConnection;
|
||||
eventListener.getChannelResponseSemaphore().tryAcquire(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelDelayedDisconnectTask();
|
||||
cancelInitializationTask();
|
||||
try {
|
||||
if (eventConnection != null) {
|
||||
bridgeHandler.getFlicClient().removeConnectionChannel(eventConnection);
|
||||
}
|
||||
if (batteryConnection != null) {
|
||||
bridgeHandler.getFlicClient().removeBatteryStatusListener(this.batteryConnection);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Button channel could not be properly removed", e);
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void initializeStatus(ConnectionStatus connectionStatus) {
|
||||
if (connectionStatus == ConnectionStatus.Disconnected) {
|
||||
setOffline();
|
||||
} else {
|
||||
setOnline();
|
||||
}
|
||||
}
|
||||
|
||||
void connectionStatusChanged(ConnectionStatus connectionStatus, @Nullable DisconnectReason disconnectReason) {
|
||||
latestDisconnectReason = disconnectReason;
|
||||
if (connectionStatus == ConnectionStatus.Disconnected) {
|
||||
// Status change to offline have to be scheduled to improve stability,
|
||||
// see https://github.com/pfink/openhab2-flicbutton/issues/2
|
||||
scheduleStatusChangeToOffline();
|
||||
} else {
|
||||
setOnline();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleStatusChangeToOffline() {
|
||||
if (delayedDisconnectTask == null) {
|
||||
delayedDisconnectTask = scheduler.schedule(this::setOffline, BUTTON_OFFLINE_GRACE_PERIOD_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setOnline() {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
}
|
||||
|
||||
protected void setOffline() {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE,
|
||||
"Disconnect Reason: " + Objects.toString(latestDisconnectReason));
|
||||
}
|
||||
|
||||
// Cleanup delayedDisconnect on status change to online
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
|
||||
if (status == ThingStatus.ONLINE) {
|
||||
cancelDelayedDisconnectTask();
|
||||
}
|
||||
super.updateStatus(status, statusDetail, description);
|
||||
}
|
||||
|
||||
private void cancelInitializationTask() {
|
||||
if (initializationTask != null) {
|
||||
initializationTask.cancel(true);
|
||||
initializationTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelDelayedDisconnectTask() {
|
||||
if (delayedDisconnectTask != null) {
|
||||
delayedDisconnectTask.cancel(false);
|
||||
delayedDisconnectTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
void updateBatteryChannel(int percent) {
|
||||
DecimalType batteryLevel = new DecimalType(percent);
|
||||
updateState(CHANNEL_ID_BATTERY_LEVEL, batteryLevel);
|
||||
}
|
||||
|
||||
void flicButtonRemoved() {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.GONE,
|
||||
"Button was removed/detached from flicd (e.g. by simpleclient).");
|
||||
}
|
||||
|
||||
void fireTriggerEvent(String event) {
|
||||
String channelID = event.equals(CommonTriggerEvents.PRESSED) || event.equals(CommonTriggerEvents.RELEASED)
|
||||
? CHANNEL_ID_RAWBUTTON_EVENTS
|
||||
: CHANNEL_ID_BUTTON_EVENTS;
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
triggerChannel(channelID, event);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The configuration of a flicd bridge handled by {@link FlicDaemonBridgeHandler}.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicDaemonBridgeConfiguration {
|
||||
|
||||
@Nullable
|
||||
private String hostname;
|
||||
private int port;
|
||||
|
||||
public @Nullable InetAddress getHost() throws UnknownHostException {
|
||||
return InetAddress.getByName(hostname);
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.flicbutton.internal.discovery.FlicButtonDiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.flic.fliclib.javaclient.FlicClient;
|
||||
|
||||
/**
|
||||
* The {@link FlicDaemonBridgeHandler} handles a running instance of the fliclib-linux-hci server (flicd).
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicDaemonBridgeHandler extends BaseBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(FlicDaemonBridgeHandler.class);
|
||||
private static final long REINITIALIZE_DELAY_SECONDS = 10;
|
||||
// Config parameters
|
||||
private @Nullable FlicDaemonBridgeConfiguration cfg;
|
||||
// Services
|
||||
private FlicButtonDiscoveryService buttonDiscoveryService;
|
||||
private @Nullable Future<?> flicClientFuture;
|
||||
// For disposal
|
||||
private Collection<@Nullable Future<?>> startedTasks = new ArrayList<>(3);
|
||||
private @Nullable FlicClient flicClient;
|
||||
|
||||
public FlicDaemonBridgeHandler(Bridge bridge, FlicButtonDiscoveryService buttonDiscoveryService) {
|
||||
super(bridge);
|
||||
this.buttonDiscoveryService = buttonDiscoveryService;
|
||||
}
|
||||
|
||||
public @Nullable FlicClient getFlicClient() {
|
||||
return flicClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
startedTasks.add(scheduler.submit(this::initializeThing));
|
||||
}
|
||||
|
||||
public void initializeThing() {
|
||||
try {
|
||||
initConfigParameters();
|
||||
startFlicdClientAsync();
|
||||
activateButtonDiscoveryService();
|
||||
initThingStatus();
|
||||
} catch (UnknownHostException ignored) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hostname wrong or unknown!");
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error connecting to flicd: " + e.getMessage());
|
||||
dispose();
|
||||
scheduleReinitialize();
|
||||
}
|
||||
}
|
||||
|
||||
private void initConfigParameters() {
|
||||
cfg = getConfigAs(FlicDaemonBridgeConfiguration.class);
|
||||
}
|
||||
|
||||
private void activateButtonDiscoveryService() {
|
||||
if (flicClient != null) {
|
||||
buttonDiscoveryService.activate((@NonNull FlicClient) flicClient);
|
||||
} else {
|
||||
throw new IllegalStateException("flicClient not properly initialized");
|
||||
}
|
||||
}
|
||||
|
||||
private void startFlicdClientAsync() throws IOException {
|
||||
flicClient = new FlicClient(cfg.getHost().getHostAddress(), cfg.getPort());
|
||||
Runnable flicClientService = () -> {
|
||||
try {
|
||||
flicClient.handleEvents();
|
||||
flicClient.close();
|
||||
logger.debug("Listening to flicd ended");
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error occured while listening to flicd", e);
|
||||
} finally {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
onClientFailure();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
flicClientFuture = scheduler.submit(flicClientService);
|
||||
startedTasks.add(flicClientFuture);
|
||||
}
|
||||
}
|
||||
|
||||
private void onClientFailure() {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"flicd client terminated, probably flicd is not reachable anymore.");
|
||||
dispose();
|
||||
scheduleReinitialize();
|
||||
}
|
||||
|
||||
private void initThingStatus() {
|
||||
if (!flicClientFuture.isDone()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"flicd client could not be started, probably flicd is not reachable.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
startedTasks.forEach(task -> task.cancel(true));
|
||||
startedTasks = new ArrayList<>(2);
|
||||
buttonDiscoveryService.deactivate();
|
||||
}
|
||||
|
||||
private void scheduleReinitialize() {
|
||||
startedTasks.add(scheduler.schedule(this::initialize, REINITIALIZE_DELAY_SECONDS, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// No commands to the fliclib-linux-hci are supported.
|
||||
// So there is nothing to handle in the bridge handler
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.util;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
|
||||
/**
|
||||
* The {@link FlicButtonUtils} class defines static utility methods that are used within the binding.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonUtils {
|
||||
public static ThingUID getThingUIDFromBdAddr(Bdaddr bdaddr, ThingUID bridgeUID) {
|
||||
String thingID = bdaddr.toString().replace(":", "-");
|
||||
return new ThingUID(FlicButtonBindingConstants.FLICBUTTON_THING_TYPE, bridgeUID, thingID);
|
||||
}
|
||||
|
||||
public static Bdaddr getBdAddrFromThingUID(ThingUID thingUID) {
|
||||
String bdaddrRaw = thingUID.getId().replace("-", ":");
|
||||
return new Bdaddr(bdaddrRaw);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="flicbutton" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>FlicButton Binding</name>
|
||||
<description>This is the binding for Flic buttons by Shortcut Labs.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="flicbutton"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="flicd-bridge">
|
||||
<label>FlicButton Bridge</label>
|
||||
<description>This bridge represents a running instance of the fliclib-linux-hci server (flicd).</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text" required="false">
|
||||
<context>network-address</context>
|
||||
<label>Flic Daemon (flicd) Hostname</label>
|
||||
<description>IP or Host name of the Flic daemon (flicd).</description>
|
||||
<default>localhost</default>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="false">
|
||||
<label>Flic Daemon (flicd) Port</label>
|
||||
<description>Port where flicd is running. Defaults to 5551.</description>
|
||||
<default>5551</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="button">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="flicd-bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Flic Button</label>
|
||||
<description>The thing(-type) representing a Flic Button</description>
|
||||
<channels>
|
||||
<channel id="rawbutton" typeId="system.rawbutton"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="battery-level" typeId="system.battery-level"/>
|
||||
</channels>
|
||||
<config-description>
|
||||
<parameter name="address" type="text" required="true">
|
||||
<label>Address</label>
|
||||
<description>Bluetooth address in XX:XX:XX:XX:XX:XX format</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
@ -125,6 +125,7 @@
|
||||
<module>org.openhab.binding.exec</module>
|
||||
<module>org.openhab.binding.feed</module>
|
||||
<module>org.openhab.binding.feican</module>
|
||||
<module>org.openhab.binding.flicbutton</module>
|
||||
<module>org.openhab.binding.fmiweather</module>
|
||||
<module>org.openhab.binding.folderwatcher</module>
|
||||
<module>org.openhab.binding.folding</module>
|
||||
|
Loading…
Reference in New Issue
Block a user