[OmniLink] Fix daylight savings when setting date/time (#12546)

* Fix daylight savings when setting date/time
* Use an action to set date/time
* Add default time zone detection and i18n

Signed-off-by: Ethan Dye <mrtops03@gmail.com>
This commit is contained in:
Ethan Dye 2022-04-18 14:07:58 -06:00 committed by GitHub
parent 485ee144b0
commit b6fa985fa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 239 additions and 108 deletions

View File

@ -8,7 +8,6 @@ It connects to many other devices through serial ports or wired contacts and exp
The OmniPro/Lumina controller acts as a "bridge" for accessing other connected devices.
| Omni type | Hardware Type | Things |
|:---------------------------|:-------------------------------------------------|:----------------------------------|
| Controller | Omni (Pro II, IIe, LTe), Lumina | `controller` (omni, lumina) |
@ -26,7 +25,6 @@ The OmniPro/Lumina controller acts as a "bridge" for accessing other connected d
| Access Control Reader Lock | Leviton Access Control Reader | `lock` |
## Discovery
### Controller
@ -57,7 +55,7 @@ The devices are identified by the device number that the OmniLink bridge assigns
The devices support some of the following channels:
| Channel Type ID | Item Type | Description | Thing types supporting this channel |
|-----------------------------|----------------------|--------------------------------------------------------------------------------------|-----------------------------------------------------|
|-----------------------------|----------------------|----------------------------------------------------------------------------------------------|-----------------------------------------------------|
| `activate_keypad_emergency` | Number | Activate a burglary, fire, or auxiliary keypad emergency alarm on Omni based models. | `area` |
| `alarm_burglary` | Switch | Indicates if a burglary alarm is active. | `area` |
| `alarm_fire` | Switch | Indicates if a fire alarm is active. | `area` |
@ -88,7 +86,7 @@ The devices support some of the following channels:
| `zone_volume` | Dimmer | Volume level of this audio zone. | `audio_zone` |
| `zone_source` | Number | Source for this audio zone. | `audio_zone` |
| `zone_control` | Player | Control the audio zone, e.g. start/stop/next/previous. | `audio_zone` |
| `system_date` | DateTime | Set controller date/time. | `controller` |
| `system_date` | DateTime | Controller date/time. See [Rule Actions](#rule-actions) for how to set controller date/time. | `controller` |
| `last_log` | String | Last log message on the controller, represented in JSON. | `controller` |
| `enable_disable_beeper` | Switch | Enable/Disable the beeper for this/all console(s). | `controller`, `console` |
| `beep` | Switch | Send a beep command to this/all console(s). | `controller`, `console` |
@ -127,7 +125,6 @@ The devices support some of the following channels:
| `bypass` | String | Send a 4 digit user code to bypass this zone. | `zone` |
| `restore` | String | Send a 4 digit user code to restore this zone. | `zone` |
### Trigger Channels
The devices support some of the following trigger channels:
@ -146,6 +143,55 @@ The devices support some of the following trigger channels:
| `activated_event` | Event sent when a button is activated. | `button` |
| `switch_press_event` | Event sent when an ALC, UPB, Radio RA, or Starlite switch is pressed. | `dimmable`, `upb` |
## Rule Actions
This binding includes a rule action, which allows synchronizing the controller time to match the openHAB system time with a user specified zone.
There is a separate instance for each contoller, which can be retrieved through:
:::: tabs
::: tab JavaScript
``` javascript
var omnilinkActions = actions.get("omnilink", "omnilink:controller:home");
```
:::
::: tab DSL
``` php
val omnilinkActions = getActions("omnilink", "omnilink:controller:home")
```
:::
::::
where the first parameter always has to be `omnilink` and the second is the full Thing UID of the controller that should be used.
Once this action instance is retrieved, you can invoke the `synchronizeControllerTime(String zone)` method on it:
:::: tabs
::: tab JavaScript
``` javascript
omnilinkAction.synchronizeControllerTime("America/Denver");
```
:::
::: tab DSL
``` php
omnilinkAction.synchronizeControllerTime("America/Denver")
```
:::
::::
## Full Example
@ -308,14 +354,3 @@ DateTime OmniProTime "Last Time Update [%1$ta %1$tR]" <time> {channel="o
14=Arming night delay
=Unknown
```
### Example `omnilink.rules`
```
rule "Update OmniPro Time"
when
Time cron "0 0 0/1 1/1 * ? *"
then
OmniProTime.sendCommand( new DateTimeType() )
end
```

View File

@ -16,6 +16,7 @@ import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.omnilink.internal.action.OmnilinkActions;
import org.openhab.binding.omnilink.internal.handler.AudioSourceHandler;
import org.openhab.binding.omnilink.internal.handler.AudioZoneHandler;
import org.openhab.binding.omnilink.internal.handler.ButtonHandler;
@ -34,13 +35,16 @@ import org.openhab.binding.omnilink.internal.handler.units.FlagHandler;
import org.openhab.binding.omnilink.internal.handler.units.OutputHandler;
import org.openhab.binding.omnilink.internal.handler.units.UpbRoomHandler;
import org.openhab.binding.omnilink.internal.handler.units.dimmable.UpbUnitHandler;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link OmnilinkHandlerFactory} is responsible for creating things and thing
@ -53,6 +57,11 @@ import org.osgi.service.component.annotations.Component;
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.omnilink")
public class OmnilinkHandlerFactory extends BaseThingHandlerFactory {
@Activate
public OmnilinkHandlerFactory(final @Reference TimeZoneProvider timeZoneProvider) {
OmnilinkActions.setTimeZoneProvider(timeZoneProvider);
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);

View File

@ -0,0 +1,84 @@
/**
* 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.omnilink.internal.action;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.omnilink.internal.handler.OmnilinkBridgeHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the action handler service for the synchronizeControllerTime action.
*
* @author Ethan Dye - Initial contribution
*/
@ThingActionsScope(name = "omnilink")
@NonNullByDefault
public class OmnilinkActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(OmnilinkActions.class);
public static Optional<TimeZoneProvider> timeZoneProvider = Optional.empty();
private @Nullable OmnilinkBridgeHandler handler;
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof OmnilinkBridgeHandler) {
this.handler = (OmnilinkBridgeHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
@RuleAction(label = "@text/actionLabel", description = "@text/actionDesc")
public void synchronizeControllerTime(
@ActionInput(name = "zone", label = "@text/actionInputZoneLabel", description = "@text/actionInputZoneDesc") @Nullable String zone) {
OmnilinkBridgeHandler actionsHandler = handler;
if (actionsHandler == null) {
logger.debug("Action service ThingHandler is null!");
} else {
ZonedDateTime zdt;
if (ZoneId.getAvailableZoneIds().contains(zone)) {
zdt = ZonedDateTime.now(ZoneId.of(zone));
} else {
logger.debug("Time zone provided invalid, using system default!");
if (timeZoneProvider.isPresent()) {
zdt = ZonedDateTime.now(timeZoneProvider.get().getTimeZone());
} else {
zdt = ZonedDateTime.now(ZoneId.systemDefault());
}
}
actionsHandler.synchronizeControllerTime(zdt);
}
}
public static void synchronizeSystemTime(ThingActions actions, @Nullable String zone) {
((OmnilinkActions) actions).synchronizeControllerTime(zone);
}
public static void setTimeZoneProvider(TimeZoneProvider tzp) {
timeZoneProvider = Optional.of(tzp);
}
}

View File

@ -18,9 +18,9 @@ import java.io.IOException;
import java.net.UnknownHostException;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@ -29,6 +29,7 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.omnilink.internal.AudioPlayer;
import org.openhab.binding.omnilink.internal.SystemType;
import org.openhab.binding.omnilink.internal.TemperatureFormat;
import org.openhab.binding.omnilink.internal.action.OmnilinkActions;
import org.openhab.binding.omnilink.internal.config.OmnilinkBridgeConfig;
import org.openhab.binding.omnilink.internal.discovery.OmnilinkDiscoveryService;
import org.openhab.binding.omnilink.internal.exceptions.BridgeOfflineException;
@ -105,7 +106,7 @@ public class OmnilinkBridgeHandler extends BaseBridgeHandler implements Notifica
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(OmnilinkDiscoveryService.class);
return Set.of(OmnilinkDiscoveryService.class, OmnilinkActions.class);
}
public void sendOmnilinkCommand(final int message, final int param1, final int param2)
@ -158,6 +159,17 @@ public class OmnilinkBridgeHandler extends BaseBridgeHandler implements Notifica
}
}
public void synchronizeControllerTime(ZonedDateTime zdt) {
boolean inDaylightSavings = zdt.getZone().getRules().isDaylightSavings(zdt.toInstant());
try {
getOmniConnection().setTimeCommand(zdt.getYear() - 2000, zdt.getMonthValue(), zdt.getDayOfMonth(),
zdt.getDayOfWeek().getValue(), zdt.getHour(), zdt.getMinute(), inDaylightSavings);
} catch (IOException | OmniNotConnectedException | OmniInvalidResponseException
| OmniUnknownMessageTypeException e) {
logger.debug("Could not send set date time command to OmniLink Controller: {}", e.getMessage());
}
}
private SystemFeatures reqSystemFeatures()
throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
try {
@ -178,22 +190,6 @@ public class OmnilinkBridgeHandler extends BaseBridgeHandler implements Notifica
}
switch (channelUID.getId()) {
case CHANNEL_SYSTEM_DATE:
if (command instanceof DateTimeType) {
ZonedDateTime zdt = ((DateTimeType) command).getZonedDateTime();
boolean inDaylightSavings = zdt.getZone().getRules().isDaylightSavings(zdt.toInstant());
try {
getOmniConnection().setTimeCommand(zdt.getYear() - 2000, zdt.getMonthValue(),
zdt.getDayOfMonth(), zdt.getDayOfWeek().getValue(), zdt.getHour(), zdt.getMinute(),
inDaylightSavings);
} catch (IOException | OmniNotConnectedException | OmniInvalidResponseException
| OmniUnknownMessageTypeException e) {
logger.debug("Could not send Set Time command to OmniLink Controller: {}", e.getMessage());
}
} else {
logger.debug("Invalid command: {}, must be DateTimeType", command);
}
break;
case CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER:
if (command instanceof StringType) {
try {
@ -485,7 +481,7 @@ public class OmnilinkBridgeHandler extends BaseBridgeHandler implements Notifica
OmniUnknownMessageTypeException {
SystemStatus status = getOmniConnection().reqSystemStatus();
logger.debug("Received system status: {}", status);
// Let's update system time
// Update controller's reported time
String dateString = new StringBuilder().append(2000 + status.getYear()).append("-")
.append(String.format("%02d", status.getMonth())).append("-")
.append(String.format("%02d", status.getDay())).append("T")

View File

@ -246,9 +246,9 @@ channel-type.omnilink.sensor_temperature.label = Temperature
channel-type.omnilink.sensor_temperature.description = The current temperature at this temperature sensor.
channel-type.omnilink.switch_press_event.label = Switch Press Event
channel-type.omnilink.switch_press_event.description = Event sent when an ALC, UPB, Radio RA, or Starlite switch is pressed.
channel-type.omnilink.sysDate.label = Date/Time
channel-type.omnilink.sysDate.description = Set controller date/time.
channel-type.omnilink.sysDate.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
channel-type.omnilink.system_date.label = Date/Time
channel-type.omnilink.system_date.description = Controller date/time.
channel-type.omnilink.system_date.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
channel-type.omnilink.thermostat_comm_failure.label = Thermostat Communications Failure
channel-type.omnilink.thermostat_comm_failure.description = Closed during a communications failure with this thermostat.
channel-type.omnilink.thermostat_cool_setpoint.label = Cool SetPoint
@ -318,3 +318,10 @@ channel-type.omnilink.zone_latched_alarm_status.state.option.1 = Tripped
channel-type.omnilink.zone_latched_alarm_status.state.option.2 = Reset, but previously tripped
channel-type.omnilink.zone_restore.label = Restore Zone
channel-type.omnilink.zone_restore.description = Send a 4 digit user code to restore this zone.
# thing actions
actionInputZoneLabel = Time zone
actionInputZoneDesc = The time zone of the controller, provided in the "America/Denver" format.
actionLabel = Synchronize controller Date/Time
actionDesc = Synchronizes the Date/Time and DST flag of the controller with openHAB's system time.

View File

@ -58,9 +58,9 @@
<channel-type id="system_date">
<item-type>DateTime</item-type>
<label>Date/Time</label>
<description>Set controller date/time.</description>
<description>Controller date/time.</description>
<category>Time</category>
<state pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
</channel-type>
<channel-type id="last_log">