mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-31 13:34:22 +02:00
[ondilo] Add new Channels and Properties (#18978)
* increased buffer to 3min fixed error with assignment to local instead of global variable Signed-off-by: Michael Weger <weger.michael@gmx.net>
This commit is contained in:
@@ -4,8 +4,8 @@ This binding integrates Ondilo ICO pool monitoring devices with openHAB, allowin
|
||||
|
||||
## Supported Things
|
||||
|
||||
`account:` Represents your Ondilo Account (authentication using OAuth2 flow)
|
||||
`ondilo:` Represents an individual Ondilo ICO device
|
||||
- `account:` Represents your Ondilo Account (authentication using OAuth2 flow)
|
||||
- `ondilo:` Represents an individual Ondilo ICO device
|
||||
|
||||
Ondilo ICO Pool as well as Spa devices are supported.
|
||||
Chlor as well as salt water.
|
||||
@@ -20,7 +20,7 @@ Each Ondilo ICO will appear as a new Thing in the inbox.
|
||||
### `account` Thing Configuration
|
||||
|
||||
- **url**: The URL of the openHAB instance. Required for the redirect during OAuth2 authentication flow (e.g. `http://localhost:8080`)
|
||||
- **refreshInterval**: Polling interval in seconds (default: `900 s`).
|
||||
- **refreshInterval**: Polling interval in seconds (default: `900 s`)
|
||||
|
||||
### `ondilo` Thing Configuration
|
||||
|
||||
@@ -35,8 +35,10 @@ The requests to the Ondilo Customer API are limited to the following per user qu
|
||||
- 5 requests per second
|
||||
- 30 requests per hour
|
||||
|
||||
`account` Thing performs 1 request per cycle - 4 per hour per Ondilo Account with default interval.
|
||||
`ondilo` Thing performs 2 requests per cycle - 8 per hour per Ondilo ICO with default interval.
|
||||
Example using default interval:
|
||||
|
||||
- `account` Thing performs 2 request per cycle - 8 requests per hour per Ondilo Account
|
||||
- `ondilo` Thing performs 4 requests per cycle - 16 requests per hour per Ondilo ICO
|
||||
|
||||
## Channels
|
||||
|
||||
@@ -48,16 +50,21 @@ The requests to the Ondilo Customer API are limited to the following per user qu
|
||||
|
||||
### Measures Channels
|
||||
|
||||
| Channel ID | Type | Advanced | Access | Description |
|
||||
|---------------------------|-------------------------|----------|--------|--------------------------------------------------------|
|
||||
| temperature | Number:Temperature | false | R | Water temperature in the pool |
|
||||
| ph | Number | false | R | pH value of the pool water |
|
||||
| orp | Number:ElectricPotential| false | R | Oxidation-reduction potential (ORP) |
|
||||
| salt | Number:Density | false | R | Salt concentration in the pool (salt pools only) |
|
||||
| tds | Number:Density | false | R | Total dissolved solids in the pool (chlor pools only ) |
|
||||
| battery | Number:Dimensionless | false | R | Battery level of the device |
|
||||
| rssi | Number:Dimensionless | false | R | Signal strength (RSSI) |
|
||||
| value-time | DateTime | true | R | Timestamp of the set of measures |
|
||||
| Channel ID | Type | Advanced | Access | Description |
|
||||
|---------------------------|--------------------------|----------|--------|--------------------------------------------------------|
|
||||
| temperature | Number:Temperature | false | R | Water temperature in the pool |
|
||||
| temperature-trend | Number:Temperature | true | R | Change in water temperature since last measure |
|
||||
| ph | Number | false | R | pH value of the pool water |
|
||||
| ph-trend | Number | true | R | Change in pH value since last measure |
|
||||
| orp | Number:ElectricPotential | false | R | Oxidation-reduction potential (ORP) |
|
||||
| orp-trend | Number:ElectricPotential | true | R | Change in ORP since last measure |
|
||||
| salt | Number:Density | false | R | Salt concentration in the pool (salt pools only) |
|
||||
| salt-trend | Number:Density | true | R | Change in salt concentration since last measure |
|
||||
| tds | Number:Density | false | R | Total dissolved solids in the pool (chlor pools only) |
|
||||
| tds-trend | Number:Density | true | R | Change in TDS since last measure |
|
||||
| battery | Number:Dimensionless | false | R | Battery level of the device |
|
||||
| rssi | Number:Dimensionless | false | R | Signal strength (RSSI) |
|
||||
| value-time | DateTime | true | R | Timestamp of the set of measures |
|
||||
|
||||
### Recommendations Channels
|
||||
|
||||
@@ -71,13 +78,28 @@ The requests to the Ondilo Customer API are limited to the following per user qu
|
||||
| recommendation-status | String | false | R/W | Status of the current recommendation (`waiting`/`ok`)<br/>`sendCommand("ok")` to validate current `waiting` recommendation |
|
||||
| recommendation-deadline | String | true | R | Deadline of the current recommendation |
|
||||
|
||||
### Configuration Channels
|
||||
|
||||
| Channel ID | Type | Advanced | Access | Description |
|
||||
|---------------------------|----------------|----------|--------|-----------------------------------------------|
|
||||
| temperature-low | Number | true | R | Minimum water temperature |
|
||||
| temperature-high | Number | true | R | Maximum water temperature |
|
||||
| ph-low | Number | true | R | Minimum pH value |
|
||||
| ph-high | Number | true | R | Maximum pH value |
|
||||
| orp-low | Number | true | R | Minimum ORP value |
|
||||
| orp-high | Number | true | R | Maximum ORP value |
|
||||
| salt-low | Number | true | R | Minimum salt concentration (salt pools only) |
|
||||
| salt-high | Number | true | R | Maximum salt concentration (salt pools only) |
|
||||
| tds-low | Number | true | R | Minimum TDS value (chlor pools only) |
|
||||
| tds-high | Number | true | R | Maximum TDS value (chlor pools only) |
|
||||
|
||||
## Full Example
|
||||
|
||||
### Thing Configuration
|
||||
|
||||
```Java
|
||||
Bridge ondilo:account:ondiloAccount [ url="http://localhost:8080", refreshInterval=900 ] {
|
||||
Thing ondilo "<id_received_from_discovery>" [ id="<id_received_from_discovery>" ] {
|
||||
Thing ondilo 12345 [ id=12345 ] { // 12345 is an example of the id received via discovery
|
||||
}
|
||||
```
|
||||
|
||||
@@ -124,3 +146,4 @@ sitemap demo label="Ondilo ICO" {
|
||||
|
||||
- [Ondilo API Documentation](https://interop.ondilo.com/docs/api/customer/v1)
|
||||
- [openHAB Community Forum](https://community.openhab.org/t/request-ondilo-binding/98164)
|
||||
- [Pool Monitor Card Widget](https://community.openhab.org/t/pool-monitor-card/164693)
|
||||
|
||||
+24
-30
@@ -31,6 +31,8 @@ import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link OndiloApiClient} for accessing the Ondilo API using OAuth2 authentication.
|
||||
* handlers.
|
||||
@@ -44,6 +46,12 @@ public class OndiloApiClient {
|
||||
private @Nullable String bearer;
|
||||
private @Nullable AccessTokenResponse accessTokenResponse;
|
||||
private static final String ONDILO_API_URL = "https://interop.ondilo.com/api/customer/v1";
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
private static long lastRequestTime = 0;
|
||||
// Minimum interval between requests in milliseconds
|
||||
// Enforce API rate limit: 5 requests max per 1 second
|
||||
private static final long MIN_REQUEST_INTERVAL_MS = 200;
|
||||
|
||||
public OndiloApiClient(OAuthClientService oAuthService, AccessTokenResponse accessTokenResponse) {
|
||||
this.oAuthService = oAuthService;
|
||||
@@ -53,50 +61,36 @@ public class OndiloApiClient {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized String get(String endpoint) {
|
||||
try {
|
||||
refreshAccessTokenIfNeeded();
|
||||
URL url = new URI(ONDILO_API_URL + endpoint).toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Authorization", "Bearer " + bearer);
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("Accept-Charset", "utf-8");
|
||||
conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
|
||||
conn.connect();
|
||||
int responseCode = conn.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
try (InputStream is = conn.getInputStream(); Scanner scanner = new Scanner(is, "UTF-8")) {
|
||||
return scanner.useDelimiter("\\A").next();
|
||||
}
|
||||
} else {
|
||||
logger.warn("Ondilo API request failed with code: {}", responseCode);
|
||||
public synchronized <T> T request(String requestMethod, String endpoint, Class<T> type) {
|
||||
// Enforce API rate limit: at least 200ms between requests
|
||||
long now = System.currentTimeMillis();
|
||||
long wait = lastRequestTime + MIN_REQUEST_INTERVAL_MS - now;
|
||||
if (wait > 0) {
|
||||
try {
|
||||
Thread.sleep(wait);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
logger.debug("API request pause interrupted: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
logger.debug("Ondilo API request interrupted: {}", e.getMessage());
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
logger.warn("Ondilo API request error", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized String put(String endpoint) {
|
||||
try {
|
||||
refreshAccessTokenIfNeeded();
|
||||
URL url = new URI(ONDILO_API_URL + endpoint).toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("PUT");
|
||||
conn.setRequestMethod(requestMethod);
|
||||
conn.setRequestProperty("Authorization", "Bearer " + bearer);
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("Accept-Charset", "utf-8");
|
||||
conn.setRequestProperty("Accept-Encoding", "gzip, deflate");
|
||||
conn.connect();
|
||||
lastRequestTime = System.currentTimeMillis();
|
||||
int responseCode = conn.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
try (InputStream is = conn.getInputStream(); Scanner scanner = new Scanner(is, "UTF-8")) {
|
||||
return scanner.useDelimiter("\\A").next();
|
||||
String response = scanner.useDelimiter("\\A").next();
|
||||
// Parse JSON to DTO
|
||||
return GSON.fromJson(response, type);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Ondilo API request failed with code: {}", responseCode);
|
||||
|
||||
+32
-7
@@ -30,26 +30,37 @@ public class OndiloBindingConstants {
|
||||
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "account");
|
||||
public static final ThingTypeUID THING_TYPE_ONDILO = new ThingTypeUID(BINDING_ID, "ondilo");
|
||||
|
||||
// Bridge Properties
|
||||
public static final String PROPERTY_BRIDGE_USER_INFO = "userInfo";
|
||||
|
||||
// Bridge Channel ids
|
||||
public static final String CHANNEL_POLL_UPDATE = "poll-update";
|
||||
|
||||
// Ondilo Thing Properties
|
||||
public static final String ONDILO_ID = "id";
|
||||
public static final String ONDILO_NAME = "name";
|
||||
public static final String ONDILO_TYPE = "type";
|
||||
public static final String ONDILO_VOLUME = "volume";
|
||||
public static final String ONDILO_DISINFECTION = "disinfection";
|
||||
public static final String ONDILO_ADDRESS = "address";
|
||||
public static final String ONDILO_LOCATION = "location";
|
||||
public static final String PROPERTY_ONDILO_ID = "id";
|
||||
public static final String PROPERTY_ONDILO_NAME = "name";
|
||||
public static final String PROPERTY_ONDILO_TYPE = "type";
|
||||
public static final String PROPERTY_ONDILO_VOLUME = "volume";
|
||||
public static final String PROPERTY_ONDILO_DISINFECTION = "disinfection";
|
||||
public static final String PROPERTY_ONDILO_ADDRESS = "address";
|
||||
public static final String PROPERTY_ONDILO_LOCATION = "location";
|
||||
public static final String PROPERTY_ONDILO_INFO_UUID = "uuid";
|
||||
public static final String PROPERTY_ONDILO_INFO_POOL_GUY_NUMBER = "poolGuyNumber";
|
||||
public static final String PROPERTY_ONDILO_INFO_MAINTENANCE_DAY = "maintenanceDay";
|
||||
|
||||
// Ondilo Thing Measures Channel ids
|
||||
public static final String GROUP_MEASURES = "measure#";
|
||||
|
||||
public static final String CHANNEL_TEMPERATURE = GROUP_MEASURES + "temperature";
|
||||
public static final String CHANNEL_TEMPERATURE_TREND = GROUP_MEASURES + "temperature-trend";
|
||||
public static final String CHANNEL_PH = GROUP_MEASURES + "ph";
|
||||
public static final String CHANNEL_PH_TREND = GROUP_MEASURES + "ph-trend";
|
||||
public static final String CHANNEL_ORP = GROUP_MEASURES + "orp";
|
||||
public static final String CHANNEL_ORP_TREND = GROUP_MEASURES + "orp-trend";
|
||||
public static final String CHANNEL_SALT = GROUP_MEASURES + "salt";
|
||||
public static final String CHANNEL_SALT_TREND = GROUP_MEASURES + "salt-trend";
|
||||
public static final String CHANNEL_TDS = GROUP_MEASURES + "tds";
|
||||
public static final String CHANNEL_TDS_TREND = GROUP_MEASURES + "tds-trend";
|
||||
public static final String CHANNEL_BATTERY = GROUP_MEASURES + "battery";
|
||||
public static final String CHANNEL_RSSI = GROUP_MEASURES + "rssi";
|
||||
public static final String CHANNEL_VALUE_TIME = GROUP_MEASURES + "value-time";
|
||||
@@ -65,6 +76,20 @@ public class OndiloBindingConstants {
|
||||
public static final String CHANNEL_RECOMMENDATION_STATUS = GROUP_RECOMMENDATIONS + "status";
|
||||
public static final String CHANNEL_RECOMMENDATION_DEADLINE = GROUP_RECOMMENDATIONS + "deadline";
|
||||
|
||||
// Ondilo Thing Configuration Channel ids
|
||||
public static final String GROUP_CONFIGURATION = "configuration#";
|
||||
|
||||
public static final String CHANNEL_CONFIGURATION_TEMPERATURE_LOW = GROUP_CONFIGURATION + "temperature-low";
|
||||
public static final String CHANNEL_CONFIGURATION_TEMPERATURE_HIGH = GROUP_CONFIGURATION + "temperature-high";
|
||||
public static final String CHANNEL_CONFIGURATION_PH_LOW = GROUP_CONFIGURATION + "ph-low";
|
||||
public static final String CHANNEL_CONFIGURATION_PH_HIGH = GROUP_CONFIGURATION + "ph-high";
|
||||
public static final String CHANNEL_CONFIGURATION_ORP_LOW = GROUP_CONFIGURATION + "orp-low";
|
||||
public static final String CHANNEL_CONFIGURATION_ORP_HIGH = GROUP_CONFIGURATION + "orp-high";
|
||||
public static final String CHANNEL_CONFIGURATION_SALT_LOW = GROUP_CONFIGURATION + "salt-low";
|
||||
public static final String CHANNEL_CONFIGURATION_SALT_HIGH = GROUP_CONFIGURATION + "salt-high";
|
||||
public static final String CHANNEL_CONFIGURATION_TDS_LOW = GROUP_CONFIGURATION + "tds-low";
|
||||
public static final String CHANNEL_CONFIGURATION_TDS_HIGH = GROUP_CONFIGURATION + "tds-high";
|
||||
|
||||
// I18N keys for state details
|
||||
public static final String I18N_URL_INVALID = "@text/thing.ondilo.bridge.config.url.invalid";
|
||||
public static final String I18N_OAUTH2_PENDING = "@text/thing.ondilo.bridge.config.oauth2.pending";
|
||||
|
||||
+45
-45
@@ -14,7 +14,7 @@ package org.openhab.binding.ondilo.internal;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -26,15 +26,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ondilo.internal.dto.LastMeasure;
|
||||
import org.openhab.binding.ondilo.internal.dto.Pool;
|
||||
import org.openhab.binding.ondilo.internal.dto.PoolConfiguration;
|
||||
import org.openhab.binding.ondilo.internal.dto.PoolInfo;
|
||||
import org.openhab.binding.ondilo.internal.dto.Recommendation;
|
||||
import org.openhab.binding.ondilo.internal.dto.UserInfo;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* The {@link OndiloBridge} handles OAuth2 authentication for Ondilo API.
|
||||
*
|
||||
@@ -45,6 +45,7 @@ public class OndiloBridge {
|
||||
private final Logger logger = LoggerFactory.getLogger(OndiloBridge.class);
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final int refreshInterval;
|
||||
private final OndiloBridgeHandler bridgeHandler;
|
||||
private @Nullable ScheduledFuture<?> ondiloBridgePollingJob;
|
||||
private @Nullable List<Pool> pools;
|
||||
public @Nullable OndiloApiClient apiClient;
|
||||
@@ -54,10 +55,11 @@ public class OndiloBridge {
|
||||
// last measure. For whatever reason it takes a bit longer for the measures to finally reach the cloud and be
|
||||
// available via API. Therefore we add a buffer of 4 minutes to the next polling time.
|
||||
// If the last measure was taken at 12:00, we will poll again at 13:04.
|
||||
private static final int DEFAULT_REFRESH_INTERVAL = 64;
|
||||
private static final Duration TARGET_REFRESH_INTERVAL = Duration.ofMinutes(60 + 4);
|
||||
|
||||
public OndiloBridge(OndiloBridgeHandler bridgeHandler, OAuthClientService oAuthService,
|
||||
AccessTokenResponse accessTokenResponse, int refreshInterval, ScheduledExecutorService scheduler) {
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
this.scheduler = scheduler;
|
||||
this.refreshInterval = refreshInterval;
|
||||
|
||||
@@ -81,7 +83,7 @@ public class OndiloBridge {
|
||||
|
||||
private void startOndiloBridgePolling(Integer refreshInterval) {
|
||||
if (ondiloBridgePollingJob == null) {
|
||||
ondiloBridgePollingJob = scheduler.scheduleWithFixedDelay(() -> pollOndiloICOs(), 1, refreshInterval,
|
||||
ondiloBridgePollingJob = scheduler.scheduleWithFixedDelay(() -> pollOndiloICOs(), 10, refreshInterval,
|
||||
TimeUnit.SECONDS);
|
||||
} else {
|
||||
logger.warn("Ondilo bridge polling job is already running, not starting a new one");
|
||||
@@ -100,28 +102,20 @@ public class OndiloBridge {
|
||||
try {
|
||||
OndiloApiClient apiClient = this.apiClient;
|
||||
if (apiClient != null) {
|
||||
String poolsJson = apiClient.get("/pools");
|
||||
logger.trace("Ondilo ICOs: {}", poolsJson);
|
||||
// Parse JSON to DTO
|
||||
Gson gson = new Gson();
|
||||
List<Pool> pools = gson.fromJson(poolsJson, new TypeToken<List<Pool>>() {
|
||||
}.getType());
|
||||
if (pools != null && !pools.isEmpty()) {
|
||||
logger.trace("Polled {} Ondilo ICOs", pools.size());
|
||||
UserInfo userInfo = apiClient.request("GET", "/user/info", UserInfo.class);
|
||||
if (userInfo != null) {
|
||||
bridgeHandler.updateUserInfo(userInfo);
|
||||
}
|
||||
|
||||
Pool pools[] = apiClient.request("GET", "/pools", Pool[].class);
|
||||
if (pools != null && !(pools.length == 0)) {
|
||||
logger.trace("Polled {} Ondilo ICOs", pools.length);
|
||||
// Poll last measures and recommendations for each pool
|
||||
Instant lastValueTime = null;
|
||||
for (Pool pool : pools) {
|
||||
try {
|
||||
// Pause for 1 second between polls in order to keep API rate limits
|
||||
Thread.sleep(1000);
|
||||
Instant valueTime = pollOndiloICO(pool.id);
|
||||
if (lastValueTime == null || (valueTime != null && valueTime.isBefore(lastValueTime))) {
|
||||
lastValueTime = valueTime;
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
logger.warn("Polling pause interrupted: {}", ie.getMessage());
|
||||
break;
|
||||
Instant valueTime = pollOndiloICO(pool.id);
|
||||
if (lastValueTime == null || (valueTime != null && valueTime.isBefore(lastValueTime))) {
|
||||
lastValueTime = valueTime;
|
||||
}
|
||||
}
|
||||
if (lastValueTime != null) {
|
||||
@@ -130,7 +124,8 @@ public class OndiloBridge {
|
||||
} else {
|
||||
logger.warn("No Ondilo ICO found or failed to parse JSON response");
|
||||
}
|
||||
this.pools = pools;
|
||||
// Store the pools for later access
|
||||
this.pools = Arrays.asList(pools);
|
||||
} else {
|
||||
logger.warn("OndiloApiClient is not initialized, cannot poll Ondilo ICOs");
|
||||
}
|
||||
@@ -142,7 +137,7 @@ public class OndiloBridge {
|
||||
private void adaptPollingToValueTime(Instant lastValueTime, int refreshInterval) {
|
||||
// Adjusting the polling reduces the delay when new measures get available, without polling too frequently and
|
||||
// hitting API rate limits.
|
||||
Instant nextValueTime = lastValueTime.plus(DEFAULT_REFRESH_INTERVAL, ChronoUnit.MINUTES);
|
||||
Instant nextValueTime = lastValueTime.plus(TARGET_REFRESH_INTERVAL);
|
||||
Instant now = Instant.now();
|
||||
Instant scheduledTime = now.plusSeconds(refreshInterval);
|
||||
if (nextValueTime.isBefore(scheduledTime)) {
|
||||
@@ -164,15 +159,11 @@ public class OndiloBridge {
|
||||
OndiloApiClient apiClient = this.apiClient;
|
||||
Instant lastValueTime = null;
|
||||
if (ondiloHandler != null && apiClient != null) {
|
||||
String poolsJson = apiClient.get("/pools/" + id
|
||||
+ "/lastmeasures?types[]=temperature&types[]=ph&types[]=orp&types[]=salt&types[]=tds&types[]=battery&types[]=rssi");
|
||||
logger.trace("LastMeasures: {}", poolsJson);
|
||||
// Parse JSON to DTO
|
||||
Gson gson = new Gson();
|
||||
List<LastMeasure> lastMeasures = gson.fromJson(poolsJson, new TypeToken<List<LastMeasure>>() {
|
||||
}.getType());
|
||||
LastMeasure[] lastMeasures = apiClient.request("GET", "/pools/" + id
|
||||
+ "/lastmeasures?types[]=temperature&types[]=ph&types[]=orp&types[]=salt&types[]=tds&types[]=battery&types[]=rssi",
|
||||
LastMeasure[].class);
|
||||
|
||||
if (lastMeasures == null || lastMeasures.isEmpty()) {
|
||||
if (lastMeasures == null || lastMeasures.length == 0) {
|
||||
logger.warn("No lastMeasures available for Ondilo ICO with ID: {}", id);
|
||||
ondiloHandler.clearLastMeasuresChannels();
|
||||
} else {
|
||||
@@ -185,14 +176,10 @@ public class OndiloBridge {
|
||||
}
|
||||
}
|
||||
|
||||
String recommendationsJson = apiClient.get("/pools/" + id + "/recommendations");
|
||||
logger.trace("recommendations: {}", recommendationsJson);
|
||||
// Parse JSON to DTO
|
||||
List<Recommendation> recommendations = gson.fromJson(recommendationsJson,
|
||||
new TypeToken<List<Recommendation>>() {
|
||||
}.getType());
|
||||
Recommendation[] recommendations = apiClient.request("GET", "/pools/" + id + "/recommendations",
|
||||
Recommendation[].class);
|
||||
|
||||
if (recommendations == null || recommendations.isEmpty()) {
|
||||
if (recommendations == null || recommendations.length == 0) {
|
||||
logger.trace("No Recommendations available for Ondilo ICO with ID: {}", id);
|
||||
ondiloHandler.clearRecommendationChannels();
|
||||
} else {
|
||||
@@ -209,11 +196,23 @@ public class OndiloBridge {
|
||||
waitingRecommendation.title);
|
||||
recommendation = waitingRecommendation;
|
||||
} else {
|
||||
recommendation = recommendations.get(0);
|
||||
recommendation = recommendations[0];
|
||||
logger.trace("Latest Recommentation: id={}, title={}", recommendation.id, recommendation.title);
|
||||
}
|
||||
ondiloHandler.updateRecommendationChannels(recommendation);
|
||||
}
|
||||
|
||||
PoolInfo poolInfo = apiClient.request("GET", "/pools/" + id + "/device", PoolInfo.class);
|
||||
if (poolInfo != null) {
|
||||
ondiloHandler.updatePoolInfo(poolInfo);
|
||||
}
|
||||
|
||||
PoolConfiguration poolConfiguration = apiClient.request("GET", "/pools/" + id + "/configuration",
|
||||
PoolConfiguration.class);
|
||||
if (poolConfiguration != null) {
|
||||
ondiloHandler.updatePoolConfiguration(poolConfiguration);
|
||||
ondiloHandler.updatePoolInfo(poolConfiguration);
|
||||
}
|
||||
} else {
|
||||
logger.debug("No OndiloHandler found for Ondilo ICO with ID: {}", id);
|
||||
}
|
||||
@@ -223,8 +222,9 @@ public class OndiloBridge {
|
||||
public void validateRecommendation(int poolId, int recommendationId) {
|
||||
OndiloApiClient apiClient = this.apiClient;
|
||||
if (apiClient != null) {
|
||||
String response = apiClient.put("/pools/" + poolId + "/recommendations/" + recommendationId);
|
||||
if (response != null && "\"Done\"".equals(response.trim())) {
|
||||
String response = apiClient.request("PUT", "/pools/" + poolId + "/recommendations/" + recommendationId,
|
||||
String.class);
|
||||
if (response != null && "Done".equals(response.trim())) {
|
||||
logger.trace("Validated Recommendation successfully");
|
||||
} else {
|
||||
logger.warn("Failed to validate Recommendation, API response: {}", response);
|
||||
|
||||
+8
@@ -19,12 +19,14 @@ import java.io.InterruptedIOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ondilo.internal.dto.Pool;
|
||||
import org.openhab.binding.ondilo.internal.dto.UserInfo;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||
@@ -189,6 +191,12 @@ public class OndiloBridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateUserInfo(UserInfo userInfo) {
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(PROPERTY_BRIDGE_USER_INFO, userInfo.getUserInfo());
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
String clientSecret = thing.getUID().getAsString();
|
||||
|
||||
+115
-24
@@ -17,14 +17,21 @@ import static org.openhab.binding.ondilo.internal.OndiloBindingConstants.*;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ondilo.internal.dto.LastMeasure;
|
||||
import org.openhab.binding.ondilo.internal.dto.Pool;
|
||||
import org.openhab.binding.ondilo.internal.dto.PoolConfiguration;
|
||||
import org.openhab.binding.ondilo.internal.dto.PoolInfo;
|
||||
import org.openhab.binding.ondilo.internal.dto.Recommendation;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
@@ -52,15 +59,28 @@ import org.slf4j.LoggerFactory;
|
||||
@NonNullByDefault
|
||||
public class OndiloHandler extends BaseThingHandler {
|
||||
private static final String NO_ID = "NO_ID";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OndiloHandler.class);
|
||||
private final LocaleProvider localeProvider;
|
||||
private final int configPoolId;
|
||||
|
||||
private int recommendationId; // Used to track the last recommendation ID processed
|
||||
private AtomicReference<String> ondiloId = new AtomicReference<>(NO_ID);
|
||||
private final int configPoolId;
|
||||
|
||||
private @Nullable ScheduledFuture<?> bridgeRecoveryJob;
|
||||
|
||||
public OndiloHandler(Thing thing) {
|
||||
// Store last value and valueTime for trend calculation
|
||||
private OndiloMeasureState lastTemperatureState = new OndiloMeasureState(Double.NaN, null);
|
||||
private OndiloMeasureState lastPhState = new OndiloMeasureState(Double.NaN, null);
|
||||
private OndiloMeasureState lastOrpState = new OndiloMeasureState(Double.NaN, null);
|
||||
private OndiloMeasureState lastSaltState = new OndiloMeasureState(Double.NaN, null);
|
||||
private OndiloMeasureState lastTdsState = new OndiloMeasureState(Double.NaN, null);
|
||||
|
||||
public OndiloHandler(Thing thing, LocaleProvider localeProvider) {
|
||||
super(thing);
|
||||
|
||||
this.localeProvider = localeProvider;
|
||||
|
||||
OndiloConfiguration currentConfig = getConfigAs(OndiloConfiguration.class);
|
||||
this.configPoolId = currentConfig.id;
|
||||
}
|
||||
@@ -169,45 +189,69 @@ public class OndiloHandler extends BaseThingHandler {
|
||||
this.recommendationId = 0; // Reset last processed recommendation ID
|
||||
}
|
||||
|
||||
public Instant updateLastMeasuresChannels(LastMeasure lastMeasures) {
|
||||
/*
|
||||
* The measures are received using the following units:
|
||||
* - Temperature: Celsius degrees (°C)
|
||||
* - ORP: millivolts (mV)
|
||||
* - Salt: milligrams per liter (mg/l)
|
||||
* - TDS: parts per million (ppm)
|
||||
* - Battery and RSSI: percent (%)
|
||||
*/
|
||||
switch (lastMeasures.dataType) {
|
||||
private void updateTrendChannel(String channel, String trendChannel, double value, Instant valueTime,
|
||||
OndiloMeasureState lastMeasureState, Object unitOrType) {
|
||||
Instant lastMeasureTime = lastMeasureState.time;
|
||||
if (lastMeasureTime != null) {
|
||||
if (!valueTime.equals(lastMeasureTime)) {
|
||||
double delta = value - lastMeasureState.value;
|
||||
if (unitOrType instanceof Unit<?> unit) {
|
||||
updateState(trendChannel, new QuantityType<>(delta, (Unit<?>) unit));
|
||||
} else { // DecimalType
|
||||
updateState(trendChannel, new DecimalType(delta));
|
||||
}
|
||||
logger.trace(
|
||||
"channel: {}, trendChannel: {}, value: {}, valueTime: {}, lastValue: {}, lastValueTime: {}, unitOrType: {} ==> delta: {}",
|
||||
channel, trendChannel, value, valueTime.toString(), lastMeasureState.value,
|
||||
lastMeasureTime.toString(), unitOrType.toString(), delta);
|
||||
} // else: keep current value
|
||||
} else {
|
||||
updateState(trendChannel, UnDefType.UNDEF);
|
||||
}
|
||||
// Update the current value channel
|
||||
if (unitOrType instanceof Unit<?> unit) {
|
||||
updateState(channel, new QuantityType<>(value, (Unit<?>) unit));
|
||||
} else { // DecimalType
|
||||
updateState(channel, new DecimalType(value));
|
||||
}
|
||||
|
||||
lastMeasureState.value = value;
|
||||
lastMeasureState.time = valueTime;
|
||||
}
|
||||
|
||||
public Instant updateLastMeasuresChannels(LastMeasure measure) {
|
||||
Instant valueTime = parseUtcTimeToInstant(measure.valueTime);
|
||||
switch (measure.dataType) {
|
||||
case "temperature":
|
||||
updateState(CHANNEL_TEMPERATURE, new QuantityType<>(lastMeasures.value, SIUnits.CELSIUS));
|
||||
updateTrendChannel(CHANNEL_TEMPERATURE, CHANNEL_TEMPERATURE_TREND, measure.value, valueTime,
|
||||
lastTemperatureState, SIUnits.CELSIUS);
|
||||
break;
|
||||
case "ph":
|
||||
updateState(CHANNEL_PH, new DecimalType(lastMeasures.value));
|
||||
updateTrendChannel(CHANNEL_PH, CHANNEL_PH_TREND, measure.value, valueTime, lastPhState,
|
||||
DecimalType.class);
|
||||
break;
|
||||
case "orp":
|
||||
updateState(CHANNEL_ORP, new QuantityType<>(lastMeasures.value / 1000.0, Units.VOLT));
|
||||
// Convert mV to V
|
||||
updateTrendChannel(CHANNEL_ORP, CHANNEL_ORP_TREND, measure.value / 1000.0, valueTime, lastOrpState,
|
||||
Units.VOLT); // Convert mV to V
|
||||
break;
|
||||
case "salt":
|
||||
updateState(CHANNEL_SALT,
|
||||
new QuantityType<>(lastMeasures.value * 0.001, Units.KILOGRAM_PER_CUBICMETRE));
|
||||
// Convert mg/l to kg/m³
|
||||
updateTrendChannel(CHANNEL_SALT, CHANNEL_SALT_TREND, measure.value * 0.001, valueTime, lastSaltState,
|
||||
Units.KILOGRAM_PER_CUBICMETRE); // Convert mg/l to kg/m³
|
||||
break;
|
||||
case "tds":
|
||||
updateState(CHANNEL_TDS, new QuantityType<>(lastMeasures.value, Units.PARTS_PER_MILLION));
|
||||
updateTrendChannel(CHANNEL_TDS, CHANNEL_TDS_TREND, measure.value, valueTime, lastTdsState,
|
||||
Units.PARTS_PER_MILLION);
|
||||
break;
|
||||
case "battery":
|
||||
updateState(CHANNEL_BATTERY, new QuantityType<>(lastMeasures.value, Units.PERCENT));
|
||||
updateState(CHANNEL_BATTERY, new QuantityType<>(measure.value, Units.PERCENT));
|
||||
break;
|
||||
case "rssi":
|
||||
updateState(CHANNEL_RSSI, new DecimalType(lastMeasures.value));
|
||||
updateState(CHANNEL_RSSI, new DecimalType(measure.value));
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unknown data type: {}", lastMeasures.dataType);
|
||||
logger.warn("Unknown data type: {}", measure.dataType);
|
||||
}
|
||||
// Update value time channel (expect that it is the same for all measures)
|
||||
Instant valueTime = parseUtcTimeToInstant(lastMeasures.valueTime);
|
||||
updateState(CHANNEL_VALUE_TIME, new DateTimeType(valueTime));
|
||||
return valueTime;
|
||||
}
|
||||
@@ -223,6 +267,53 @@ public class OndiloHandler extends BaseThingHandler {
|
||||
this.recommendationId = recommendation.id; // Update last processed recommendation ID
|
||||
}
|
||||
|
||||
public void updatePool(Pool pool) {
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(PROPERTY_ONDILO_ID, String.valueOf(pool.id));
|
||||
properties.put(PROPERTY_ONDILO_NAME, pool.name);
|
||||
properties.put(PROPERTY_ONDILO_TYPE, pool.type);
|
||||
properties.put(PROPERTY_ONDILO_VOLUME, pool.getVolume());
|
||||
properties.put(PROPERTY_ONDILO_DISINFECTION, pool.getDisinfection());
|
||||
properties.put(PROPERTY_ONDILO_ADDRESS, pool.getAddress());
|
||||
properties.put(PROPERTY_ONDILO_LOCATION, pool.getLocation());
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
public void updatePoolInfo(PoolInfo poolInfo) {
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(PROPERTY_ONDILO_INFO_UUID, poolInfo.uuid);
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, poolInfo.serialNumber);
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, poolInfo.swVersion);
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
public void updatePoolInfo(PoolConfiguration poolConfiguration) {
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(PROPERTY_ONDILO_INFO_POOL_GUY_NUMBER, poolConfiguration.poolGuyNumber);
|
||||
properties.put(PROPERTY_ONDILO_INFO_MAINTENANCE_DAY,
|
||||
poolConfiguration.getMaintenanceDay(localeProvider.getLocale()));
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
public void updatePoolConfiguration(PoolConfiguration poolConfiguration) {
|
||||
updateState(CHANNEL_CONFIGURATION_TEMPERATURE_LOW,
|
||||
new QuantityType<>(poolConfiguration.temperatureLow, SIUnits.CELSIUS));
|
||||
updateState(CHANNEL_CONFIGURATION_TEMPERATURE_HIGH,
|
||||
new QuantityType<>(poolConfiguration.temperatureHigh, SIUnits.CELSIUS));
|
||||
updateState(CHANNEL_CONFIGURATION_PH_LOW, new DecimalType(poolConfiguration.phLow));
|
||||
updateState(CHANNEL_CONFIGURATION_PH_HIGH, new DecimalType(poolConfiguration.phHigh));
|
||||
updateState(CHANNEL_CONFIGURATION_ORP_LOW, new QuantityType<>(poolConfiguration.orpLow / 1000.0, Units.VOLT));
|
||||
updateState(CHANNEL_CONFIGURATION_ORP_HIGH, new QuantityType<>(poolConfiguration.orpHigh / 1000.0, Units.VOLT));
|
||||
updateState(CHANNEL_CONFIGURATION_SALT_LOW,
|
||||
new QuantityType<>(poolConfiguration.saltLow * 0.001, Units.KILOGRAM_PER_CUBICMETRE));
|
||||
updateState(CHANNEL_CONFIGURATION_SALT_HIGH,
|
||||
new QuantityType<>(poolConfiguration.saltHigh * 0.001, Units.KILOGRAM_PER_CUBICMETRE));
|
||||
updateState(CHANNEL_CONFIGURATION_TDS_LOW,
|
||||
new QuantityType<>(poolConfiguration.tdsLow, Units.PARTS_PER_MILLION));
|
||||
updateState(CHANNEL_CONFIGURATION_TDS_HIGH,
|
||||
new QuantityType<>(poolConfiguration.tdsHigh, Units.PARTS_PER_MILLION));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private OndiloBridge getOndiloBridge() {
|
||||
if (getBridge() instanceof Bridge bridge && bridge.getHandler() instanceof OndiloBridgeHandler bridgeHandler) {
|
||||
|
||||
+5
-2
@@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ondilo.internal.discovery.OndiloDiscoveryService;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
@@ -46,13 +47,15 @@ import org.slf4j.LoggerFactory;
|
||||
public class OndiloHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final Logger logger = LoggerFactory.getLogger(OndiloHandlerFactory.class);
|
||||
private final OAuthFactory oAuthFactory;
|
||||
private final LocaleProvider localeProvider;
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_ONDILO);
|
||||
private @Nullable ServiceRegistration<?> ondiloDiscoveryServiceRegistration;
|
||||
private @Nullable OndiloDiscoveryService discoveryService;
|
||||
|
||||
@Activate
|
||||
public OndiloHandlerFactory(@Reference OAuthFactory oAuthFactory) {
|
||||
public OndiloHandlerFactory(@Reference OAuthFactory oAuthFactory, @Reference LocaleProvider localeProvider) {
|
||||
this.oAuthFactory = oAuthFactory;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,7 +72,7 @@ public class OndiloHandlerFactory extends BaseThingHandlerFactory {
|
||||
registerOndiloDiscoveryService(handler);
|
||||
return handler;
|
||||
} else if (THING_TYPE_ONDILO.equals(thingTypeUID)) {
|
||||
return new OndiloHandler(thing);
|
||||
return new OndiloHandler(thing, localeProvider);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.ondilo.internal;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link OndiloMeasureState} store last value and valueTime for trend calculation.
|
||||
*
|
||||
* @author MikeTheTux - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OndiloMeasureState {
|
||||
public double value;
|
||||
public @Nullable Instant time;
|
||||
|
||||
public OndiloMeasureState(double value, @Nullable Instant time) {
|
||||
this.value = value;
|
||||
this.time = time;
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -96,16 +96,16 @@ public class OndiloDiscoveryService extends AbstractDiscoveryService {
|
||||
ThingUID poolThingUid = new ThingUID(THING_TYPE_ONDILO, bridgeUID, String.valueOf(pool.id));
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(ONDILO_ID, pool.id);
|
||||
properties.put(ONDILO_NAME, pool.name);
|
||||
properties.put(ONDILO_TYPE, pool.type);
|
||||
properties.put(ONDILO_VOLUME, pool.getVolume());
|
||||
properties.put(ONDILO_DISINFECTION, pool.getDisinfection());
|
||||
properties.put(ONDILO_ADDRESS, pool.getAddress());
|
||||
properties.put(ONDILO_LOCATION, pool.getLocation());
|
||||
properties.put(PROPERTY_ONDILO_ID, pool.id);
|
||||
properties.put(PROPERTY_ONDILO_NAME, pool.name);
|
||||
properties.put(PROPERTY_ONDILO_TYPE, pool.type);
|
||||
properties.put(PROPERTY_ONDILO_VOLUME, pool.getVolume());
|
||||
properties.put(PROPERTY_ONDILO_DISINFECTION, pool.getDisinfection());
|
||||
properties.put(PROPERTY_ONDILO_ADDRESS, pool.getAddress());
|
||||
properties.put(PROPERTY_ONDILO_LOCATION, pool.getLocation());
|
||||
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(poolThingUid).withThingType(thingTypeUID)
|
||||
.withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(ONDILO_ID)
|
||||
.withProperties(properties).withBridge(bridgeUID).withRepresentationProperty(PROPERTY_ONDILO_ID)
|
||||
.withLabel("Ondilo ICO: " + pool.name).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.ondilo.internal.dto;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.format.TextStyle;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link PoolConfiguration} DTO for representing Ondilo pool configurations.
|
||||
*
|
||||
* @author MikeTheTux - Initial contribution
|
||||
*/
|
||||
public class PoolConfiguration {
|
||||
/*
|
||||
* Example JSON representation:
|
||||
* {
|
||||
* "temperature_low": 10,
|
||||
* "temperature_high": 30,
|
||||
* "ph_low": 7.6,
|
||||
* "ph_high": 8.5,
|
||||
* "orp_low": 400,
|
||||
* "orp_high": 900,
|
||||
* "salt_low": 3000,
|
||||
* "salt_high": 5000,
|
||||
* "tds_low": 250,
|
||||
* "tds_high": 2000,
|
||||
* "pool_guy_number": "0123456789",
|
||||
* "maintenance_day": 2
|
||||
* }
|
||||
*/
|
||||
@SerializedName("temperature_low")
|
||||
public int temperatureLow;
|
||||
|
||||
@SerializedName("temperature_high")
|
||||
public int temperatureHigh;
|
||||
|
||||
@SerializedName("ph_low")
|
||||
public double phLow;
|
||||
|
||||
@SerializedName("ph_high")
|
||||
public double phHigh;
|
||||
|
||||
@SerializedName("orp_low")
|
||||
public int orpLow;
|
||||
|
||||
@SerializedName("orp_high")
|
||||
public int orpHigh;
|
||||
|
||||
@SerializedName("salt_low")
|
||||
public int saltLow;
|
||||
|
||||
@SerializedName("salt_high")
|
||||
public int saltHigh;
|
||||
|
||||
@SerializedName("tds_low")
|
||||
public int tdsLow;
|
||||
|
||||
@SerializedName("tds_high")
|
||||
public int tdsHigh;
|
||||
|
||||
@SerializedName("pool_guy_number")
|
||||
public String poolGuyNumber;
|
||||
|
||||
@SerializedName("maintenance_day")
|
||||
public int maintenanceDay;
|
||||
|
||||
public String getMaintenanceDay(Locale locale) {
|
||||
DayOfWeek dayOfWeek = DayOfWeek.of((maintenanceDay % 7) + 1); // Change from week start Sunday to Monday
|
||||
return dayOfWeek.getDisplayName(TextStyle.FULL, locale);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.ondilo.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link PoolInfo} DTO for representing Ondilo pool infos.
|
||||
*
|
||||
* @author MikeTheTux - Initial contribution
|
||||
*/
|
||||
public class PoolInfo {
|
||||
/*
|
||||
* Example JSON representation:
|
||||
* {
|
||||
* "uuid": "1234567890ABCDEF",
|
||||
* "serial_number": "SN00001",
|
||||
* "sw_version": "1.5.1-stable"
|
||||
* }
|
||||
*/
|
||||
public String uuid;
|
||||
@SerializedName("serial_number")
|
||||
public String serialNumber;
|
||||
@SerializedName("sw_version")
|
||||
public String swVersion;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.ondilo.internal.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link UserInfo} DTO for representing Ondilo user infos.
|
||||
*
|
||||
* @author MikeTheTux - Initial contribution
|
||||
*/
|
||||
public class UserInfo {
|
||||
/*
|
||||
* Example JSON representation:
|
||||
* {
|
||||
* "lastname": "Doe",
|
||||
* "firstname": "John",
|
||||
* "email": "john@doe.org"
|
||||
* }
|
||||
*/
|
||||
@SerializedName("lastname")
|
||||
public String lastName;
|
||||
@SerializedName("firstname")
|
||||
public String firstName;
|
||||
public String email;
|
||||
|
||||
public String getUserInfo() {
|
||||
return lastName + " " + firstName + " (" + email + ")";
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,17 @@ thing-type.config.ondilo.ondilo.id.description = The Id of an Ondilo ICO Pool/Sp
|
||||
|
||||
# channel group types
|
||||
|
||||
channel-group-type.ondilo.configuration.label = Configurations
|
||||
channel-group-type.ondilo.configuration.channel.orp-high.label = Maximum ORP
|
||||
channel-group-type.ondilo.configuration.channel.orp-high.description = Maximum Oxidation-Reduction Potential (ORP)
|
||||
channel-group-type.ondilo.configuration.channel.ph-high.label = Maximum PH Level
|
||||
channel-group-type.ondilo.configuration.channel.ph-high.description = Maximum PH level
|
||||
channel-group-type.ondilo.configuration.channel.salt-high.label = Maximum Salt Level
|
||||
channel-group-type.ondilo.configuration.channel.salt-high.description = Maximum salt level
|
||||
channel-group-type.ondilo.configuration.channel.tds-high.label = Maximum TDS
|
||||
channel-group-type.ondilo.configuration.channel.tds-high.description = Maximum Total Dissolved Solids (TDS)
|
||||
channel-group-type.ondilo.configuration.channel.temperature-high.label = Maximum Water Temperature
|
||||
channel-group-type.ondilo.configuration.channel.temperature-high.description = Maximum water temperature
|
||||
channel-group-type.ondilo.measure.label = Measures
|
||||
channel-group-type.ondilo.recommendation.label = Recommendations
|
||||
|
||||
@@ -34,22 +45,42 @@ channel-type.ondilo.id.label = ID
|
||||
channel-type.ondilo.id.description = ID of the current recommendation
|
||||
channel-type.ondilo.message.label = Message
|
||||
channel-type.ondilo.message.description = Message of the current recommendation
|
||||
channel-type.ondilo.orp-low-high.label = Minimum ORP
|
||||
channel-type.ondilo.orp-low-high.description = Minimum Oxidation-Reduction Potential (ORP)
|
||||
channel-type.ondilo.orp-trend.label = ORP Delta
|
||||
channel-type.ondilo.orp-trend.description = Change in ORP since last measure
|
||||
channel-type.ondilo.orp.label = ORP
|
||||
channel-type.ondilo.orp.description = Oxidation-Reduction Potential (ORP)
|
||||
channel-type.ondilo.ph-low-high.label = Minimum PH Level
|
||||
channel-type.ondilo.ph-low-high.description = Minimum PH level
|
||||
channel-type.ondilo.ph-trend.label = PH Level Delta
|
||||
channel-type.ondilo.ph-trend.description = Change in PH level since last measure
|
||||
channel-type.ondilo.ph.label = PH Level
|
||||
channel-type.ondilo.ph.description = PH level
|
||||
channel-type.ondilo.poll-update.label = Poll Update
|
||||
channel-type.ondilo.poll-update.description = Poll status update from the cloud (get latest measures, not a trigger for new measures)
|
||||
channel-type.ondilo.rssi.label = RSSI
|
||||
channel-type.ondilo.rssi.description = Signal strength (RSSI)
|
||||
channel-type.ondilo.salt-low-high.label = Minimum Salt Level
|
||||
channel-type.ondilo.salt-low-high.description = Minimum salt level
|
||||
channel-type.ondilo.salt-trend.label = Salt Delta
|
||||
channel-type.ondilo.salt-trend.description = Change in salt level since last measure
|
||||
channel-type.ondilo.salt.label = Salt
|
||||
channel-type.ondilo.salt.description = Salt level
|
||||
channel-type.ondilo.status.label = Status
|
||||
channel-type.ondilo.status.description = Status of the current recommendation
|
||||
channel-type.ondilo.status.state.option.waiting = waiting
|
||||
channel-type.ondilo.status.state.option.ok = ok
|
||||
channel-type.ondilo.tds-low-high.label = Minimum TDS
|
||||
channel-type.ondilo.tds-low-high.description = Minimum Total Dissolved Solids (TDS)
|
||||
channel-type.ondilo.tds-trend.label = TDS Delta
|
||||
channel-type.ondilo.tds-trend.description = Change in TDS since last measure
|
||||
channel-type.ondilo.tds.label = TDS
|
||||
channel-type.ondilo.tds.description = Total dissolved solids
|
||||
channel-type.ondilo.temperature-low-high.label = Minimum Water Temperature
|
||||
channel-type.ondilo.temperature-low-high.description = Minimum water temperature
|
||||
channel-type.ondilo.temperature-trend.label = Water Temperature Delta
|
||||
channel-type.ondilo.temperature-trend.description = Change in water temperature since last measure
|
||||
channel-type.ondilo.temperature.label = Water Temperature
|
||||
channel-type.ondilo.temperature.description = Water temperature
|
||||
channel-type.ondilo.title.label = Title
|
||||
|
||||
+141
-2
@@ -14,6 +14,10 @@
|
||||
<channel id="poll-update" typeId="poll-update"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="userInfo"/>
|
||||
</properties>
|
||||
|
||||
<config-description>
|
||||
<parameter name="url" type="text" required="true">
|
||||
<label>openHAB URL</label>
|
||||
@@ -42,6 +46,7 @@
|
||||
<channel-groups>
|
||||
<channel-group id="measure" typeId="measure"/>
|
||||
<channel-group id="recommendation" typeId="recommendation"/>
|
||||
<channel-group id="configuration" typeId="configuration"/>
|
||||
</channel-groups>
|
||||
|
||||
<properties>
|
||||
@@ -51,6 +56,10 @@
|
||||
<property name="disinfection"/>
|
||||
<property name="address"/>
|
||||
<property name="location"/>
|
||||
<property name="uuid"/>
|
||||
<property name="poolGuyNumber"/>
|
||||
<property name="maintenanceDay"/>
|
||||
<property name="thingTypeVersion">1</property>
|
||||
</properties>
|
||||
|
||||
<config-description>
|
||||
@@ -65,10 +74,15 @@
|
||||
<label>Measures</label>
|
||||
<channels>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="temperature-trend" typeId="temperature-trend"/>
|
||||
<channel id="ph" typeId="ph"/>
|
||||
<channel id="ph-trend" typeId="ph-trend"/>
|
||||
<channel id="orp" typeId="orp"/>
|
||||
<channel id="orp-trend" typeId="orp-trend"/>
|
||||
<channel id="salt" typeId="salt"/>
|
||||
<channel id="salt-trend" typeId="salt-trend"/>
|
||||
<channel id="tds" typeId="tds"/>
|
||||
<channel id="tds-trend" typeId="tds-trend"/>
|
||||
<channel id="battery" typeId="system.battery-level"/>
|
||||
<channel id="rssi" typeId="rssi"/>
|
||||
<channel id="value-time" typeId="value-time"/>
|
||||
@@ -88,13 +102,44 @@
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="configuration">
|
||||
<label>Configurations</label>
|
||||
<channels>
|
||||
<channel id="temperature-low" typeId="temperature-low-high"/>
|
||||
<channel id="temperature-high" typeId="temperature-low-high">
|
||||
<label>Maximum Water Temperature</label>
|
||||
<description>Maximum water temperature</description>
|
||||
</channel>
|
||||
<channel id="ph-low" typeId="ph-low-high"/>
|
||||
<channel id="ph-high" typeId="ph-low-high">
|
||||
<label>Maximum PH Level</label>
|
||||
<description>Maximum PH level</description>
|
||||
</channel>
|
||||
<channel id="orp-low" typeId="orp-low-high"/>
|
||||
<channel id="orp-high" typeId="orp-low-high">
|
||||
<label>Maximum ORP</label>
|
||||
<description>Maximum Oxidation-Reduction Potential (ORP)</description>
|
||||
</channel>
|
||||
<channel id="salt-low" typeId="salt-low-high"/>
|
||||
<channel id="salt-high" typeId="salt-low-high">
|
||||
<label>Maximum Salt Level</label>
|
||||
<description>Maximum salt level</description>
|
||||
</channel>
|
||||
<channel id="tds-low" typeId="tds-low-high"/>
|
||||
<channel id="tds-high" typeId="tds-low-high">
|
||||
<label>Maximum TDS</label>
|
||||
<description>Maximum Total Dissolved Solids (TDS)</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<!-- Bridge Channel Types -->
|
||||
<channel-type id="poll-update" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Poll Update</label>
|
||||
<description>Poll status update from the cloud (get latest measures, not a trigger for new measures)</description>
|
||||
<tags>
|
||||
<tag>Control</tag>
|
||||
<tag>Setpoint</tag>
|
||||
</tags>
|
||||
</channel-type>
|
||||
|
||||
@@ -109,6 +154,16 @@
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="temperature-trend" advanced="true">
|
||||
<item-type unitHint="°C">Number:Temperature</item-type>
|
||||
<label>Water Temperature Delta</label>
|
||||
<description>Change in water temperature since last measure</description>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="Δ %+.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="ph">
|
||||
<item-type>Number</item-type>
|
||||
<label>PH Level</label>
|
||||
@@ -116,7 +171,16 @@
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%d"/>
|
||||
<state readOnly="true" pattern="%.2f"/>
|
||||
</channel-type>
|
||||
<channel-type id="ph-trend" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>PH Level Delta</label>
|
||||
<description>Change in PH level since last measure</description>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="Δ %+.2f"/>
|
||||
</channel-type>
|
||||
<channel-type id="orp">
|
||||
<item-type unitHint="mV">Number:ElectricPotential</item-type>
|
||||
@@ -127,6 +191,15 @@
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="orp-trend" advanced="true">
|
||||
<item-type unitHint="mV">Number:ElectricPotential</item-type>
|
||||
<label>ORP Delta</label>
|
||||
<description>Change in ORP since last measure</description>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="Δ %+.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="salt">
|
||||
<item-type unitHint="mg/l">Number:Density</item-type>
|
||||
<label>Salt</label>
|
||||
@@ -136,6 +209,15 @@
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="salt-trend" advanced="true">
|
||||
<item-type unitHint="mg/l">Number:Density</item-type>
|
||||
<label>Salt Delta</label>
|
||||
<description>Change in salt level since last measure</description>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="Δ %+.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="tds">
|
||||
<item-type unitHint="ppm">Number:Dimensionless</item-type>
|
||||
<label>TDS</label>
|
||||
@@ -145,6 +227,15 @@
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="tds-trend" advanced="true">
|
||||
<item-type unitHint="ppm">Number:Dimensionless</item-type>
|
||||
<label>TDS Delta</label>
|
||||
<description>Change in TDS since last measure</description>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="Δ %+.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="rssi">
|
||||
<item-type>Number</item-type>
|
||||
<label>RSSI</label>
|
||||
@@ -240,4 +331,52 @@
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Configuration Channel Types -->
|
||||
<channel-type id="temperature-low-high" advanced="true">
|
||||
<item-type unitHint="°C">Number:Temperature</item-type>
|
||||
<label>Minimum Water Temperature</label>
|
||||
<description>Minimum water temperature</description>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="ph-low-high" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Minimum PH Level</label>
|
||||
<description>Minimum PH level</description>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.2f"/>
|
||||
</channel-type>
|
||||
<channel-type id="orp-low-high" advanced="true">
|
||||
<item-type unitHint="mV">Number:ElectricPotential</item-type>
|
||||
<label>Minimum ORP</label>
|
||||
<description>Minimum Oxidation-Reduction Potential (ORP)</description>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="salt-low-high" advanced="true">
|
||||
<item-type unitHint="mg/l">Number:Density</item-type>
|
||||
<label>Minimum Salt Level</label>
|
||||
<description>Minimum salt level</description>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="tds-low-high" advanced="true">
|
||||
<item-type unitHint="ppm">Number:Dimensionless</item-type>
|
||||
<label>Minimum TDS</label>
|
||||
<description>Minimum Total Dissolved Solids (TDS)</description>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
|
||||
|
||||
<thing-type uid="ondilo:ondilo">
|
||||
<instruction-set targetVersion="1">
|
||||
<add-channel id="temperature-trend" groupIds="measure">
|
||||
<type>ondilo:temperature-trend</type>
|
||||
</add-channel>
|
||||
<add-channel id="ph-trend" groupIds="measure">
|
||||
<type>ondilo:ph-trend</type>
|
||||
</add-channel>
|
||||
<add-channel id="orp-trend" groupIds="measure">
|
||||
<type>ondilo:orp-trend</type>
|
||||
</add-channel>
|
||||
<add-channel id="salt-trend" groupIds="measure">
|
||||
<type>ondilo:salt-trend</type>
|
||||
</add-channel>
|
||||
<add-channel id="tds-trend" groupIds="measure">
|
||||
<type>ondilo:tds-trend</type>
|
||||
</add-channel>
|
||||
|
||||
<add-channel id="temperature-low" groupIds="configuration">
|
||||
<type>ondilo:temperature-low-high</type>
|
||||
</add-channel>
|
||||
<add-channel id="temperature-high" groupIds="configuration">
|
||||
<type>ondilo:temperature-low-high</type>
|
||||
<label>Maximum Water Temperature</label>
|
||||
<description>Maximum water temperature</description>
|
||||
</add-channel>
|
||||
<add-channel id="ph-low" groupIds="configuration">
|
||||
<type>ondilo:ph-low-high</type>
|
||||
</add-channel>
|
||||
<add-channel id="ph-high" groupIds="configuration">
|
||||
<type>ondilo:ph-low-high</type>
|
||||
<label>Maximum PH Level</label>
|
||||
<description>Maximum PH level</description>
|
||||
</add-channel>
|
||||
<add-channel id="orp-low" groupIds="configuration">
|
||||
<type>ondilo:orp-low-high</type>
|
||||
</add-channel>
|
||||
<add-channel id="orp-high" groupIds="configuration">
|
||||
<type>ondilo:orp-low-high</type>
|
||||
<label>Maximum ORP</label>
|
||||
<description>Maximum Oxidation-Reduction Potential (ORP)</description>
|
||||
</add-channel>
|
||||
<add-channel id="salt-low" groupIds="configuration">
|
||||
<type>ondilo:salt-low-high</type>
|
||||
</add-channel>
|
||||
<add-channel id="salt-high" groupIds="configuration">
|
||||
<type>ondilo:salt-low-high</type>
|
||||
<label>Maximum Salt Level</label>
|
||||
<description>Maximum salt level</description>
|
||||
</add-channel>
|
||||
<add-channel id="tds-low" groupIds="configuration">
|
||||
<type>ondilo:tds-low-high</type>
|
||||
</add-channel>
|
||||
<add-channel id="tds-high" groupIds="configuration">
|
||||
<type>ondilo:tds-low-high</type>
|
||||
<label>Maximum TDS</label>
|
||||
<description>Maximum Total Dissolved Solids (TDS)</description>
|
||||
</add-channel>
|
||||
</instruction-set>
|
||||
</thing-type>
|
||||
</update:update-descriptions>
|
||||
Reference in New Issue
Block a user