Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[huesync][WIP] Hue Play HDMI Sync Box Binding - Initial contribution #16516

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9e7fa3a
☠️ Binding skeleton created for org.openhab.binding.huesync
pgfeller Feb 8, 2024
bc72f9a
🔃 Version Sync - we'll use 4.1.x for development ...
pgfeller Feb 9, 2024
377df2f
💳 updated credits in headers (initial code by Marco Kawon)
pgfeller Feb 9, 2024
360f167
✏️ class & filenames updated to be consistent with other bindings
pgfeller Feb 9, 2024
676fe38
🔎 skeleton mDNS discovery implemented
pgfeller Feb 9, 2024
452df77
🔎 mDNS device discovery - use API to get device information (wip 🔨)
pgfeller Feb 10, 2024
0b18704
🔐 preparation(s) to support SSL (wip), create ThingUID for discovered…
pgfeller Feb 13, 2024
f72a383
🏭 Inbox/Thing Factory (wip)
pgfeller Feb 14, 2024
edbf604
🔎 device discovery improvements (wip)
pgfeller Feb 16, 2024
0c59b98
💬 Basic JSON de-serialization added to Thing-Handler to get device in…
pgfeller Feb 17, 2024
f52e9b7
🔐 SSL Handshake & 🔎 Discovery working - 👍
pgfeller Feb 24, 2024
3178f26
🔓 Registration (acquire API token) - wip 🔧
pgfeller Feb 25, 2024
eab70d1
🔧 Prototype to use resource strings for loggger, exceptions & UI ...
pgfeller Feb 29, 2024
f7a9a76
🔓 Registration (acquire API token) - wip 🔧
pgfeller Mar 2, 2024
a2a03ec
🔓 Registration (acquire API token)
pgfeller Mar 7, 2024
416137b
⏫ pom version change to 4.2
pgfeller Mar 12, 2024
ba801a2
📜 linter fix ...
pgfeller Mar 12, 2024
a42500f
📜 linter fix
pgfeller Mar 13, 2024
e51e230
📜 ... temporary refactoring/removal of log message consistency mechan…
pgfeller Mar 16, 2024
7c7bd48
📦 Device information DTO
pgfeller Mar 17, 2024
397dacb
Merge branch 'openhab:main' into 10218-hue-add-hue-sync-box
pgfeller Mar 18, 2024
5b5f6e6
👤 Added myself as owner of the huesync binding
pgfeller Mar 21, 2024
d3f4f64
🔧 refactoring to simplify API connection
pgfeller Mar 22, 2024
c6ce2dc
feat(unregistration): 👋 Device unregistration
pgfeller Mar 23, 2024
51fe84e
fix(linter): 🔎 Format should be constant. Use placeholder to reduce t…
pgfeller Mar 23, 2024
fdf069e
fix(linter): 🔎 Format should be constant. Use placeholder to reduce t…
pgfeller Mar 23, 2024
cd5b2b9
refactor: 📜 code moved and group areas added to improve overview
pgfeller Mar 29, 2024
31923c7
refactor: 📜 refactoring to simplify maintentance and pending implemen…
pgfeller Mar 31, 2024
8fae6c1
fix(linter): 🔎 linter performance check fixed
pgfeller Mar 31, 2024
9b01c45
feat(infrastructure): 🔌 check online/offline state (poll) - wip ...
pgfeller Apr 6, 2024
84f3704
refactor(code review): 🔎 improved null handling based on code review
pgfeller Apr 12, 2024
b51b136
refactor(code review): 🔎 improved null handling based on code review
pgfeller Apr 12, 2024
4a12cf1
refactor(code review): 🔎 improved null handling based on code review
pgfeller Apr 12, 2024
5e97fc4
refactor(code review): 🔎 improved null handling based on code review
pgfeller Apr 12, 2024
d992334
refactor(code review): 🔎 code split (single response) and better enca…
pgfeller Apr 15, 2024
3bdbdc7
refactor(code review): 🔎 improved null handling based on code review …
pgfeller Apr 15, 2024
2196f10
refactor(code review): 🔎 improved null handling based on code review
pgfeller Apr 18, 2024
7cce127
refactor(connection): 📜 Simplified authentication mechanism
pgfeller Apr 26, 2024
153d91f
refactor(communication): 📜 improved error handling and manual configu…
pgfeller Apr 30, 2024
a4e7e94
feat(channel): 🔧 firmware channel group added
pgfeller May 3, 2024
0549ac6
fix(channel): 🔃 keep properties and firmware channel information in sync
pgfeller May 7, 2024
ca9af19
feat(dto): 📺 HDMI Status DTO & Get API implemented
pgfeller May 8, 2024
9a33031
feat(channel): Some HDMI input and ouput channels added (read only) -…
pgfeller May 14, 2024
52fa73e
feat(channel): Additional channel `connection-status`added
pgfeller May 18, 2024
5481bfc
feat(channel): ➕ Additional channel `connection-lastSyncMode` added
pgfeller May 19, 2024
41208a7
Merge branch 'openhab:main' into 10218-hue-add-hue-sync-box
pgfeller May 25, 2024
1c948dd
refactor(dto class renamed): 📜 Dto classes renamed to include the nat…
pgfeller May 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.huesync.internal.api.dto;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* HDMI Sync Box Device Information Cababilities DTO
*
Expand All @@ -21,6 +23,7 @@
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
@NonNullByDefault
public class HueSyncDeviceInfoCapabilities {
/** The total number of IR codes configurable */
public int maxIrCodes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.util.Date;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
Expand All @@ -26,28 +27,31 @@
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
@NonNullByDefault
public class HueSyncDetailedDeviceInfo extends HueSyncDeviceInfo {
public HueSyncDeteiledDeviceInfoWifi wifi;
public HueSyncDetailedDeviceInfoUpdate update;
public @Nullable HueSyncDetailedDeviceInfoWifi wifi;
public @Nullable HueSyncDetailedDeviceInfoUpdate update;

/** UTC time when last check for update was performed. */
public Date lastCheckedUpdate;
public @Nullable Date lastCheckedUpdate;
/**
* Build number that is available to update to. Item is set to null when there
* is no update available.
*/
public @Nullable Integer updatableBuildNumber;
public int updatableBuildNumber;
/**
* User readable version of the firmware the device can upgrade to. Item is set
* to null when there is no update available.
*/
public @Nullable String updatableFirmwareVersion;
public int updatableFirmwareVersion;
/**
* 1 = regular; 0 = off in powersave, passthrough or sync mode; 2 = dimmed in
* powersave or passthrough mode and off in sync mode
* 1 = regular;
* 0 = off in powersave, passthrough or sync mode;
* 2 = dimmed in powersave or passthrough mode and off in sync mode
*/
public int ledMode;

/** none, doSoftwareRestart, doFirmwareUpdate */
public String action;
public String pushlink;
public @Nullable String action;
public @Nullable String pushlink;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.huesync.internal.api.dto.device;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
* HDMI Sync Box Device Information DTO - Automatic Firmware update
*
Expand All @@ -21,7 +23,7 @@
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/

@NonNullByDefault
public class HueSyncDetailedDeviceInfoUpdate {
/**
* Sync Box checks daily for a firmware update. If true, an available update
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
*/
package org.openhab.binding.huesync.internal.api.dto.device;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* HDMI Sync Box Device Information DTO - Wifi connection information
*
Expand All @@ -21,9 +24,16 @@
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
public class HueSyncDeteiledDeviceInfoWifi {
@NonNullByDefault
public class HueSyncDetailedDeviceInfoWifi {
/** Wifi SSID */
public String ssid;
/** 0 = not connected; 1 = weak; 2 = fair; 3 = good; 4 = excellent */
public @Nullable String ssid;
/**
* 0 = not connected;
* 1 = weak;
* 2 = fair;
* 3 = good;
* 4 = excellent
*/
public int strength;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
*/
package org.openhab.binding.huesync.internal.api.dto.device;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.huesync.internal.api.dto.HueSyncDeviceInfoCapabilities;

/**
Expand All @@ -24,17 +25,18 @@
* "https://developers.meethue.com/develop/hue-entertainment/hue-hdmi-sync-box-api/#Resource%20Table">Hue
* HDMI Sync Box API</a>
*/
@NonNullByDefault
public class HueSyncDeviceInfo {
/** Friendly name of the device */
public String name;
public @Nullable String name;
/** Device Type identifier */
public String deviceType;
public @Nullable String deviceType;
/**
* Capitalized hex string of the 6 byte / 12 characters device id without
* delimiters. Used as unique id on label, certificate common name, hostname
* etc.
*/
public @NonNull String uniqueId = "";
public @Nullable String uniqueId;
/**
* Increased between firmware versions when api changes. Only apiLevel >= 7 is
* supported.
Expand All @@ -44,17 +46,21 @@ public class HueSyncDeviceInfo {
* User readable version of the device firmware, starting with decimal major
* .minor .maintenance format e.g. “1.12.3”
*/
public String firmwareVersion;
public @Nullable String firmwareVersion;
/**
* Build number of the firmware. Unique for every build with newer builds
* guaranteed a higher number than older.
*/
public int buildNumber;

public boolean termsAgreed;

/** uninitialized, disconnected, lan, wan */
public String wifiState;
public String ipAddress;
public HueSyncDeviceInfoCapabilities capabilities;
public @Nullable String wifiState;
public @Nullable String ipAddress;

public @Nullable HueSyncDeviceInfoCapabilities capabilities;

public boolean beta;
public boolean overheating;
public boolean bluetooth;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@
*/
package org.openhab.binding.huesync.internal.api.dto.registration;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
*
* @author Patrik Gfeller - Initial Contribution
*/
@NonNullByDefault
public class HueSyncRegistration {
public String registrationId;
public String accessToken;
public @Nullable String registrationId;
public @Nullable String accessToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@
*/
package org.openhab.binding.huesync.internal.api.dto.registration;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
*
* @author Patrik Gfeller - Initial Contribution
*/
@NonNullByDefault
public class HueSyncRegistrationRequest {
/** User recognizable name of registered application */
public String appName;
public @Nullable String appName;
/** User recognizable name of application instance. */
public String instanceName;
public @Nullable String instanceName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
Expand All @@ -42,19 +41,19 @@
import org.slf4j.Logger;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* Handles the connection to a Hue HDMI Sync Box using the official API.
*
* @author Patrik Gfeller - Initial Contribution
*/
@NonNullByDefault
public class HueSyncConnection {
pgfeller marked this conversation as resolved.
Show resolved Hide resolved
/**
* Request format: The Sync Box API can be accessed locally via HTTPS on root
* level (port 443, /api/v1), resource level /api/v1/<resource> and in some
* cases subresource level /api/v1/<resource>/<subresource>.
* cases sub-resource level /api/v1/<resource>/<sub-resource>.
*/
private static final String REQUEST_FORMAT = "https://%s:%s/%s/%s";
private static final String API = "api/v1";
Expand All @@ -69,16 +68,15 @@ private static class ENDPOINTS {

private final Logger logger = HueSyncLogFactory.getLogger(HueSyncConnection.class);

private @NonNull HttpClient httpClient;
private @NonNull String host;
private @NonNull Integer port;
private HttpClient httpClient;
private String host;
private Integer port;

private @Nullable String apiAccessToken;
private @Nullable String registrationId;

private ServiceRegistration<?> tlsProviderService;

@NonNullByDefault
public HueSyncConnection(HttpClient httpClient, String host, Integer port, String apiAccessToken,
String registrationId) throws CertificateException, IOException {

Expand All @@ -95,59 +93,57 @@ public HueSyncConnection(HttpClient httpClient, String host, Integer port, Strin
this.httpClient = httpClient;
}

@SuppressWarnings("null")
pgfeller marked this conversation as resolved.
Show resolved Hide resolved
public @Nullable HueSyncDeviceInfo getDeviceInfo() {
HueSyncDeviceInfo deviceInfo = null;

try {
ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE);
ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE);

if (response.getStatus() == HttpStatus.OK_200) {
deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDeviceInfo.class);
}
} catch (InterruptedException | ExecutionException | TimeoutException | JsonProcessingException e) {
this.logger.error("{}", e.getMessage());
if (this.responseReceived(response)) {
deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDeviceInfo.class);
}

return deviceInfo;
}

@SuppressWarnings("null")
public @Nullable HueSyncDetailedDeviceInfo getDetailedDeviceInfo() {
if (!this.isRegistered())
return null;

HueSyncDetailedDeviceInfo deviceInfo = null;

try {
ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE);
ContentResponse response = this.executeGetRequest(ENDPOINTS.DEVICE);

if (response.getStatus() == HttpStatus.OK_200) {
deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDetailedDeviceInfo.class);
}
} catch (InterruptedException | ExecutionException | TimeoutException | JsonProcessingException e) {
this.logger.error("{}", e.getMessage());
if (this.responseReceived(response)) {
deviceInfo = this.deserialize(response.getContentAsString(), HueSyncDetailedDeviceInfo.class);
}

return deviceInfo;
}

public @Nullable HueSyncRegistration registerDevice(String id) {
public @Nullable HueSyncRegistration registerDevice(@Nullable String id) {
HueSyncRegistration registration = null;

try {
HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest();
if (id != null) {
HueSyncRegistrationRequest dto = new HueSyncRegistrationRequest();

dto.appName = HueSyncConstants.APPLICATION_NAME;
dto.instanceName = id;
dto.appName = HueSyncConstants.APPLICATION_NAME;
dto.instanceName = id;

String json = ObjectMapper.writeValueAsString(dto);
String json = ObjectMapper.writeValueAsString(dto);

ContentResponse response = this.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, json);
ContentResponse response = this.executeRequest(HttpMethod.POST, ENDPOINTS.REGISTRATIONS, json);

if (response.getStatus() == HttpStatus.OK_200) {
registration = this.deserialize(response.getContentAsString(), HueSyncRegistration.class);
if (this.responseReceived(response)) {
registration = this.deserialize(response.getContentAsString(), HueSyncRegistration.class);

this.apiAccessToken = registration.accessToken;
this.registrationId = registration.registrationId;
if (registration != null) {
this.apiAccessToken = registration.accessToken;
this.registrationId = registration.registrationId;
}
}
}
} catch (InterruptedException | TimeoutException | ExecutionException | JsonProcessingException e) {
this.logger.error("{}", e.getMessage());
Expand Down Expand Up @@ -182,15 +178,35 @@ public boolean isRegistered() {

// #region - private

private <T> T deserialize(String json, Class<T> type) throws JsonMappingException, JsonProcessingException {
return ObjectMapper.readValue(json, type);
private boolean responseReceived(@Nullable ContentResponse response) {
if (response != null && response.getStatus() == HttpStatus.OK_200) {
return true;
} else {
// TODO: responseReceived - log error details ...
return false;
}
}

private @Nullable <T> T deserialize(String json, Class<T> type) {
try {
return ObjectMapper.readValue(json, type);
} catch (JsonProcessingException | NoClassDefFoundError e) {
this.logger.error("{}", e.getMessage());

return null;
}
}

private ContentResponse executeGetRequest(String endpoint)
throws InterruptedException, ExecutionException, TimeoutException {
private @Nullable ContentResponse executeGetRequest(String endpoint) {
String uri = String.format(REQUEST_FORMAT, this.host, this.port, API, endpoint);

return httpClient.GET(uri);
try {
return httpClient.GET(uri);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
this.logger.error("Endpoint: {} - {}", endpoint, e.getMessage());

return null;
}
}

private ContentResponse executeRequest(HttpMethod method, String endpoint)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
*/
package org.openhab.binding.huesync.internal.exceptions;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;

/**
*
* @author Patrik Gfeller - Initial contribution
*/
@NonNullByDefault
public class HueSyncApiException extends HueSyncException {
public HueSyncApiException(String message, Logger logger) {
super(message, logger);
Expand Down