[automower] Add planner, calendar and command channels (#8802)
* [Automower] Enhanced binding: - Added support for the planner and calendar data - Added command channels - Updated docs Signed-off-by: Marcin Czeczko <marcin.czeczko@gmail.com> * [Automower] Fixed consts with channel ids after removal of channel groups. Improved the mower state update: - Cache the last read state from API - Use cached mower state so the items linked will always be up to date without the need to wait for API refresh period. - Use timeZoneProvider to user user set timezone. Signed-off-by: Marcin Czeczko <marcin.czeczko@gmail.com> * Rolledback NotNullByDefault annotation Signed-off-by: Marcin Czeczko <marcin.czeczko@gmail.com>
This commit is contained in:
parent
ad7472847f
commit
0eb48e6abe
|
@ -1,26 +1,26 @@
|
||||||
# Automower Binding
|
# Automower Binding
|
||||||
|
|
||||||
This binding communicates to the Husqvarna Automower Connect API in order to send commands and query the state of Husqvarna Automower robots.
|
This is the binding for [Husqvarna Automower a robotic lawn mowers](https://www.husqvarna.com/uk/products/robotic-lawn-mowers/).
|
||||||
|
This binding allows you to integrate, view and control Automower lawn mowers in the openHAB environment.
|
||||||
|
|
||||||
## Supported Things
|
## Supported Things
|
||||||
|
|
||||||
`bridge:` The bridge needs to be configured with credentials and an application key that allows communicating with the Automower Connect Api
|
`bridge:` The bridge needs to be configured with credentials and an application key that allows communicating with the Automower Connect API
|
||||||
|
|
||||||
`automower:` A single Husqvarna Automower robot
|
`automower:` A single Husqvarna Automower robot
|
||||||
|
|
||||||
Basically all Husqvarna Automower models with "Automower Connect" support should be supported. It was tested only with a Husqvarna Automower 450X
|
All Husqvarna Automower models with "Automower Connect" should be supported. It was tested only with a Husqvarna Automower 430X and 450X.
|
||||||
|
|
||||||
|
|
||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
Once the bridge is created and configured, registered automowers will be discovered automatically
|
Once the bridge is created and configured, OpenHab will automatically discover all Automowers registered on your account.
|
||||||
|
|
||||||
|
|
||||||
## Thing Configuration
|
## Thing Configuration
|
||||||
|
|
||||||
`bridge:`
|
`bridge:`
|
||||||
|
|
||||||
- appKey (mandatory): The Application Key is required to communication with the Automower Connect Api. It can be obtained by registering an Application on the Husqvarna Website. This application also needs to be connected to the "Authentication API" and the "Automower Connect API"
|
- appKey (mandatory): The Application Key is required to communicate with the Automower Connect API. It can be obtained by registering an Application on [the Husqvarna Website](https://developer.husqvarnagroup.cloud/). This application also needs to be connected to the ["Authentication API" and the "Automower Connect API"](https://developer.husqvarnagroup.cloud/docs/getting-started)
|
||||||
- userName (mandatory): The user name for which the application key has been issued
|
- userName (mandatory): The user name for which the application key has been issued
|
||||||
- password (mandatory): The password for the given user
|
- password (mandatory): The password for the given user
|
||||||
- pollingInterval (optional): How often the bridge state should be queried in seconds. Default is 1h (3600s)
|
- pollingInterval (optional): How often the bridge state should be queried in seconds. Default is 1h (3600s)
|
||||||
|
@ -34,23 +34,31 @@ With the default value of 1h this would mean ~720 requests per month for the bri
|
||||||
- mowerId (mandatory): The Id of an automower as used by the Automower Connect Api to identify a mower. This is automatically filled when the thing is discovered
|
- mowerId (mandatory): The Id of an automower as used by the Automower Connect Api to identify a mower. This is automatically filled when the thing is discovered
|
||||||
- pollingInterval (optional): How often the current automower state should be polled in seconds. Default is 10min (600s)
|
- pollingInterval (optional): How often the current automower state should be polled in seconds. Default is 10min (600s)
|
||||||
|
|
||||||
Keep in mind that the status of the automowers should not be queried too often.
|
Keep in mind that the status of the Automowers should not be queried too often.
|
||||||
According to the Husqvarna documentation not more than 10000 requests per month and application key are allowed.
|
According to the Husqvarna documentation, no more than 10000 requests per month and application key are allowed.
|
||||||
With the default value of 10min this would mean ~4300 requests per month per automower
|
With the default value of 10min this would mean ~4300 requests per month per single Automower
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
|
|
||||||
| channel | type | description |
|
| channel | type | access mode | description |
|
||||||
|-----------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-------------------------|----------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| name | String | (readonly) The name of the Automower |
|
| mower-status#mode | String | R | The current mode (MAIN_AREA, SECONDARY_AREA, HOME, DEMO, UNKNOWN) |
|
||||||
| mode | String | (readonly) The current mode (MAIN_AREA, SECONDARY_AREA, HOME, DEMO, UNKNOWN) |
|
| mower-status#activity | String | R | The current activity (UNKNOWN, NOT_APPLICABLE, MOWING, GOING_HOME, CHARGING, LEAVING, PARKED_IN_CS, STOPPED_IN_GARDEN) |
|
||||||
| activity | String | (readonly) The current activity (UNKNOWN, NOT_APPLICABLE, MOWING, GOING_HOME, CHARGING, LEAVING, PARKED_IN_CS, STOPPED_IN_GARDEN) |
|
| mower-status#state | String | R | The current state (UNKNOWN, NOT_APPLICABLE, PAUSED, IN_OPERATION, WAIT_UPDATING, WAIT_POWER_UP, RESTRICTED_NONE, RESTRICTED_WEEK_SCHEDULE, RESTRICTED_PARK_OVERRIDE, RESTRICTED_SENSOR, RESTRICTED_DAILY_LIMIT, OFF, STOPPED, ERROR, FATAL_ERROR, ERROR_AT_POWER_UP) |
|
||||||
| state | String | (readonly) The current state (UNKNOWN, NOT_APPLICABLE, PAUSED, IN_OPERATION, WAIT_UPDATING, WAIT_POWER_UP, RESTRICTED, OFF, STOPPED, ERROR, FATAL_ERROR, ERROR_AT_POWER_UP) |
|
| mower-status#last-update | DateTime | R | The time when the automower updated its states |
|
||||||
| last-update | DateTime | (readonly) The time when the automower updated its states |
|
| mower-status#battery | Number | R | The battery state of charge in percent |
|
||||||
| battery | Number | (readonly) The battery state of charge in percent |
|
| mower-status#error-code | Number | R | The current error code |
|
||||||
| error-code | Number | (readonly) The current error code |
|
| mower-status#error-timestamp | DateTime | R | The timestamp when the current error occurred |
|
||||||
| error-timestamp | DateTime | (readonly) The timestamp when the current error occurred |
|
| mower-status#planner-next-start | DateTime | R | The time for the next auto start. If the mower is charging then the value is the estimated time when it will be leaving the charging station. If the mower is about to start now, the value is NULL. |
|
||||||
|
| mower-status#planner-override-action | String | R | The action that overrides current planner operation. |
|
||||||
|
| mower-status#calendar-tasks | String | R | The JSON with the information about Automower planner. |
|
||||||
|
| mower#start | Number | W | Starts the automower for a duration |
|
||||||
|
| mower#resume_schedule | Switch | W | Resumes the Automower schedule |
|
||||||
|
| mower#pause | Switch | W | Pause the Automower |
|
||||||
|
| mower#park | Number | W | Park the Automower for a duration |
|
||||||
|
| mower#park_until_next_schedule | Switch | W | Park the Automower until next schedule |
|
||||||
|
| mower#park_until_further_notice | Switch | W | Park the Automower until further notice. |
|
||||||
|
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
@ -79,19 +87,23 @@ The following actions are available for `automower`things:
|
||||||
|
|
||||||
### automower.items
|
### automower.items
|
||||||
|
|
||||||
String Automower_Name "Name" { channel="automower:automower:mybridge:myAutomower:name" }
|
String Automower_Mode "Mode [%s]" { channel="automower:automower:mybridge:myAutomower:mower-status#mode" }
|
||||||
String Automower_Mode "Mode" { channel="automower:automower:mybridge:myAutomower:mode" }
|
String Automower_Activity "Activity [%s]" { channel="automower:automower:mybridge:myAutomower:mower-status#activity" }
|
||||||
String Automower_Activity "Activity" { channel="automower:automower:mybridge:myAutomower:activity" }
|
String Automower_State "State [%s]" { channel="automower:automower:mybridge:myAutomower:mower-status#state" }
|
||||||
String Automower_State "State" { channel="automower:automower:mybridge:myAutomower:state" }
|
DateTime Automower_Last_Update "Last Update" { channel="automower:automower:mybridge:myAutomower:mower-status#last-update" }
|
||||||
DateTime Automower_Last_Update "Last Update" { channel="automower:automower:mybridge:myAutomower:last-update" }
|
Number Automower_Battery "Battery [%d %%]" { channel="automower:automower:mybridge:myAutomower:mower-status#battery" }
|
||||||
Number Automower_Battery "Battery" { channel="automower:automower:mybridge:myAutomower:battery" }
|
Number Automower_Error_Code "Error Code [%d]" { channel="automower:automower:mybridge:myAutomower:mower-status#error-code" }
|
||||||
Number Automower_Error_Code "Error Code" { channel="automower:automower:mybridge:myAutomower:error-code" }
|
DateTime Automower_Error_Time "Error Time" { channel="automower:automower:mybridge:myAutomower:mower-status#error-timestamp" }
|
||||||
DateTime Automower_Error_Time "Error Time" { channel="automower:automower:mybridge:myAutomower:error-timestamp" }
|
String Automower_Override_Action "Override Action [%s]" { channel="automower:automower:mybridge:myAutomower:mower-status#planner-override-action" }
|
||||||
|
DateTime Automower_Next_Start_Time "Next Start Time" { channel="automower:automower:mybridge:myAutomower:mower-status#planner-next-start" }
|
||||||
|
String Automower_Calendar_Tasks "Planned Tasks [%s]" { channel="automower:automower:mybridge:myAutomower:mower-status#calendar-tasks" }
|
||||||
|
|
||||||
|
Number Automower_Command_Start "Start mowing for duration [%d min]" { channel="automower:automower:mybridge:myAutomower:mower#start" }
|
||||||
String Automower_Command "Command" { channel="automower:automower:mybridge:myAutomower:command" }
|
Switch Automower_Command_Resume "Resume the schedule" { channel="automower:automower:mybridge:myAutomower:mower#resume_schedule" }
|
||||||
Number Automower_Command_Duration "Command Duration" { channel="automower:automower:mybridge:myAutomower:command-duration" }
|
Switch Automower_Command_Pause "Pause the automower" { channel="automower:automower:mybridge:myAutomower:mower#pause" }
|
||||||
String Automower_Command_Response "Command Response" { channel="automower:automower:mybridge:myAutomower:command-response" }
|
Number Automower_Command_Park "Park for duration [%d min]" { channel="automower:automower:mybridge:myAutomower:mower#park" }
|
||||||
|
Switch Automower_Command_Park_Next_Schedule "Park until next schedule" { channel="automower:automower:mybridge:myAutomower:mower#park_until_next_schedule" }
|
||||||
|
Switch Automower_Command_Park_Notice "Park until further notice" { channel="automower:automower:mybridge:myAutomower:mower#park_until_further_notice" }
|
||||||
|
|
||||||
### automower.sitemap
|
### automower.sitemap
|
||||||
|
|
||||||
|
@ -100,7 +112,6 @@ The following actions are available for `automower`things:
|
||||||
sitemap demo label="Automower"
|
sitemap demo label="Automower"
|
||||||
{
|
{
|
||||||
Frame {
|
Frame {
|
||||||
Text item=Automower_Name
|
|
||||||
Text item=Automower_Mode
|
Text item=Automower_Mode
|
||||||
Text item=Automower_Activity
|
Text item=Automower_Activity
|
||||||
Text item=Automower_State
|
Text item=Automower_State
|
||||||
|
@ -108,6 +119,9 @@ sitemap demo label="Automower"
|
||||||
Text item=Automower_Battery
|
Text item=Automower_Battery
|
||||||
Text item=Automower_Error_Code
|
Text item=Automower_Error_Code
|
||||||
Text item=Automower_Error_Time
|
Text item=Automower_Error_Time
|
||||||
|
Text item=Automower_Override_Action
|
||||||
|
Text item=Automower_Next_Start_Time
|
||||||
|
Text item=Automower_Calendar_Tasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -20,10 +20,10 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||||
* used across the whole binding.
|
* used across the whole binding.
|
||||||
*
|
*
|
||||||
* @author Markus Pfleger - Initial contribution
|
* @author Markus Pfleger - Initial contribution
|
||||||
|
* @author Marcin Czeczko - Added support for planner & calendar data
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AutomowerBindingConstants {
|
public class AutomowerBindingConstants {
|
||||||
|
|
||||||
private static final String BINDING_ID = "automower";
|
private static final String BINDING_ID = "automower";
|
||||||
|
|
||||||
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
|
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
|
||||||
|
@ -32,7 +32,7 @@ public class AutomowerBindingConstants {
|
||||||
public static final ThingTypeUID THING_TYPE_AUTOMOWER = new ThingTypeUID(BINDING_ID, "automower");
|
public static final ThingTypeUID THING_TYPE_AUTOMOWER = new ThingTypeUID(BINDING_ID, "automower");
|
||||||
|
|
||||||
// List of all Channel ids
|
// List of all Channel ids
|
||||||
public static final String CHANNEL_MOWER_NAME = "name";
|
public static final String CHANNEL_STATUS_NAME = "name";
|
||||||
public static final String CHANNEL_STATUS_MODE = "mode";
|
public static final String CHANNEL_STATUS_MODE = "mode";
|
||||||
public static final String CHANNEL_STATUS_ACTIVITY = "activity";
|
public static final String CHANNEL_STATUS_ACTIVITY = "activity";
|
||||||
public static final String CHANNEL_STATUS_STATE = "state";
|
public static final String CHANNEL_STATUS_STATE = "state";
|
||||||
|
@ -40,6 +40,17 @@ public class AutomowerBindingConstants {
|
||||||
public static final String CHANNEL_STATUS_BATTERY = "battery";
|
public static final String CHANNEL_STATUS_BATTERY = "battery";
|
||||||
public static final String CHANNEL_STATUS_ERROR_CODE = "error-code";
|
public static final String CHANNEL_STATUS_ERROR_CODE = "error-code";
|
||||||
public static final String CHANNEL_STATUS_ERROR_TIMESTAMP = "error-timestamp";
|
public static final String CHANNEL_STATUS_ERROR_TIMESTAMP = "error-timestamp";
|
||||||
|
public static final String CHANNEL_PLANNER_NEXT_START = "planner-next-start";
|
||||||
|
public static final String CHANNEL_PLANNER_OVERRIDE_ACTION = "planner-override-action";
|
||||||
|
public static final String CHANNEL_CALENDAR_TASKS = "calendar-tasks";
|
||||||
|
|
||||||
|
// Command channels
|
||||||
|
public static final String CHANNEL_COMMAND_START = "start";
|
||||||
|
public static final String CHANNEL_COMMAND_RESUME_SCHEDULE = "resume_schedule";
|
||||||
|
public static final String CHANNEL_COMMAND_PAUSE = "pause";
|
||||||
|
public static final String CHANNEL_COMMAND_PARK = "park";
|
||||||
|
public static final String CHANNEL_COMMAND_PARK_UNTIL_NEXT_SCHEDULE = "park_until_next_schedule";
|
||||||
|
public static final String CHANNEL_COMMAND_PARK_UNTIL_NOTICE = "park_until_further_notice";
|
||||||
|
|
||||||
// Automower properties
|
// Automower properties
|
||||||
public static final String AUTOMOWER_ID = "mowerId";
|
public static final String AUTOMOWER_ID = "mowerId";
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.openhab.binding.automower.internal.discovery.AutomowerDiscoveryServic
|
||||||
import org.openhab.binding.automower.internal.things.AutomowerHandler;
|
import org.openhab.binding.automower.internal.things.AutomowerHandler;
|
||||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||||
import org.openhab.core.config.discovery.DiscoveryService;
|
import org.openhab.core.config.discovery.DiscoveryService;
|
||||||
|
import org.openhab.core.i18n.TimeZoneProvider;
|
||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
|
@ -55,12 +56,14 @@ public class AutomowerHandlerFactory extends BaseThingHandlerFactory {
|
||||||
private final OAuthFactory oAuthFactory;
|
private final OAuthFactory oAuthFactory;
|
||||||
protected final @NonNullByDefault({}) HttpClient httpClient;
|
protected final @NonNullByDefault({}) HttpClient httpClient;
|
||||||
private @Nullable ServiceRegistration<?> automowerDiscoveryServiceRegistration;
|
private @Nullable ServiceRegistration<?> automowerDiscoveryServiceRegistration;
|
||||||
|
private final TimeZoneProvider timeZoneProvider;
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public AutomowerHandlerFactory(@Reference OAuthFactory oAuthFactory,
|
public AutomowerHandlerFactory(@Reference OAuthFactory oAuthFactory, @Reference HttpClientFactory httpClientFactory,
|
||||||
@Reference HttpClientFactory httpClientFactory) {
|
@Reference TimeZoneProvider timeZoneProvider) {
|
||||||
this.oAuthFactory = oAuthFactory;
|
this.oAuthFactory = oAuthFactory;
|
||||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
|
this.timeZoneProvider = timeZoneProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -77,7 +80,7 @@ public class AutomowerHandlerFactory extends BaseThingHandlerFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AutomowerHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
if (AutomowerHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
||||||
return new AutomowerHandler(thing);
|
return new AutomowerHandler(thing, timeZoneProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -38,7 +38,6 @@ import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AutomowerBridge {
|
public class AutomowerBridge {
|
||||||
|
|
||||||
private final OAuthClientService authService;
|
private final OAuthClientService authService;
|
||||||
private final String appKey;
|
private final String appKey;
|
||||||
private final String userName;
|
private final String userName;
|
||||||
|
@ -96,7 +95,6 @@ public class AutomowerBridge {
|
||||||
*/
|
*/
|
||||||
public void sendAutomowerCommand(String id, AutomowerCommand command, long commandDuration)
|
public void sendAutomowerCommand(String id, AutomowerCommand command, long commandDuration)
|
||||||
throws AutomowerCommunicationException {
|
throws AutomowerCommunicationException {
|
||||||
|
|
||||||
MowerCommandAttributes attributes = new MowerCommandAttributes();
|
MowerCommandAttributes attributes = new MowerCommandAttributes();
|
||||||
attributes.setDuration(commandDuration);
|
attributes.setDuration(commandDuration);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ import com.google.gson.GsonBuilder;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public abstract class HusqvarnaApi {
|
public abstract class HusqvarnaApi {
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
protected final Gson gson;
|
protected final Gson gson;
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ import com.google.gson.JsonSyntaxException;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AutomowerConnectApi extends HusqvarnaApi {
|
public class AutomowerConnectApi extends HusqvarnaApi {
|
||||||
|
|
||||||
public AutomowerConnectApi(HttpClient httpClient) {
|
public AutomowerConnectApi(HttpClient httpClient) {
|
||||||
super(httpClient);
|
super(httpClient);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +80,6 @@ public class AutomowerConnectApi extends HusqvarnaApi {
|
||||||
|
|
||||||
private ContentResponse executeRequest(String appKey, String token, final Request request)
|
private ContentResponse executeRequest(String appKey, String token, final Request request)
|
||||||
throws AutomowerCommunicationException {
|
throws AutomowerCommunicationException {
|
||||||
|
|
||||||
request.timeout(10, TimeUnit.SECONDS);
|
request.timeout(10, TimeUnit.SECONDS);
|
||||||
|
|
||||||
request.header("Authorization-Provider", "husqvarna");
|
request.header("Authorization-Provider", "husqvarna");
|
||||||
|
|
|
@ -12,9 +12,17 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.automower.internal.rest.api.automowerconnect.dto;
|
package org.openhab.binding.automower.internal.rest.api.automowerconnect.dto;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Markus Pfleger - Initial contribution
|
* @author Markus Pfleger - Initial contribution
|
||||||
|
* @author Marcin Czeczko - Added support for planner & calendar data
|
||||||
*/
|
*/
|
||||||
public class Calendar {
|
public class Calendar {
|
||||||
|
private List<CalendarTask> tasks = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<CalendarTask> getTasks() {
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 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.automower.internal.rest.api.automowerconnect.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Marcin Czeczko - Initial contribution
|
||||||
|
*/
|
||||||
|
public class CalendarTask {
|
||||||
|
/**
|
||||||
|
* Start time expressed in minutes after midnight.
|
||||||
|
*/
|
||||||
|
private Integer start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration time expressed in minutes
|
||||||
|
*/
|
||||||
|
private Integer duration;
|
||||||
|
private Boolean monday;
|
||||||
|
private Boolean tuesday;
|
||||||
|
private Boolean wednesday;
|
||||||
|
private Boolean thursday;
|
||||||
|
private Boolean friday;
|
||||||
|
private Boolean saturday;
|
||||||
|
private Boolean sunday;
|
||||||
|
|
||||||
|
public Integer getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getMonday() {
|
||||||
|
return monday;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getTuesday() {
|
||||||
|
return tuesday;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getWednesday() {
|
||||||
|
return wednesday;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getThursday() {
|
||||||
|
return thursday;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getFriday() {
|
||||||
|
return friday;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSaturday() {
|
||||||
|
return saturday;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSunday() {
|
||||||
|
return sunday;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,37 @@ package org.openhab.binding.automower.internal.rest.api.automowerconnect.dto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Markus Pfleger - Initial contribution
|
* @author Markus Pfleger - Initial contribution
|
||||||
|
* @author Marcin Czeczko - Added support for planner & calendar data
|
||||||
*/
|
*/
|
||||||
public class Planner {
|
public class Planner {
|
||||||
|
private long nextStartTimestamp;
|
||||||
|
private RestrictedReason restrictedReason;
|
||||||
|
private PlannerOverride override;
|
||||||
|
|
||||||
|
public long getNextStartTimestamp() {
|
||||||
|
return nextStartTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Planner setNextStartTimestamp(long nextStartTimestamp) {
|
||||||
|
this.nextStartTimestamp = nextStartTimestamp;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestrictedReason getRestrictedReason() {
|
||||||
|
return restrictedReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Planner setRestrictedReason(RestrictedReason restrictedReason) {
|
||||||
|
this.restrictedReason = restrictedReason;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlannerOverride getOverride() {
|
||||||
|
return override;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Planner setOverride(PlannerOverride override) {
|
||||||
|
this.override = override;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 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.automower.internal.rest.api.automowerconnect.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Marcin Czeczko - Initial contribution
|
||||||
|
*/
|
||||||
|
public class PlannerOverride {
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
public String getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlannerOverride setAction(String action) {
|
||||||
|
this.action = action;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 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.automower.internal.rest.api.automowerconnect.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Marcin Czeczko - Initial Contribution
|
||||||
|
*/
|
||||||
|
public enum RestrictedReason {
|
||||||
|
NONE,
|
||||||
|
WEEK_SCHEDULE,
|
||||||
|
PARK_OVERRIDE,
|
||||||
|
SENSOR,
|
||||||
|
DAILY_LIMIT,
|
||||||
|
NOT_APPLICABLE
|
||||||
|
}
|
|
@ -12,25 +12,47 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.automower.internal.things;
|
package org.openhab.binding.automower.internal.things;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Markus Pfleger - Initial contribution
|
* @author Markus Pfleger - Initial contribution
|
||||||
*/
|
*/
|
||||||
public enum AutomowerCommand {
|
public enum AutomowerCommand {
|
||||||
|
START("Start", "mower#start"),
|
||||||
|
RESUME_SCHEDULE("ResumeSchedule", "mower#resume_schedule"),
|
||||||
|
PAUSE("Pause", "mower#pause"),
|
||||||
|
PARK("Park", "mower#park"),
|
||||||
|
PARK_UNTIL_NEXT_SCHEDULE("ParkUntilNextSchedule", "mower#park_until_next_schedule"),
|
||||||
|
PARK_UNTIL_FURTHER_NOTICE("ParkUntilFurtherNotice", "mower#park_until_further_notice");
|
||||||
|
|
||||||
START("Start"),
|
private static final Map<String, AutomowerCommand> CHANNEL_TO_CMD_MAP = new HashMap<>();
|
||||||
RESUME_SCHEDULE("ResumeSchedule"),
|
|
||||||
PAUSE("Pause"),
|
static {
|
||||||
PARK("Park"),
|
EnumSet.allOf(AutomowerCommand.class).forEach(cmd -> CHANNEL_TO_CMD_MAP.put(cmd.getChannel(), cmd));
|
||||||
PARK_UNTIL_NEXT_SCHEDULE("ParkUntilNextSchedule"),
|
}
|
||||||
PARK_UNTIL_FURTHER_NOTICE("ParkUntilFurtherNotice");
|
|
||||||
|
|
||||||
private final String command;
|
private final String command;
|
||||||
|
private final String channel;
|
||||||
|
|
||||||
private AutomowerCommand(String command) {
|
AutomowerCommand(String command, String channel) {
|
||||||
this.command = command;
|
this.command = command;
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<AutomowerCommand> fromChannelUID(ChannelUID channelUID) {
|
||||||
|
return Optional.ofNullable(CHANNEL_TO_CMD_MAP.get(channelUID.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCommand() {
|
public String getCommand() {
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AutomowerConfiguration {
|
public class AutomowerConfiguration {
|
||||||
|
|
||||||
public @Nullable String mowerId;
|
public @Nullable String mowerId;
|
||||||
public @Nullable Integer pollingInterval;
|
public @Nullable Integer pollingInterval;
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,11 @@ package org.openhab.binding.automower.internal.things;
|
||||||
|
|
||||||
import static org.openhab.binding.automower.internal.AutomowerBindingConstants.*;
|
import static org.openhab.binding.automower.internal.AutomowerBindingConstants.*;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.*;
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -34,7 +33,10 @@ import org.openhab.binding.automower.internal.actions.AutomowerActions;
|
||||||
import org.openhab.binding.automower.internal.bridge.AutomowerBridge;
|
import org.openhab.binding.automower.internal.bridge.AutomowerBridge;
|
||||||
import org.openhab.binding.automower.internal.bridge.AutomowerBridgeHandler;
|
import org.openhab.binding.automower.internal.bridge.AutomowerBridgeHandler;
|
||||||
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.Mower;
|
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.Mower;
|
||||||
|
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.RestrictedReason;
|
||||||
|
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.State;
|
||||||
import org.openhab.binding.automower.internal.rest.exceptions.AutomowerCommunicationException;
|
import org.openhab.binding.automower.internal.rest.exceptions.AutomowerCommunicationException;
|
||||||
|
import org.openhab.core.i18n.TimeZoneProvider;
|
||||||
import org.openhab.core.library.types.DateTimeType;
|
import org.openhab.core.library.types.DateTimeType;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
@ -51,14 +53,19 @@ import org.openhab.core.thing.binding.ThingHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.openhab.core.types.Type;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link AutomowerHandler} is responsible for handling commands, which are
|
* The {@link AutomowerHandler} is responsible for handling commands, which are
|
||||||
* sent to one of the channels.
|
* sent to one of the channels.
|
||||||
*
|
*
|
||||||
* @author Markus Pfleger - Initial contribution
|
* @author Markus Pfleger - Initial contribution
|
||||||
|
* @author Marcin Czeczko - Added support for planner & calendar data
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class AutomowerHandler extends BaseThingHandler {
|
public class AutomowerHandler extends BaseThingHandler {
|
||||||
|
@ -68,12 +75,18 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||||
private static final long DEFAULT_POLLING_INTERVAL_S = TimeUnit.MINUTES.toSeconds(10);
|
private static final long DEFAULT_POLLING_INTERVAL_S = TimeUnit.MINUTES.toSeconds(10);
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(AutomowerHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(AutomowerHandler.class);
|
||||||
|
private final TimeZoneProvider timeZoneProvider;
|
||||||
|
|
||||||
private AtomicReference<String> automowerId = new AtomicReference<String>(NO_ID);
|
private AtomicReference<String> automowerId = new AtomicReference<String>(NO_ID);
|
||||||
private long lastQueryTimeMs = 0L;
|
private long lastQueryTimeMs = 0L;
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> automowerPollingJob;
|
private @Nullable ScheduledFuture<?> automowerPollingJob;
|
||||||
private long maxQueryFrequencyNanos = TimeUnit.MINUTES.toNanos(1);
|
private long maxQueryFrequencyNanos = TimeUnit.MINUTES.toNanos(1);
|
||||||
|
|
||||||
|
private @Nullable Mower mowerState;
|
||||||
|
|
||||||
|
private Gson gson = new Gson();
|
||||||
|
|
||||||
private Runnable automowerPollingRunnable = () -> {
|
private Runnable automowerPollingRunnable = () -> {
|
||||||
Bridge bridge = getBridge();
|
Bridge bridge = getBridge();
|
||||||
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
|
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
|
||||||
|
@ -83,17 +96,32 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public AutomowerHandler(Thing thing) {
|
public AutomowerHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
|
||||||
super(thing);
|
super(thing);
|
||||||
|
this.timeZoneProvider = timeZoneProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
if (command instanceof RefreshType) {
|
if (RefreshType.REFRESH == command) {
|
||||||
|
logger.debug("Refreshing channel '{}'", channelUID);
|
||||||
refreshChannels(channelUID);
|
refreshChannels(channelUID);
|
||||||
|
} else {
|
||||||
|
AutomowerCommand.fromChannelUID(channelUID).ifPresent(commandName -> {
|
||||||
|
logger.debug("Sending command '{}'", commandName);
|
||||||
|
getCommandValue(command).ifPresentOrElse(duration -> sendAutomowerCommand(commandName, duration),
|
||||||
|
() -> sendAutomowerCommand(commandName));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<Integer> getCommandValue(Type type) {
|
||||||
|
if (type instanceof DecimalType) {
|
||||||
|
return Optional.of(((DecimalType) type).intValue());
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
private void refreshChannels(ChannelUID channelUID) {
|
private void refreshChannels(ChannelUID channelUID) {
|
||||||
updateAutomowerState();
|
updateAutomowerState();
|
||||||
}
|
}
|
||||||
|
@ -121,7 +149,6 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||||
automowerId.set(configMowerId);
|
automowerId.set(configMowerId);
|
||||||
startAutomowerPolling(pollingIntervalS);
|
startAutomowerPolling(pollingIntervalS);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||||
}
|
}
|
||||||
|
@ -163,49 +190,47 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidResult(Mower mower) {
|
private boolean isValidResult(@Nullable Mower mower) {
|
||||||
return mower.getAttributes() != null && mower.getAttributes().getMetadata() != null
|
return mower != null && mower.getAttributes() != null && mower.getAttributes().getMetadata() != null
|
||||||
&& mower.getAttributes().getBattery() != null && mower.getAttributes().getSystem() != null;
|
&& mower.getAttributes().getBattery() != null && mower.getAttributes().getSystem() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isConnected(Mower mower) {
|
private boolean isConnected(@Nullable Mower mower) {
|
||||||
return mower.getAttributes() != null && mower.getAttributes().getMetadata() != null
|
return mower != null && mower.getAttributes() != null && mower.getAttributes().getMetadata() != null
|
||||||
&& mower.getAttributes().getMetadata().isConnected();
|
&& mower.getAttributes().getMetadata().isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void updateAutomowerState() {
|
private synchronized void updateAutomowerState() {
|
||||||
if (System.nanoTime() - lastQueryTimeMs > maxQueryFrequencyNanos) {
|
String id = automowerId.get();
|
||||||
lastQueryTimeMs = System.nanoTime();
|
try {
|
||||||
String id = automowerId.get();
|
AutomowerBridge automowerBridge = getAutomowerBridge();
|
||||||
try {
|
if (automowerBridge != null) {
|
||||||
AutomowerBridge automowerBridge = getAutomowerBridge();
|
if (mowerState == null || (System.nanoTime() - lastQueryTimeMs > maxQueryFrequencyNanos)) {
|
||||||
if (automowerBridge != null) {
|
lastQueryTimeMs = System.nanoTime();
|
||||||
Mower mower = automowerBridge.getAutomowerStatus(id);
|
mowerState = automowerBridge.getAutomowerStatus(id);
|
||||||
|
}
|
||||||
|
if (isValidResult(mowerState)) {
|
||||||
|
initializeProperties(mowerState);
|
||||||
|
|
||||||
if (isValidResult(mower)) {
|
updateChannelState(mowerState);
|
||||||
initializeProperties(mower);
|
|
||||||
|
|
||||||
updateChannelState(mower);
|
if (isConnected(mowerState)) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
if (isConnected(mower)) {
|
|
||||||
updateStatus(ThingStatus.ONLINE);
|
|
||||||
} else {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
|
||||||
"@text/comm-error-mower-not-connected-to-cloud");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
"@text/comm-error-query-mower-failed");
|
"@text/comm-error-mower-not-connected-to-cloud");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
"@text/conf-error-no-bridge");
|
"@text/comm-error-query-mower-failed");
|
||||||
}
|
}
|
||||||
} catch (AutomowerCommunicationException e) {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/conf-error-no-bridge");
|
||||||
"@text/comm-error-query-mower-failed");
|
|
||||||
logger.warn("Unable to query automower status for: {}. Error: {}", id, e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
} catch (AutomowerCommunicationException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"@text/comm-error-query-mower-failed");
|
||||||
|
logger.warn("Unable to query automower status for: {}. Error: {}", id, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,6 +253,7 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||||
* "Park" commands
|
* "Park" commands
|
||||||
*/
|
*/
|
||||||
public void sendAutomowerCommand(AutomowerCommand command, long commandDurationMinutes) {
|
public void sendAutomowerCommand(AutomowerCommand command, long commandDurationMinutes) {
|
||||||
|
logger.debug("Sending command '{} {}'", command.getCommand(), commandDurationMinutes);
|
||||||
String id = automowerId.get();
|
String id = automowerId.get();
|
||||||
try {
|
try {
|
||||||
AutomowerBridge automowerBridge = getAutomowerBridge();
|
AutomowerBridge automowerBridge = getAutomowerBridge();
|
||||||
|
@ -243,30 +269,53 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||||
updateAutomowerState();
|
updateAutomowerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateChannelState(Mower mower) {
|
private String restrictedState(RestrictedReason reason) {
|
||||||
if (isValidResult(mower)) {
|
return "RESTRICTED_" + reason.name();
|
||||||
updateState(CHANNEL_MOWER_NAME, new StringType(mower.getAttributes().getSystem().getName()));
|
}
|
||||||
|
|
||||||
|
private void updateChannelState(@Nullable Mower mower) {
|
||||||
|
if (isValidResult(mower)) {
|
||||||
|
updateState(CHANNEL_STATUS_NAME, new StringType(mower.getAttributes().getSystem().getName()));
|
||||||
updateState(CHANNEL_STATUS_MODE, new StringType(mower.getAttributes().getMower().getMode().name()));
|
updateState(CHANNEL_STATUS_MODE, new StringType(mower.getAttributes().getMower().getMode().name()));
|
||||||
updateState(CHANNEL_STATUS_ACTIVITY, new StringType(mower.getAttributes().getMower().getActivity().name()));
|
updateState(CHANNEL_STATUS_ACTIVITY, new StringType(mower.getAttributes().getMower().getActivity().name()));
|
||||||
updateState(CHANNEL_STATUS_STATE, new StringType(mower.getAttributes().getMower().getState().name()));
|
|
||||||
|
|
||||||
Instant statusTimestamp = Instant.ofEpochMilli(mower.getAttributes().getMetadata().getStatusTimestamp());
|
if (mower.getAttributes().getMower().getState() != State.RESTRICTED) {
|
||||||
|
updateState(CHANNEL_STATUS_STATE, new StringType(mower.getAttributes().getMower().getState().name()));
|
||||||
|
} else {
|
||||||
|
updateState(CHANNEL_STATUS_STATE,
|
||||||
|
new StringType(restrictedState(mower.getAttributes().getPlanner().getRestrictedReason())));
|
||||||
|
}
|
||||||
|
|
||||||
updateState(CHANNEL_STATUS_LAST_UPDATE,
|
updateState(CHANNEL_STATUS_LAST_UPDATE,
|
||||||
new DateTimeType(ZonedDateTime.ofInstant(statusTimestamp, ZoneId.systemDefault())));
|
new DateTimeType(toZonedDateTime(mower.getAttributes().getMetadata().getStatusTimestamp())));
|
||||||
updateState(CHANNEL_STATUS_BATTERY, new QuantityType<Dimensionless>(
|
updateState(CHANNEL_STATUS_BATTERY, new QuantityType<Dimensionless>(
|
||||||
mower.getAttributes().getBattery().getBatteryPercent(), Units.PERCENT));
|
mower.getAttributes().getBattery().getBatteryPercent(), Units.PERCENT));
|
||||||
|
|
||||||
updateState(CHANNEL_STATUS_ERROR_CODE, new DecimalType(mower.getAttributes().getMower().getErrorCode()));
|
updateState(CHANNEL_STATUS_ERROR_CODE, new DecimalType(mower.getAttributes().getMower().getErrorCode()));
|
||||||
|
|
||||||
Instant errorCodeTimestamp = Instant.ofEpochMilli(mower.getAttributes().getMower().getErrorCodeTimestamp());
|
long errorCodeTimestamp = mower.getAttributes().getMower().getErrorCodeTimestamp();
|
||||||
updateState(CHANNEL_STATUS_ERROR_TIMESTAMP,
|
if (errorCodeTimestamp == 0L) {
|
||||||
new DateTimeType(ZonedDateTime.ofInstant(errorCodeTimestamp, ZoneId.systemDefault())));
|
updateState(CHANNEL_STATUS_ERROR_TIMESTAMP, UnDefType.NULL);
|
||||||
|
} else {
|
||||||
|
updateState(CHANNEL_STATUS_ERROR_TIMESTAMP, new DateTimeType(toZonedDateTime(errorCodeTimestamp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
long nextStartTimestamp = mower.getAttributes().getPlanner().getNextStartTimestamp();
|
||||||
|
// If next start timestamp is 0 it means the mower should start now, so using current timestamp
|
||||||
|
if (nextStartTimestamp == 0L) {
|
||||||
|
updateState(CHANNEL_PLANNER_NEXT_START, UnDefType.NULL);
|
||||||
|
} else {
|
||||||
|
updateState(CHANNEL_PLANNER_NEXT_START, new DateTimeType(toZonedDateTime(nextStartTimestamp)));
|
||||||
|
}
|
||||||
|
updateState(CHANNEL_PLANNER_OVERRIDE_ACTION,
|
||||||
|
new StringType(mower.getAttributes().getPlanner().getOverride().getAction()));
|
||||||
|
|
||||||
|
updateState(CHANNEL_CALENDAR_TASKS,
|
||||||
|
new StringType(gson.toJson(mower.getAttributes().getCalendar().getTasks())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeProperties(Mower mower) {
|
private void initializeProperties(@Nullable Mower mower) {
|
||||||
Map<String, String> properties = editProperties();
|
Map<String, String> properties = editProperties();
|
||||||
|
|
||||||
properties.put(AutomowerBindingConstants.AUTOMOWER_ID, mower.getId());
|
properties.put(AutomowerBindingConstants.AUTOMOWER_ID, mower.getId());
|
||||||
|
@ -280,4 +329,18 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||||
|
|
||||||
updateProperties(properties);
|
updateProperties(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts timestamp returned by the Automower API into local time-zone.
|
||||||
|
* Timestamp returned by the API doesn't have offset and it always in the current time zone - it can be treated as
|
||||||
|
* UTC.
|
||||||
|
* Method builds a ZonedDateTime with same hour value but in the current system timezone.
|
||||||
|
*
|
||||||
|
* @param timestamp - Automower API timestamp
|
||||||
|
* @return ZonedDateTime in system timezone
|
||||||
|
*/
|
||||||
|
private ZonedDateTime toZonedDateTime(long timestamp) {
|
||||||
|
Instant timestampInstant = Instant.ofEpochMilli(timestamp);
|
||||||
|
return ZonedDateTime.ofInstant(timestampInstant, timeZoneProvider.getTimeZone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,6 @@ thing-type.config.automower.automower.timestamp.label = Last State Update
|
||||||
thing-type.config.automower.automower.batteryPct.label = Battery Percentage
|
thing-type.config.automower.automower.batteryPct.label = Battery Percentage
|
||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
channel-type.automower.name.label = Name
|
|
||||||
channel-type.automower.name.description = Automower name
|
|
||||||
channel-type.automower.mode.label = Mode
|
channel-type.automower.mode.label = Mode
|
||||||
channel-type.automower.mode.description = Mode
|
channel-type.automower.mode.description = Mode
|
||||||
channel-type.automower.activity.label = Activity
|
channel-type.automower.activity.label = Activity
|
||||||
|
@ -23,7 +21,14 @@ channel-type.automower.state.label = State
|
||||||
channel-type.automower.state.description = State
|
channel-type.automower.state.description = State
|
||||||
channel-type.automower.last-update.label = Last Update
|
channel-type.automower.last-update.label = Last Update
|
||||||
channel-type.automower.last-update.description = Last Update
|
channel-type.automower.last-update.description = Last Update
|
||||||
|
channel-type.automower.planner-next-start.label= Next planned start
|
||||||
|
channel-type.automower.planner-next-start.description= Next planned start
|
||||||
|
channel-type.automower.planner-override-action.label = Planner override action
|
||||||
|
channel-type.automower.planner-override-action.description = Planner override action
|
||||||
|
channel-type.automower.planner-restricted-reason.label = Planner restriction
|
||||||
|
channel-type.automower.planner-restricted-reason.description = Planner restriction
|
||||||
|
channel-type.automower.calendar-tasks.label = The information about the planner as JSON
|
||||||
|
channel-type.automower.calendar-tasks.description = The information about the planner as JSON
|
||||||
|
|
||||||
conf-error-no-app-key = Cannot connect to Automower bridge as no app key is available in the configuration
|
conf-error-no-app-key = Cannot connect to Automower bridge as no app key is available in the configuration
|
||||||
conf-error-no-username = Cannot connect to Automower bridge as no username is available in the configuration
|
conf-error-no-username = Cannot connect to Automower bridge as no username is available in the configuration
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
<config-description>
|
<config-description>
|
||||||
<parameter name="appKey" type="text" required="true">
|
<parameter name="appKey" type="text" required="true">
|
||||||
<label>Application Key</label>
|
<label>Application Key</label>
|
||||||
<description>The Application Key is required to communication with the Automower Connect Api. It can be obtained by
|
<description>The Application Key is required to communication with the Automower Connect Api at
|
||||||
registering an Application on the Husqvarna Website. This application also needs to be connected to the
|
https://developer.husqvarnagroup.cloud/. It can be obtained by
|
||||||
|
registering an Application on the Husqvarna Website.
|
||||||
|
This application also needs to be connected to the
|
||||||
"Authentication API" and the "Automower Connect API"</description>
|
"Authentication API" and the "Automower Connect API"</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="userName" type="text" required="true">
|
<parameter name="userName" type="text" required="true">
|
||||||
|
@ -21,7 +23,7 @@
|
||||||
<description>The user name for which the application key has been issued</description>
|
<description>The user name for which the application key has been issued</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="password" type="text" required="true">
|
<parameter name="password" type="text" required="true">
|
||||||
<context>Password</context>
|
<context>password</context>
|
||||||
<description>The password for the given user</description>
|
<description>The password for the given user</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="pollingInterval" type="integer" required="false" unit="s">
|
<parameter name="pollingInterval" type="integer" required="false" unit="s">
|
||||||
|
@ -48,9 +50,18 @@
|
||||||
<channel id="activity" typeId="activityType"/>
|
<channel id="activity" typeId="activityType"/>
|
||||||
<channel id="state" typeId="stateType"/>
|
<channel id="state" typeId="stateType"/>
|
||||||
<channel id="last-update" typeId="lastUpdateType"/>
|
<channel id="last-update" typeId="lastUpdateType"/>
|
||||||
<channel id="battery" typeId="batteryType"/>
|
<channel id="battery" typeId="system.battery-level"/>
|
||||||
<channel id="error-code" typeId="errorCodeType"/>
|
<channel id="error-code" typeId="errorCodeType"/>
|
||||||
<channel id="error-timestamp" typeId="errorTimestampType"/>
|
<channel id="error-timestamp" typeId="errorTimestampType"/>
|
||||||
|
<channel id="planner-next-start" typeId="plannerNextStartTimestampType"/>
|
||||||
|
<channel id="planner-override-action" typeId="plannerOverrideActionType"/>
|
||||||
|
<channel id="calendar-tasks" typeId="calendarTasksType"/>
|
||||||
|
<channel id="start" typeId="start"/>
|
||||||
|
<channel id="resume_schedule" typeId="resumeSchedule"/>
|
||||||
|
<channel id="pause" typeId="pause"/>
|
||||||
|
<channel id="park" typeId="park"/>
|
||||||
|
<channel id="park_until_next_schedule" typeId="parkUntilNextSchedule"/>
|
||||||
|
<channel id="park_until_further_notice" typeId="parkUntilFurtherNotice"/>
|
||||||
</channels>
|
</channels>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
@ -126,7 +137,11 @@
|
||||||
<option value="IN_OPERATION">Working</option>
|
<option value="IN_OPERATION">Working</option>
|
||||||
<option value="WAIT_UPDATING">Downloading new firmware</option>
|
<option value="WAIT_UPDATING">Downloading new firmware</option>
|
||||||
<option value="WAIT_POWER_UP">Booting mower</option>
|
<option value="WAIT_POWER_UP">Booting mower</option>
|
||||||
<option value="RESTRICTED">Waiting</option>
|
<option value="RESTRICTED_NONE">Waiting</option>
|
||||||
|
<option value="RESTRICTED_WEEK_SCHEDULE">Restricted by week schedule</option>
|
||||||
|
<option value="RESTRICTED_PARK_OVERRIDE">Forced to park</option>
|
||||||
|
<option value="RESTRICTED_SENSOR">Restricted by sensor</option>
|
||||||
|
<option value="RESTRICTED_DAILY_LIMIT">Restricted by daily limit</option>
|
||||||
<option value="OFF">Off</option>
|
<option value="OFF">Off</option>
|
||||||
<option value="STOPPED">Stopped- Manual intervention required</option>
|
<option value="STOPPED">Stopped- Manual intervention required</option>
|
||||||
<option value="ERROR">Error</option>
|
<option value="ERROR">Error</option>
|
||||||
|
@ -164,4 +179,64 @@
|
||||||
<state readOnly="true"/>
|
<state readOnly="true"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="plannerNextStartTimestampType">
|
||||||
|
<item-type>DateTime</item-type>
|
||||||
|
<label>Next Auto Start</label>
|
||||||
|
<description>The channel providing the time for the next auto start. If the mower is charging then the value is the
|
||||||
|
estimated time when it will be leaving the charging station. If the mower is about to start now, the value is NULL.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="plannerOverrideActionType">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Override Action</label>
|
||||||
|
<description>The channel providing an action that overrides current planner operation.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="calendarTasksType">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Planner Info JSON</label>
|
||||||
|
<description>The channel providing a JSON with the information about the planner tasks.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="start">
|
||||||
|
<item-type>Number</item-type>
|
||||||
|
<label>Start with Duration</label>
|
||||||
|
<description>Start for a duration in minutes</description>
|
||||||
|
<state pattern="%d min" min="1" max="1440" step="1"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="resumeSchedule">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Resume Schedule</label>
|
||||||
|
<description>Resume schedule</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="pause">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Pause</label>
|
||||||
|
<description>Pause the mower now until manual resume</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="park">
|
||||||
|
<item-type>Number</item-type>
|
||||||
|
<label>Park with Duration</label>
|
||||||
|
<description>Park for a duration in minutes</description>
|
||||||
|
<state pattern="%d min" min="1" max="1440" step="1"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="parkUntilNextSchedule">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Park Until Next Schedule</label>
|
||||||
|
<description>Park until next schedule</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="parkUntilFurtherNotice">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Park and Pause the Schedule</label>
|
||||||
|
<description>Park and pause the mower schedule until manual resume</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
|
Loading…
Reference in New Issue