From 9e75294187b47464be09628b4265d71e0e03c2e8 Mon Sep 17 00:00:00 2001 From: Massimo Valla Date: Mon, 6 Feb 2023 13:02:39 +0100 Subject: [PATCH 1/2] [openwebnet] improved shutterRun calibration and README Signed-off-by: Massimo Valla --- .../org.openhab.binding.openwebnet/README.md | 26 +++++++------- .../handler/OpenWebNetAutomationHandler.java | 34 ++++++++++++------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/bundles/org.openhab.binding.openwebnet/README.md b/bundles/org.openhab.binding.openwebnet/README.md index c36b44150a0f1..f0a567500820a 100644 --- a/bundles/org.openhab.binding.openwebnet/README.md +++ b/bundles/org.openhab.binding.openwebnet/README.md @@ -257,31 +257,31 @@ OPEN command to execute: *5*8#134## #### `shutter` position -For Percent commands and position feedback to work correctly, the `shutterRun` Thing config parameter must be configured equal to the time (in ms) to go from full UP to full DOWN. It's possible to enter a value manually or set `shutterRun=AUTO` (default) to calibrate `shutterRun` automatically. +For Percent commands and position feedback to work correctly, the `shutterRun` Thing config parameter must be configured equal to the time (in milliseconds) to go from full UP to full DOWN. It's possible to enter a value manually or set `shutterRun=AUTO` (default) to calibrate `shutterRun` automatically. -_Automatic calibration of `shutterRun`_ +##### Automatic calibration of `shutterRun` -If `shutterRun` is set to AUTO a _UP >> DOWN >> Position%_ cycle will be performed automatically the first time a Percent command is sent to the shutter. The binding then relies on the _STOP_ command sent by the actuator at the stop time set in the actuator's advanced configuration in the My Home Suite. Therefore the binding's automatic calibration of `shutterRun` only works correctly if the stop time is set to the time the shutters need to go from full UP to full DOWN. +When `shutterRun` is set to `AUTO`, a _UP >> DOWN >> Position%_ cycle will be performed automatically the first time a Percent command is sent to the shutter: this way the binding will calculate the shutter "run time" and set the `shutterRun` parameter accordingly. +Note that there is a "stop time" parameter that is usually set on the physical actuator (eg. F411U2) either via virtual configuration (MyHOME_Suite: parameter "Stop time" or "M") or physical configuration (jumpers): check the actuator instructions to configure correctly this parameter before doing an automatic calibration of `shutterRun`. +If the "stop time" of the physical actuator is wrongly set or cannot be modified to the actual "run time", auto calibration cannot be used and `shutterRun` has to be set manually. -Older actuators that come without an ID and do not support advanced configuration via the My Home Suite have a fixed stop time of 60 seconds. In this case auto calibration of the binding cannot be used and `shutterRun` has to be set manually. - -_Notes on `shutter` position_ +##### Position estimation of the shutter - if `shutterRun` is not set, or is set to `AUTO` but calibration has not been performed yet, then position estimation will remain `UNDEF` (undefined) -- if `shutterRun` is wrongly set higher than the actual runtime, then position estimation will remain `UNDEF`: try to reduce shutterRun until you find the right value +- if `shutterRun` is wrongly set *higher* than the actual runtime, then position estimation will remain `UNDEF`: try to reduce `shutterRun` until you find the right value - before adding/configuring roller shutter Things it is suggested to have all roller shutters `UP`, otherwise the Percent command won’t work until the roller shutter is fully rolled up -- if OH is restarted the binding does not know if a shutter position has changed in the meantime, so its position will be `UNDEF`. Move the shutter all `UP`/`DOWN` to synchronize again its position with the binding -- the shutter position is estimated based on UP/DOWN timing: an error of ±2% is normal +- if OH is restarted the binding does not know if a shutter position has changed in the meantime, so its position will be `UNDEF`. Move the shutter all `UP`/`DOWN` to synchronise again its position with the binding +- the shutter position is estimated based on UP/DOWN timings: an error of ±2% is normal #### Scenario channels Basic Scenarios and CEN/CEN+ Scenarios channels are [TRIGGER channels](https://www.openhab.org/docs/configuration/rules-dsl.html#channel-based-triggers]): they handle events and do not have a state. A powerful feature is to detect scenario activations and CEN/CEN+ buttons press events to trigger rules in openHAB: this way openHAB becomes a very powerful scenario manager activated by BTicino scenario control modules or by CEN/CEN+ scenarios physical buttons. -See [openwebnet.rules](#openwebnetrules) for examples on how to define rules that trigger on scenarios and on CEN/CEN+ button press events. +See [openwebnet.rules](#openwebnet-rules) for examples on how to define rules that trigger on scenarios and on CEN/CEN+ button press events. It's also possible to send _virtual scenario activation_ and _virtual press_ events on the BUS, for example to enable the activation of MH202 or F420 scenarios from openHAB.. -See [openwebnet.sitemap](#openwebnetsitemap) & [openwebnet.rules](#openwebnetrules) sections for examples on how to use the `activateScenario` and `virtualPress` actions connected to a pushbutton on a sitemap. +See [openwebnet.sitemap](#openwebnet-sitemap) & [openwebnet.rules](#openwebnet-rules) sections for examples on how to use the `activateScenario` and `virtualPress` actions connected to a pushbutton on a sitemap. - basic scenario channels are named `scenario` and possible events are: `SCENARIO_01` ... `SCENARIO_16` (or up to `SCENARIO_20` in case of module IR3456) when a scenario is activated - CEN/CEN+ channels are named `button#X` where `X` is the button number on the CEN/CEN+ Scenario Control device @@ -319,7 +319,7 @@ BUS gateway and things configuration: Bridge openwebnet:bus_gateway:mybridge "MyHOMEServer1" [ host="192.168.1.35", passwd="abcde", port=20000, discoveryByActivation=false ] { bus_on_off_switch LR_switch "Living Room Light" [ where="51" ] bus_dimmer LR_dimmer "Living Room Dimmer" [ where="0311#4#01" ] - bus_automation LR_shutter "Living Room Shutter" [ where="93", shutterRun="10050"] + bus_automation LR_shutter "Living Room Shutter" [ where="93", shutterRun="20050"] bus_energy_meter CENTRAL_Ta "Energy Meter Ta" [ where="51" ] bus_energy_meter CENTRAL_Tb "Energy Meter Tb" [ where="52" ] @@ -438,7 +438,7 @@ sitemap openwebnet label="OpenWebNet Binding Example Sitemap" Frame label="Living Room Thermo" { Default item=iLR_zone_temp label="Temperature" icon="fire" valuecolor=[<20="red"] - Setpoint item=iLR_zone_set label="Setpoint [%.1f °C]" step=0.5 minValue=15 maxValue=30 + Setpoint item=iLR_zone_setTemp label="Setpoint [%.1f °C]" step=0.5 minValue=15 maxValue=30 Selection item=iLR_zone_fanSpeed label="Fan Speed" icon="fan" mappings=[AUTO="AUTO", SPEED_1="Low", SPEED_2="Medium", SPEED_3="High"] Switch item=iLR_zone_mode label="Mode" icon="settings" Selection item=iLR_zone_func label="Function" icon="heating" mappings=[HEATING="Heating", COOLING="Cooling", GENERIC="Heating/Cooling"] diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetAutomationHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetAutomationHandler.java index 73b2093659dbd..e5ba0561c9af7 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetAutomationHandler.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetAutomationHandler.java @@ -47,7 +47,8 @@ import org.slf4j.LoggerFactory; /** - * The {@link OpenWebNetAutomationHandler} is responsible for handling commands/messages for an Automation OpenWebNet + * The {@link OpenWebNetAutomationHandler} is responsible for handling + * commands/messages for an Automation OpenWebNet * device. It extends the abstract {@link OpenWebNetThingHandler}. * * @author Massimo Valla - Initial contribution @@ -247,18 +248,21 @@ private void handlePercentCommand(PercentType command, String w) { positionRequested = percent; } else if (shutterRun >= 1000 && positionEstimation != POSITION_UNKNOWN) { // these two must be known to calculate moveTime. - // Calculate how much time we have to move and set a deadline to stop after that time + // Calculate how much time we have to move and set a deadline to stop after that + // time int moveTime = Math .round(((float) Math.abs(percent - positionEstimation) / POSITION_MAX_STEPS * shutterRun)); logger.debug("# {} # target moveTime={}", deviceWhere, moveTime); if (moveTime > MIN_STEP_TIME_MSEC) { ScheduledFuture mSch = moveSchedule; if (mSch != null && !mSch.isDone()) { - // a moveSchedule was already scheduled and is not done... let's cancel the schedule + // a moveSchedule was already scheduled and is not done... let's cancel the + // schedule mSch.cancel(false); logger.debug("# {} # new XX% requested, old moveSchedule cancelled", deviceWhere); } - // send a requestFirmwareVersion message to BUS gateways to wake up the CMD connection before + // send a requestFirmwareVersion message to BUS gateways to wake up the CMD + // connection before // sending further cmds OpenWebNetBridgeHandler h = bridgeHandler; if (h != null && h.isBusGateway()) { @@ -270,7 +274,8 @@ private void handlePercentCommand(PercentType command, String w) { } } } - // REMINDER: start the schedule BEFORE sending the command, because the synch command waits for + // REMINDER: start the schedule BEFORE sending the command, because the synch + // command waits for // ACK and can take some 300ms logger.debug("# {} # Starting schedule...", deviceWhere); moveSchedule = scheduler.schedule(() -> { @@ -294,7 +299,7 @@ private void handlePercentCommand(PercentType command, String w) { } } else { logger.info( - "Command {} cannot be executed: unknown position or shutterRun configuration params not/wrongly set (thing={})", + "Command {} cannot be executed: UNDEF position or shutterRun configuration parameter not/wrongly set (thing={})", command, thing.getUID()); } } @@ -312,12 +317,14 @@ protected String ownIdPrefix() { protected void handleMessage(BaseOpenMessage msg) { logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID()); updateAutomationState((Automation) msg); - // REMINDER: update automation state, and only after update thing status using the super method, to avoid delays + // REMINDER: update automation state, and only after update thing status using + // the super method, to avoid delays super.handleMessage(msg); } /** - * Updates automation device state based on the Automation message received from OWN network + * Updates automation device state based on the Automation message received from + * OWN network * * @param msg the Automation message */ @@ -345,10 +352,12 @@ private void updateAutomationState(Automation msg) { logger.debug("& {} & CALIBRATION - started going ALL DOWN...", deviceWhere); } } else if (msg.isStop()) { - long stoppedAt = System.currentTimeMillis(); + long measuredRuntime = System.currentTimeMillis() - startedMovingAtTS; if (calibrating == CALIBRATION_GOING_DOWN && shutterRun == SHUTTER_RUN_UNDEFINED) { - shutterRun = (int) (stoppedAt - startedMovingAtTS); - logger.debug("& {} & CALIBRATION - reached DOWN ---> shutterRun={}", deviceWhere, shutterRun); + // since there are transmission delays we set shutterRun slightly less (-500ms + // and -2%) than measuredRuntime + shutterRun = (int) ((measuredRuntime - 500) * 0.98); + logger.debug("& {} & CALIBRATION - reached DOWN : measuredRuntime={}", deviceWhere, measuredRuntime); updateMovingState(MOVING_STATE_STOPPED); logger.debug("& {} & CALIBRATION - COMPLETED, now going to {}%", deviceWhere, positionRequested); handleShutterCommand(new PercentType(positionRequested)); @@ -416,7 +425,8 @@ private void updateMovingState(int newState) { } /** - * Updates positionEstimation and then channel state based on movedTime and current movingState + * Updates positionEstimation and then channel state based on movedTime and + * current movingState */ private void updatePosition() { int newPos = POSITION_UNKNOWN; From d5fc81d6805417848fdc83196d6c0366d743a63e Mon Sep 17 00:00:00 2001 From: Massimo Valla Date: Mon, 6 Feb 2023 13:04:16 +0100 Subject: [PATCH 2/2] [openwebnet] added Thing status detail when bridge is offline Signed-off-by: Massimo Valla --- .../handler/OpenWebNetBridgeHandler.java | 2 +- .../handler/OpenWebNetThingHandler.java | 54 +++++++++++-------- .../OH-INF/i18n/openwebnet.properties | 19 +++---- 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetBridgeHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetBridgeHandler.java index ae1d9cfbfa989..1037599a51a5d 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetBridgeHandler.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetBridgeHandler.java @@ -517,7 +517,7 @@ public void onEventMessage(@Nullable OpenMessage msg) { || (discoveryByActivation && !scanIsActive))) { discoverByActivation(baseMsg); } else { - logger.debug("ownId={} has NO DEVICE associated, ignoring it", ownId); + logger.debug("ownId={} has NO DEVICE associated to bridge {}: ignoring it", ownId, thing.getUID()); } } else { deviceHandler.handleMessage(baseMsg); diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThingHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThingHandler.java index 05d90f18f612f..8d4ceb1337052 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThingHandler.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThingHandler.java @@ -46,9 +46,10 @@ import org.slf4j.LoggerFactory; /** - * The {@link OpenWebNetThingHandler} is responsible for handling commands for an OpenWebNet device. - * It's the abstract class for all OpenWebNet things. It should be extended by each specific OpenWebNet category of - * device (WHO). + * The {@link OpenWebNetThingHandler} is responsible for handling commands for + * an OpenWebNet device. + * It's the abstract class for all OpenWebNet things. It should be extended by + * each specific OpenWebNet category of device (WHO). * * @author Massimo Valla - Initial contribution */ @@ -118,7 +119,7 @@ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) { updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/unknown.waiting-state"); } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "@text/offline.bridge-offline"); } } @@ -157,8 +158,8 @@ public void handleCommand(ChannelUID channel, Command command) { protected abstract void handleChannelCommand(ChannelUID channel, Command command); /** - * Handle incoming message from OWN network via bridge Thing, directed to this device. - * It should be further implemented by each specific device handler. + * Handle incoming message from OWN network via bridge Thing, directed to this + * device. It should be further implemented by each specific device handler. * * @param msg the message to handle */ @@ -203,8 +204,8 @@ protected void handleMessage(BaseOpenMessage msg) { } /** - * Request the state for the specified channel. If no answer is received within THING_STATE_REQ_TIMEOUT_SEC, it is - * put OFFLINE. + * Request the state for the specified channel. If no answer is received within + * THING_STATE_REQ_TIMEOUT_SEC, it is put OFFLINE. * The method must be further implemented by each specific handler. * * @param channel the {@link ChannelUID} to request the state for @@ -217,7 +218,8 @@ protected void requestChannelState(ChannelUID channel) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-where"); return; } - // set a schedule to put device OFFLINE if no answer is received after THING_STATE_REQ_TIMEOUT_SEC + // set a schedule to put device OFFLINE if no answer is received after + // THING_STATE_REQ_TIMEOUT_SEC requestChannelStateTimeout = scheduler.schedule(() -> { if (thing.getStatus().equals(ThingStatus.UNKNOWN)) { logger.debug("requestChannelState() TIMEOUT for thing {}", thing.getUID()); @@ -228,21 +230,24 @@ protected void requestChannelState(ChannelUID channel) { } /** - * Refresh a device, possibly using a single OWN command if refreshAll=true and if supported. - * The method must be further implemented by each specific handler. + * Refresh a device, possibly using a single OWN command if refreshAll=true and + * if supported. The method must be further implemented by each specific + * handler. * - * @param refreshAll true if all devices for this handler must be refreshed with a single OWN command, if supported, - * otherwise just refresh the single device. + * @param refreshAll true if all devices for this handler must be refreshed with + * a single OWN command, if supported, otherwise just refresh + * the single device. */ protected abstract void refreshDevice(boolean refreshAll); /** - * If the subclass supports refreshing all devices with a single OWN command, returns the last TS when a refreshAll - * was requested, or 0 if not requested yet. If not supported return -1 (default). + * If the subclass supports refreshing all devices with a single OWN command, + * returns the last TS when a refreshAll was requested, or 0 if not requested + * yet. If not supported return -1 (default). * It must be implemented by each subclass that supports all devices refresh. * - * @return timestamp when last refreshAll command was sent, 0 if not requested yet, or -1 if it's not supported by - * subclass. + * @return timestamp when last refreshAll command was sent, 0 if not requested + * yet, or -1 if it's not supported by subclass. */ protected long getRefreshAllLastTS() { return -1; @@ -266,8 +271,8 @@ protected void refreshAllDevices() { logger.debug("--- refreshAllDevices() : refresh all devices sent {}msec ago, skipping... ({})", ALL_DEVICES_REFRESH_INTERVAL_MSEC, thing.getUID()); } - // sometimes GENERAL (e.g. #*1*0##) refresh requests do not return state for all devices, so let's - // schedule another single refresh device, just in case + // sometimes GENERAL (e.g. #*1*0##) refresh requests do not return state for all + // devices, so let's schedule another single refresh device, just in case refreshTimeout = scheduler.schedule(() -> { if (thing.getStatus().equals(ThingStatus.UNKNOWN)) { logger.debug( @@ -286,8 +291,9 @@ protected void refreshAllDevices() { } /** - * Abstract builder for device Where address, to be implemented by each subclass to choose the right Where subclass - * (the method is used only if the Thing is associated to a BUS gateway). + * Abstract builder for device Where address, to be implemented by each subclass + * to choose the right Where subclass (the method is used only if the Thing is + * associated to a BUS gateway). * * @param wStr the WHERE string */ @@ -312,7 +318,8 @@ public void dispose() { } /** - * Helper method to return a Quantity from a Number value or UnDefType.NULL if value is null + * Helper method to return a Quantity from a Number value or UnDefType.NULL if + * value is null * * @param value to be used * @param unit to be used @@ -323,7 +330,8 @@ protected > State getAsQuantityTypeOrNull(@Nullable Number } /** - * Returns a prefix String for ownId specific for each handler. To be implemented by sub-classes. + * Returns a prefix String for ownId specific for each handler. + * To be implemented by sub-classes. * * @return */ diff --git a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties index 201fb6144fc52..e3fd76cb14cc2 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties +++ b/bundles/org.openhab.binding.openwebnet/src/main/resources/OH-INF/i18n/openwebnet.properties @@ -285,13 +285,14 @@ channel-type.openwebnet.zoneAlarm.state.option.SILENT = Silent # thing status descriptions -offline.comm-error-disconnected = Disconnected from gateway. -offline.comm-error-timeout = Connection to gateway timed out. -offline.comm-error-connection = Could not connect to gateway. -offline.comm-error-state = Could not get channel state. -offline.conf-error-no-ip-address = Cannot connect to gateway. No host/IP address has been provided in configuration. -offline.conf-error-no-serial-port = Cannot connect to gateway. No serial port has been provided in configuration. -offline.conf-error-where = OpenWebNet Address (where) parameter in configuration is null or invalid. -offline.conf-error-no-bridge = No bridge associated. Assign a bridge in configuration. -offline.conf-error-auth = Authentication failed. Check gateway password in configuration. +offline.comm-error-disconnected = Disconnected from gateway +offline.comm-error-timeout = Connection to gateway timed out +offline.comm-error-connection = Could not connect to gateway +offline.comm-error-state = Could not get channel state +offline.conf-error-no-ip-address = Cannot connect to gateway. No host/IP address has been provided in configuration +offline.conf-error-no-serial-port = Cannot connect to gateway. No serial port has been provided in configuration +offline.conf-error-where = OpenWebNet Address (where) parameter in configuration is null or invalid +offline.conf-error-no-bridge = No bridge associated. Assign a bridge in configuration +offline.conf-error-auth = Authentication failed. Check gateway password in configuration +offline.bridge-offline = Bridge offline unknown.waiting-state = Waiting state update...