Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 9 commits into from
Apr 18, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 51 additions & 13 deletions bundles/org.openhab.binding.omnilink/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,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
Expand Down Expand Up @@ -88,7 +87,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. | `controller` |
ecdye marked this conversation as resolved.
Show resolved Hide resolved
ecdye marked this conversation as resolved.
Show resolved Hide resolved
| `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` |
Expand Down Expand Up @@ -147,6 +146,56 @@ The devices support some of the following trigger channels:
| `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
ecdye marked this conversation as resolved.
Show resolved Hide resolved

::: 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

### Example `omnilink.things`
Expand Down Expand Up @@ -308,14 +357,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
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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;
Expand All @@ -34,13 +35,16 @@
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
Expand All @@ -53,6 +57,11 @@
@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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 set_date action.
ecdye marked this conversation as resolved.
Show resolved Hide resolved
*
* @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")
ecdye marked this conversation as resolved.
Show resolved Hide resolved
public void synchronizeControllerTime(
@ActionInput(name = "zone", label = "@text/actionInputZoneLabel", description = "@text/actionInputZoneDesc") @Nullable String zone) {
OmnilinkBridgeHandler actionsHandler = handler;
ZonedDateTime zdt;
if (ZoneId.getAvailableZoneIds().contains(zone)) {
zdt = ZonedDateTime.now(ZoneId.of(zone));
ecdye marked this conversation as resolved.
Show resolved Hide resolved
} 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());
}
}
if (actionsHandler == null) {
ecdye marked this conversation as resolved.
Show resolved Hide resolved
logger.debug("Action service ThingHandler is null!");
} else {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
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;

Expand All @@ -29,6 +29,7 @@
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;
Expand Down Expand Up @@ -105,7 +106,7 @@ public OmnilinkBridgeHandler(Bridge bridge) {

@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)
Expand Down Expand Up @@ -158,6 +159,17 @@ public SystemFormats reqSystemFormats()
}
}

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 {
Expand All @@ -178,22 +190,6 @@ public void handleCommand(ChannelUID channelUID, Command command) {
}

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 {
Expand Down Expand Up @@ -485,7 +481,7 @@ private void getSystemStatus() throws IOException, OmniNotConnectedException, Om
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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 the Date/Time and DST flag of the controller
ecdye marked this conversation as resolved.
Show resolved Hide resolved
actionDesc = Synchronizes the Date/Time and DST flag of the controller with openHAB's system time.
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down