Skip to content

Commit

Permalink
[viessmann] add status channel for OneTimeCharge (#415)
Browse files Browse the repository at this point in the history
* [viessmann] reaload OneTimeCharge state
* [viessmann] add support for manual polling
* [viessmann] initChannelState if command was unsuccessful
* [viessmann] fix renew refresh token

Signed-off-by: Ronny Grun <[email protected]>
  • Loading branch information
rogrun authored Nov 13, 2022
1 parent ab423fd commit 14cfc25
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 42 deletions.
27 changes: 15 additions & 12 deletions bundles/org.smarthomej.binding.viessmann/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<img src="org.smarthomej.binding.viessmann/doc/viessmann_wordmark_rgb_1_vitorange.png" width="140"/>

This binding connects Viessmann Heatings via the new Viessmann API.
This binding connects Viessmann Heating via the new Viessmann API.
It provides features like the ViCare-App.

## Note / Important
Expand All @@ -17,13 +17,13 @@ You have to register your ViCare Account at the [Viessmann developer portal](htt

### Hint:

On the Viessman developer portal you can add more than one RedirectURI by tapping the plus sign.
On the Viessmann developer portal you can add more than one RedirectURI by tapping the plus sign.

## Supported Things

The binding supports the following thing types:

* `bridge` - Supports connection to the Viesmmann API.
* `bridge` - Supports connection to the Viessmann API.
* `device` - Provides a device which is connected (Discovery)

## Discovery
Expand All @@ -34,18 +34,19 @@ Discovery is supported for all devices connected in your account.

The `bridge` thing supports the connection to the Viessmann API.

* `apiKey` (required) The Client ID from the Viessman developer portal
* `apiKey` (required) The Client ID from the Viessmann developer portal
* `user` (required) The E-Mail address which is registered for the ViCare App
* `password` (required) The password which is registered for the ViCare App
* `installationId` (optional / it will be discovered) The installation Id which belongs to your installation
* `installationId` (optional / it will be discovered) The installation ID which belongs to your installation
* `gatewaySerial` (optional / it will be discovered) The gateway serial which belongs to your installation
* `apiCallLimit` (default = 1450) The limit how often call the API (*)
* `bufferApiCommands` (default = 450) The buffer for commands (*)
* `pollingInterval` (default = 0) How often the available devices should be queried in seconds (**)
* `pollingIntervalErrors` (default = 60) How often the errors should be queried in minutes
* `disablePolling` (default = OFF) Deactivates the polling to carry out the manual poll using an item


(*) Used to calcuate refresh time in seconds.
(*) Used to calculate refresh time in seconds.
(**) If set to 0, then the interval will be calculated by the binding.

## Thing Configuration
Expand All @@ -56,11 +57,13 @@ _All configurations are made in the UI_

### `bridge`

| channel | type | RO/RW | description |
|--------------------|--------|-------|---------------------------------------------|
| `countApiCalls` | Number | RO | How often the API is called this day |
| `errorIsActive` | Switch | RO | Indicates whether the error is set / unset |
| `lastErrorMessage` | String | RO | Last error message from the installation |
| channel | type | RO/RW | description |
|---------------------|--------|-------|--------------------------------------------|
| `countApiCalls` | Number | RO | How often the API is called this day |
| `errorIsActive` | Switch | RO | Indicates whether the error is set / unset |
| `lastErrorMessage` | String | RO | Last error message from the installation |
| `runQueryOnce` | Switch | W | Run device query once |
| `runErrorQueryOnce` | Switch | W | Run error query once |


### `device`
Expand All @@ -80,5 +83,5 @@ The item type of each item has to be adjusted:
| unit | old item type | new item type |
|-------------------|---------------|-----------------------|
| hour, minutes,... | Number | Number:Time |
| percent | Nubmer | Number:Dimensionless |
| percent | Number | Number:Dimensionless |
| temperature | Number | Number:Temperature |
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,7 @@ public class ViessmannBindingConstants {

public static final Map<String, String> MODES_MAP = ResourceUtil.readProperties(ViessmannBindingConstants.class,
"modes.properties");

public static final String CHANNEL_RUN_QUERY_ONCE = "runQueryOnce";
public static final String CHANNEL_RUN_ERROR_QUERY_ONCE = "runErrorQueryOnce";
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ public class BridgeConfiguration {
public int bufferApiCommands = 450;
public int pollingInterval = 0;
public int pollingIntervalErrors = 60;
public boolean disablePolling = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -129,19 +130,34 @@ public void handleCommand(ChannelUID channelUID, Command command) {
Channel ch = thing.getChannel(channelUID.getId());
if (ch != null) {
logger.trace("ChannelUID: {} | Properties: {}", ch.getUID(), ch.getProperties());
Boolean initState = false;
boolean initState = false;
Map<String, String> prop = ch.getProperties();
String commands = prop.get("command");
if (commands != null) {
String uri = null;
String param = null;
String com[] = commands.split(",");
int initStateDelay = 0;
String[] com = commands.split(",");
if (OnOffType.ON.equals(command)) {
uri = prop.get("activateUri");
param = "{}";
String feature = prop.get("feature");
if (feature != null) {
if (feature.contains("oneTimeCharge")) {
initStateDelay = 2;
initState = true;
}
}
} else if (OnOffType.OFF.equals(command)) {
uri = prop.get("deactivateUri");
param = "{}";
String feature = prop.get("feature");
if (feature != null) {
if (feature.contains("oneTimeCharge")) {
initStateDelay = 2;
initState = true;
}
}
} else if (command instanceof DecimalType) {
logger.trace("Received DecimalType Command for Channel {}",
thing.getChannel(channelUID.getId()));
Expand Down Expand Up @@ -200,10 +216,12 @@ public void handleCommand(ChannelUID channelUID, Command command) {
: (ViessmannBridgeHandler) bridge.getHandler();
if (bridgeHandler != null) {
if (!bridgeHandler.setData(uri, param) || initState) {
initChannelState();
scheduler.schedule(this::initChannelState, initStateDelay, TimeUnit.SECONDS);
}
}
}
} else {
initChannelState();
}
}
} catch (IllegalArgumentException e) {
Expand Down Expand Up @@ -233,7 +251,7 @@ public void handleUpdate(FeatureDataDTO featureDataDTO) {
for (String entry : entr) {
String valueEntry = "";
String typeEntry = "";
Boolean bool = false;
boolean bool = false;
String viUnit = "";
String unit = null;
HeatingCircuit heatingCircuit = new HeatingCircuit();
Expand Down Expand Up @@ -289,7 +307,7 @@ public void handleUpdate(FeatureDataDTO featureDataDTO) {
break;
case "entries":
msg.setSuffix("schedule");
typeEntry = prop.entries.type.toString();
typeEntry = prop.entries.type;
valueEntry = new Gson().toJson(prop.entries.value);
break;
case "overlapAllowed":
Expand All @@ -298,7 +316,6 @@ public void handleUpdate(FeatureDataDTO featureDataDTO) {
bool = prop.overlapAllowed.value;
break;
case "temperature":
typeEntry = prop.temperature.type;
valueEntry = prop.temperature.value.toString();
typeEntry = "temperature";
viUnit = prop.temperature.unit;
Expand Down Expand Up @@ -436,7 +453,7 @@ public void handleUpdate(FeatureDataDTO featureDataDTO) {
switch (entry) {
case "entries":
subMsg.setSuffix("produced");
subMsg.setChannelType("type-boolean");
subMsg.setChannelType("type-boolean-readOnly");
createSubChannel(subMsg);
break;
case "day":
Expand Down Expand Up @@ -467,6 +484,12 @@ public void handleUpdate(FeatureDataDTO featureDataDTO) {
subMsg.setSuffix("lastYear");
createSubChannel(subMsg);
break;
case "active":
if (featureDataDTO.feature.contains("oneTimeCharge")) {
subMsg.setSuffix("status");
subMsg.setChannelType("type-boolean-readOnly");
createSubChannel(subMsg);
}
default:
break;
}
Expand All @@ -485,12 +508,15 @@ public void handleUpdate(FeatureDataDTO featureDataDTO) {
case "boolean":
OnOffType state = bool ? OnOffType.ON : OnOffType.OFF;
updateState(msg.getChannelId(), state);
if (featureDataDTO.feature.contains("oneTimeCharge")) {
updateState(subMsg.getChannelId(), state);
}
break;
case "string":
case "array":
updateState(msg.getChannelId(), StringType.valueOf(msg.getValue()));

String parts[] = msg.getValue().replace("[", "").replace("]", "").replace(" ", "")
String[] parts = msg.getValue().replace("[", "").replace("]", "").replace(" ", "")
.split(",");
if (parts.length > 1) {
switch (entry) {
Expand Down Expand Up @@ -598,8 +624,7 @@ private String getFeatureName(String feature) {
if (matcher.find()) {
String circuit = matcher.group(0);
feature = matcher.replaceAll(".N");
String name = FEATURES_MAP.getOrDefault(feature, feature) + " (Circuit: " + circuit.replace(".", "") + ")";
return name;
return FEATURES_MAP.getOrDefault(feature, feature) + " (Circuit: " + circuit.replace(".", "") + ")";
}
return FEATURES_MAP.getOrDefault(feature, feature);
}
Expand All @@ -612,7 +637,7 @@ private String getFeatureName(String feature) {
private OnOffType parseSchedule(String scheduleJson) {
Calendar now = Calendar.getInstance();

int hour = now.get(Calendar.HOUR_OF_DAY); // Get hour in 24 hour format
int hour = now.get(Calendar.HOUR_OF_DAY); // Get hour in 24-hour format
int minute = now.get(Calendar.MINUTE);

Date currTime = parseTime(hour + ":" + minute);
Expand Down Expand Up @@ -731,7 +756,7 @@ private Map<String, String> buildProperties(ThingMessageDTO msg) {
case "changeEndDate":
prop.put("changeEndDateUri", commands.changeEndDate.uri);
prop.put("command", "changeEndDate,schedule,unschedule");
prop.put("changeEndDatepParams", "end");
prop.put("changeEndDateParams", "end");
prop.put("scheduleParams", "start,end");
prop.put("unscheduleParams", "{}");
break;
Expand Down Expand Up @@ -785,14 +810,10 @@ private String convertChannelType(ThingMessageDTO msg) {
FeatureCommands commands = msg.getCommands();
if (commands != null) {
List<String> com = commands.getUsedCommands();
if (!com.isEmpty()) {
if (!com.isEmpty() && !"type-boolean-readOnly".equals(channelType)) {
for (String command : com) {
switch (command) {
case "setTemperature":
if (!"type-boolean".equals(channelType)) {
channelType = "type-settemperature";
}
break;
case "setTargetTemperature":
if (!"type-boolean".equals(channelType)) {
channelType = "type-settemperature";
Expand All @@ -814,11 +835,21 @@ private String convertChannelType(ThingMessageDTO msg) {
case "setMode":
channelType = "type-setMode";
break;
case "setSchedule":
case "setName":
if (msg.getSuffix().contains("active")) {
channelType = "type-boolean-readOnly";
}
break;
default:
break;
}
}
} else if ("type-boolean".equals(channelType)) {
channelType = channelType + "-readOnly";
}
} else if ("type-boolean".equals(channelType)) {
channelType = channelType + "-readOnly";
}
return channelType;
}
Expand All @@ -827,7 +858,7 @@ private void setStateDescriptionOptions(ThingMessageDTO msg) {
if ("type-setMode".equals(convertChannelType(msg))) {
List<String> modes = msg.commands.setMode.params.mode.constraints.enumValue;
if (modes != null) {
List<StateOption> stateOptions = new ArrayList<StateOption>();
List<StateOption> stateOptions = new ArrayList<>();
for (String command : modes) {
StateOption stateOption = new StateOption(command, MODES_MAP.get(command));
stateOptions.add(stateOption);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,16 @@ private boolean errorChannelsLinked() {

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Nothing to handle here currently
if (channelUID.getId().equals(CHANNEL_RUN_QUERY_ONCE) && OnOffType.ON.equals(command)) {
logger.debug("Received command: CHANNEL_RUN_QUERY_ONCE");
pollingFeatures();
updateState(CHANNEL_RUN_QUERY_ONCE, OnOffType.OFF);
}
if (channelUID.getId().equals(CHANNEL_RUN_ERROR_QUERY_ONCE) && OnOffType.ON.equals(command)) {
logger.debug("Received command: CHANNEL_RUN_ERROR_QUERY_ONCE");
getDeviceError();
updateState(CHANNEL_RUN_ERROR_QUERY_ONCE, OnOffType.OFF);
}
}

@Override
Expand Down Expand Up @@ -162,7 +171,7 @@ public void initialize() {
setConfigInstallationGatewayId();
}

if (errorChannelsLinked()) {
if (!config.disablePolling && errorChannelsLinked()) {
startViessmannErrorsPolling(config.pollingIntervalErrors);
}

Expand Down Expand Up @@ -236,9 +245,9 @@ private void countApiCalls() {

private void checkResetApiCalls() {
LocalTime time = LocalTime.now();
if (time.isAfter(LocalTime.of(00, 00, 01)) && (time.isBefore(LocalTime.of(01, 00, 00)))) {
if (time.isAfter(LocalTime.of(0, 0, 1)) && (time.isBefore(LocalTime.of(1, 0, 0)))) {
if (countReset) {
logger.debug("Resettig API call counts");
logger.debug("Resetting API call counts");
apiCalls = 0;
countReset = false;
}
Expand All @@ -251,7 +260,7 @@ private void pollingFeatures() {
List<String> devices = pollingDevicesList;
if (devices != null) {
for (String deviceId : devices) {
logger.debug("Loading featueres from Device ID: {}", deviceId);
logger.debug("Loading features from Device ID: {}", deviceId);
getAllFeaturesByDeviceId(deviceId);
}
}
Expand Down Expand Up @@ -280,11 +289,13 @@ private void startViessmannBridgePolling(Integer pollingIntervalS, Integer initi
ScheduledFuture<?> currentPollingJob = viessmannBridgePollingJob;
if (currentPollingJob == null) {
viessmannBridgePollingJob = scheduler.scheduleWithFixedDelay(() -> {
logger.debug("Refresh job scheduled to run every {} seconds for '{}'", pollingIntervalS,
getThing().getUID());
api.checkExpiringToken();
checkResetApiCalls();
pollingFeatures();
if (!config.disablePolling) {
logger.debug("Refresh job scheduled to run every {} seconds for '{}'", pollingIntervalS,
getThing().getUID());
pollingFeatures();
}
}, initialDelay, pollingIntervalS, TimeUnit.SECONDS);
}
}
Expand Down Expand Up @@ -364,7 +375,7 @@ public void waitForApiCallLimitReset(Long resetLimitMillis) {
}

/**
* Notify appropriate child thing handlers of an Viessmann message by calling their handleUpdate() methods.
* Notify appropriate child thing handlers of a Viessmann message by calling their handleUpdate() methods.
*
* @param msg message to forward to child handler(s)
*/
Expand Down
Loading

0 comments on commit 14cfc25

Please sign in to comment.