import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
+import org.openhab.binding.hdpowerview.internal.exceptions.HubShadeTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData moveShade(int shadeId, ShadePosition position)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData moveShade(int shadeId, ShadePosition position) throws HubInvalidResponseException,
+ HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
String jsonRequest = gson.toJson(new ShadeMove(position));
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return shadeDataFromJson(jsonResponse);
}
- private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseException {
+ private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseException, HubShadeTimeoutException {
try {
Shade shade = gson.fromJson(json, Shade.class);
if (shade == null) {
if (shadeData == null) {
throw new HubInvalidResponseException("Missing 'shade.shade' element");
}
+ if (Boolean.TRUE.equals(shadeData.timedOut)) {
+ throw new HubShadeTimeoutException("Timeout when sending request to the shade");
+ }
return shadeData;
} catch (JsonParseException e) {
throw new HubInvalidResponseException("Error parsing shade response", e);
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData stopShade(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData stopShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonRequest = gson.toJson(new ShadeStop());
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return shadeDataFromJson(jsonResponse);
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData jogShade(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData jogShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonRequest = gson.toJson(new ShadeJog());
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return shadeDataFromJson(jsonResponse);
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData calibrateShade(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData calibrateShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonRequest = gson.toJson(new ShadeCalibrate());
String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
return shadeDataFromJson(jsonResponse);
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData getShade(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData getShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), null, null);
return shadeDataFromJson(jsonResponse);
}
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
public ShadeData refreshShadePosition(int shadeId)
- throws JsonParseException, HubProcessingException, HubMaintenanceException {
+ throws JsonParseException, HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
Query.of("refresh", Boolean.toString(true)), null);
return shadeDataFromJson(jsonResponse);
* @throws HubInvalidResponseException if response is invalid
* @throws HubProcessingException if there is any processing error
* @throws HubMaintenanceException if the hub is down for maintenance
+ * @throws HubShadeTimeoutException if the shade did not respond to a request
*/
- public ShadeData refreshShadeBatteryLevel(int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ public ShadeData refreshShadeBatteryLevel(int shadeId) throws HubInvalidResponseException, HubProcessingException,
+ HubMaintenanceException, HubShadeTimeoutException {
String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
Query.of("updateBatteryLevel", Boolean.toString(true)), null);
return shadeDataFromJson(jsonResponse);
--- /dev/null
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.hdpowerview.internal.exceptions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link HubShadeTimeoutException} is a custom exception for the HD PowerView Hub
+ * which is thrown when a shade does not respond to a request.
+ *
+ * @author @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class HubShadeTimeoutException extends HubException {
+
+ private static final long serialVersionUID = -362347489903471011L;
+
+ public HubShadeTimeoutException(String message) {
+ super(message);
+ }
+}
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
if (childHandler instanceof HDPowerViewShadeHandler) {
ShadeData shadeData = pendingShadeInitializations.remove(childThing.getUID());
if (shadeData != null) {
- updateShadeThing(shadeData.id, childThing, shadeData);
+ if (shadeData.id > 0) {
+ updateShadeThing(shadeData.id, childThing, shadeData);
+ } else {
+ updateUnknownShadeThing(childThing);
+ }
}
}
super.childHandlerInitialized(childHandler, childThing);
Thing thing = item.getKey();
int shadeId = item.getValue();
ShadeData shadeData = idShadeDataMap.get(shadeId);
- updateShadeThing(shadeId, thing, shadeData);
+ if (shadeData != null) {
+ updateShadeThing(shadeId, thing, shadeData);
+ } else {
+ updateUnknownShadeThing(thing);
+ }
}
}
- private void updateShadeThing(int shadeId, Thing thing, @Nullable ShadeData shadeData) {
- if (shadeData == null) {
- logger.debug("Shade '{}' has no data in hub", shadeId);
- return;
- }
+ private void updateShadeThing(int shadeId, Thing thing, ShadeData shadeData) {
HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
if (thingHandler == null) {
logger.debug("Shade '{}' handler not initialized", shadeId);
}
}
+ private void updateUnknownShadeThing(Thing thing) {
+ String shadeId = thing.getUID().getId();
+ logger.debug("Shade '{}' has no data in hub", shadeId);
+ HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
+ if (thingHandler == null) {
+ logger.debug("Shade '{}' handler not initialized", shadeId);
+ pendingShadeInitializations.put(thing.getUID(), new ShadeData());
+ return;
+ }
+ ThingStatus thingStatus = thingHandler.getThing().getStatus();
+ switch (thingStatus) {
+ case UNKNOWN:
+ case ONLINE:
+ case OFFLINE:
+ thing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
+ "@text/offline.gone.shade-unknown-to-hub"));
+ break;
+ case UNINITIALIZED:
+ case INITIALIZING:
+ logger.debug("Shade '{}' handler not yet ready; status: {}", shadeId, thingStatus);
+ pendingShadeInitializations.put(thing.getUID(), new ShadeData());
+ break;
+ case REMOVING:
+ case REMOVED:
+ default:
+ logger.debug("Ignoring shade status update for shade '{}' in status {}", shadeId, thingStatus);
+ break;
+ }
+ }
+
private List<Scene> fetchScenes()
throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
HDPowerViewWebTargets webTargets = this.webTargets;
Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
Thing thing = item.getKey();
+ if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
+ // Skip shades unknown to the Hub.
+ logger.debug("Shade '{}' is unknown, skipping position refresh", item.getValue());
+ continue;
+ }
ThingHandler handler = thing.getHandler();
if (handler instanceof HDPowerViewShadeHandler) {
((HDPowerViewShadeHandler) handler).requestRefreshShadePosition();
Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
Thing thing = item.getKey();
+ if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
+ // Skip shades unknown to the Hub.
+ logger.debug("Shade '{}' is unknown, skipping battery level refresh", item.getValue());
+ continue;
+ }
ThingHandler handler = thing.getHandler();
if (handler instanceof HDPowerViewShadeHandler) {
((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel();
import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
+import org.openhab.binding.hdpowerview.internal.exceptions.HubShadeTimeoutException;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
+ } catch (HubShadeTimeoutException e) {
+ logger.warn("Shade {} timeout when sending command {}", shadeId, command);
} catch (HubException e) {
// ScheduledFutures will be cancelled by dispose(), naturally causing InterruptedException in invoke()
// for any ongoing requests. Logging this would only cause confusion.
}
private void handleShadeCommand(String channelId, Command command, HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException,
+ HubShadeTimeoutException {
switch (channelId) {
case CHANNEL_SHADE_POSITION:
if (command instanceof PercentType) {
/**
* Update the state of the channels based on the ShadeData provided.
*
- * @param shadeData the ShadeData to be used; may be null.
+ * @param shadeData the ShadeData to be used.
*/
- protected void onReceiveUpdate(@Nullable ShadeData shadeData) {
- if (shadeData != null) {
- updateStatus(ThingStatus.ONLINE);
- updateCapabilities(shadeData);
- updateSoftProperties(shadeData);
- updateFirmwareProperties(shadeData);
- ShadePosition shadePosition = shadeData.positions;
- if (shadePosition != null) {
- updatePositionStates(shadePosition);
- }
- updateBatteryLevelStates(shadeData.batteryStatus);
- updateState(CHANNEL_SHADE_BATTERY_VOLTAGE,
- shadeData.batteryStrength > 0 ? new QuantityType<>(shadeData.batteryStrength / 10, Units.VOLT)
- : UnDefType.UNDEF);
- updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ protected void onReceiveUpdate(ShadeData shadeData) {
+ updateStatus(ThingStatus.ONLINE);
+ updateCapabilities(shadeData);
+ updateSoftProperties(shadeData);
+ updateFirmwareProperties(shadeData);
+ ShadePosition shadePosition = shadeData.positions;
+ if (shadePosition != null) {
+ updatePositionStates(shadePosition);
}
+ updateBatteryStates(shadeData.batteryStatus, shadeData.batteryStrength);
+ updateState(CHANNEL_SHADE_SIGNAL_STRENGTH, new DecimalType(shadeData.signalStrength));
}
private void updateCapabilities(ShadeData shade) {
updateState(CHANNEL_SHADE_SECONDARY_POSITION, shadePos.getState(capabilities, SECONDARY_POSITION));
}
+ private void updateBatteryStates(int batteryStatus, double batteryStrength) {
+ updateBatteryLevelStates(batteryStatus);
+ updateState(CHANNEL_SHADE_BATTERY_VOLTAGE,
+ batteryStrength > 0 ? new QuantityType<>(batteryStrength / 10, Units.VOLT) : UnDefType.UNDEF);
+ }
+
private void updateBatteryLevelStates(int batteryStatus) {
int mappedValue;
switch (batteryStatus) {
}
private void moveShade(CoordinateSystem coordSys, int newPercent, HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException,
+ HubShadeTimeoutException {
ShadePosition newPosition = null;
// (try to) read the positions from the hub
ShadeData shadeData = webTargets.getShade(shadeId);
updateShadePositions(shadeData);
}
- private void stopShade(HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ private void stopShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+ HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
updateShadePositions(webTargets.stopShade(shadeId));
// Positions in response from stop motion is not updated to to actual positions yet,
// so we need to request hard refresh.
requestRefreshShadePosition();
}
- private void identifyShade(HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ private void identifyShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+ HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
updateShadePositions(webTargets.jogShade(shadeId));
}
- private void calibrateShade(HDPowerViewWebTargets webTargets, int shadeId)
- throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
+ private void calibrateShade(HDPowerViewWebTargets webTargets, int shadeId) throws HubInvalidResponseException,
+ HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
updateShadePositions(webTargets.calibrateShade(shadeId));
}
switch (kind) {
case POSITION:
shadeData = webTargets.refreshShadePosition(shadeId);
+ updateShadePositions(shadeData);
+ updateHardProperties(shadeData);
break;
case SURVEY:
Survey survey = webTargets.getShadeSurvey(shadeId);
} else {
logger.warn("No response from shade {} survey", shadeId);
}
- return;
+ break;
case BATTERY_LEVEL:
shadeData = webTargets.refreshShadeBatteryLevel(shadeId);
+ updateBatteryStates(shadeData.batteryStatus, shadeData.batteryStrength);
break;
default:
throw new NotSupportedException("Unsupported refresh kind " + kind.toString());
}
- if (Boolean.TRUE.equals(shadeData.timedOut)) {
- logger.warn("Shade {} wireless refresh time out", shadeId);
- } else if (kind == RefreshKind.POSITION) {
- updateShadePositions(shadeData);
- updateHardProperties(shadeData);
- }
} catch (HubInvalidResponseException e) {
Throwable cause = e.getCause();
if (cause == null) {
}
} catch (HubMaintenanceException e) {
// exceptions are logged in HDPowerViewWebTargets
+ } catch (HubShadeTimeoutException e) {
+ logger.info("Shade {} wireless refresh time out", shadeId);
} catch (HubException e) {
// ScheduledFutures will be cancelled by dispose(), naturally causing InterruptedException in invoke()
// for any ongoing requests. Logging this would only cause confusion.
offline.conf-error.no-host-address = Host address must be set
offline.conf-error.invalid-id = Configuration 'id' not a valid integer
offline.conf-error.invalid-bridge-handler = Invalid bridge handler
+offline.gone.shade-unknown-to-hub = Shade is unknown to Hub
# dynamic channels