Skip to content

Commit 4ce97d5

Browse files
authored
[automower] Implementation of complete automower API (openhab#17545)
* started implementation of complete automower API Signed-off-by: Michael Weger <[email protected]>
1 parent 1b90467 commit 4ce97d5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+4009
-351
lines changed

bundles/org.openhab.binding.automower/README.md

+189-122
Large diffs are not rendered by default.

bundles/org.openhab.binding.automower/src/main/java/org/openhab/binding/automower/internal/AutomowerBindingConstants.java

+311-37
Large diffs are not rendered by default.

bundles/org.openhab.binding.automower/src/main/java/org/openhab/binding/automower/internal/AutomowerHandlerFactory.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.openhab.binding.automower.internal.things.AutomowerHandler;
2828
import org.openhab.core.auth.client.oauth2.OAuthFactory;
2929
import org.openhab.core.config.discovery.DiscoveryService;
30+
import org.openhab.core.i18n.TimeZoneProvider;
3031
import org.openhab.core.io.net.http.HttpClientFactory;
3132
import org.openhab.core.thing.Bridge;
3233
import org.openhab.core.thing.Thing;
@@ -55,12 +56,14 @@ public class AutomowerHandlerFactory extends BaseThingHandlerFactory {
5556
private final OAuthFactory oAuthFactory;
5657
protected final @NonNullByDefault({}) HttpClient httpClient;
5758
private @Nullable ServiceRegistration<?> automowerDiscoveryServiceRegistration;
59+
private final TimeZoneProvider timeZoneProvider;
5860

5961
@Activate
60-
public AutomowerHandlerFactory(@Reference OAuthFactory oAuthFactory,
61-
@Reference HttpClientFactory httpClientFactory) {
62+
public AutomowerHandlerFactory(@Reference OAuthFactory oAuthFactory, @Reference HttpClientFactory httpClientFactory,
63+
@Reference TimeZoneProvider timeZoneProvider) {
6264
this.oAuthFactory = oAuthFactory;
6365
this.httpClient = httpClientFactory.getCommonHttpClient();
66+
this.timeZoneProvider = timeZoneProvider;
6467
}
6568

6669
@Override
@@ -77,7 +80,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
7780
}
7881

7982
if (AutomowerHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
80-
return new AutomowerHandler(thing);
83+
return new AutomowerHandler(thing, timeZoneProvider);
8184
}
8285

8386
return null;

bundles/org.openhab.binding.automower/src/main/java/org/openhab/binding/automower/internal/actions/AutomowerActions.java

+128
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import org.eclipse.jdt.annotation.NonNullByDefault;
1616
import org.eclipse.jdt.annotation.Nullable;
17+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.HeadlightMode;
1718
import org.openhab.binding.automower.internal.things.AutomowerCommand;
1819
import org.openhab.binding.automower.internal.things.AutomowerHandler;
1920
import org.openhab.core.automation.annotation.ActionInput;
@@ -131,4 +132,131 @@ public void resumeSchedule() {
131132
public static void resumeSchedule(ThingActions actions) {
132133
((AutomowerActions) actions).resumeSchedule();
133134
}
135+
136+
@RuleAction(label = "@text/action-confirm-error-label", description = "@text/action-confirm-error-desc")
137+
public void confirmError() {
138+
AutomowerHandler automowerHandler = handler;
139+
if (automowerHandler == null) {
140+
logger.warn("Automower Action service ThingHandler is null!");
141+
} else {
142+
automowerHandler.sendAutomowerConfirmError();
143+
}
144+
}
145+
146+
public static void confirmError(ThingActions actions) {
147+
((AutomowerActions) actions).confirmError();
148+
}
149+
150+
@RuleAction(label = "@text/action-reset-cutting-blade-usage-time-label", description = "@text/action-reset-cutting-blade-usage-time-desc")
151+
public void resetCuttingBladeUsageTime() {
152+
AutomowerHandler automowerHandler = handler;
153+
if (automowerHandler == null) {
154+
logger.warn("Automower Action service ThingHandler is null!");
155+
} else {
156+
automowerHandler.sendAutomowerResetCuttingBladeUsageTime();
157+
}
158+
}
159+
160+
public static void resetCuttingBladeUsageTime(ThingActions actions) {
161+
((AutomowerActions) actions).resetCuttingBladeUsageTime();
162+
}
163+
164+
@RuleAction(label = "@text/action-set-settings-label", description = "@text/action-set-settings-desc")
165+
public void setSettings(
166+
@ActionInput(name = "cutting-height", label = "@text/action-input-cutting-height-label", description = "@text/action-input-cutting-height-desc") @Nullable Byte cuttingHeight,
167+
@ActionInput(name = "headlight-mode", label = "@text/action-input-headlight-mode-label", description = "@text/action-input-headlight-mode-desc") @Nullable String headlightMode) {
168+
AutomowerHandler automowerHandler = handler;
169+
if (automowerHandler == null) {
170+
logger.warn("Automower Action service ThingHandler is null!");
171+
} else {
172+
try {
173+
if (headlightMode != null) {
174+
automowerHandler.sendAutomowerSettings(cuttingHeight, HeadlightMode.valueOf(headlightMode));
175+
} else {
176+
automowerHandler.sendAutomowerSettings(cuttingHeight, null);
177+
}
178+
} catch (IllegalArgumentException e) {
179+
logger.warn("Invalid HeadlightMode: {}, Error: {}", headlightMode, e.getMessage());
180+
}
181+
}
182+
}
183+
184+
public static void setSettings(ThingActions actions, @Nullable Byte cuttingHeight, @Nullable String headlightMode) {
185+
((AutomowerActions) actions).setSettings(cuttingHeight, headlightMode);
186+
}
187+
188+
@RuleAction(label = "@text/action-set-work-area-label", description = "@text/action-set-work-area-desc")
189+
public void setWorkArea(
190+
@ActionInput(name = "workarea-id", label = "@text/action-input-workarea-id-label", description = "@text/action-input-workarea-id-desc") long workAreaId,
191+
@ActionInput(name = "enable", label = "@text/action-input-enable-label", description = "@text/action-input-enable-desc") boolean enable,
192+
@ActionInput(name = "cutting-height", label = "@text/action-input-cutting-height-label", description = "@text/action-input-cutting-height-desc") byte cuttingHeight) {
193+
AutomowerHandler automowerHandler = handler;
194+
if (automowerHandler == null) {
195+
logger.warn("Automower Action service ThingHandler is null!");
196+
} else {
197+
automowerHandler.sendAutomowerWorkArea(workAreaId, enable, cuttingHeight);
198+
}
199+
}
200+
201+
public static void setWorkArea(ThingActions actions, long workAreaId, boolean enable, byte cuttingHeight) {
202+
((AutomowerActions) actions).setWorkArea(workAreaId, enable, cuttingHeight);
203+
}
204+
205+
@RuleAction(label = "@text/action-set-stayoutzone-label", description = "@text/action-set-stayoutzone-desc")
206+
public void setStayOutZone(
207+
@ActionInput(name = "zone-id", label = "@text/action-input-zone-id-label", description = "@text/action-input-zone-id-desc") String zoneId,
208+
@ActionInput(name = "enable", label = "@text/action-input-enable-label", description = "@text/action-input-enable-desc") boolean enable) {
209+
AutomowerHandler automowerHandler = handler;
210+
if (automowerHandler == null) {
211+
logger.warn("Automower Action service ThingHandler is null!");
212+
} else {
213+
automowerHandler.sendAutomowerStayOutZone(zoneId, enable);
214+
}
215+
}
216+
217+
public static void setStayOutZone(ThingActions actions, String zoneId, boolean enable) {
218+
((AutomowerActions) actions).setStayOutZone(zoneId, enable);
219+
}
220+
221+
@RuleAction(label = "@text/action-set-calendartask-label", description = "@text/action-set-calendartask-desc")
222+
public void setCalendarTask(
223+
@ActionInput(name = "workarea-id", label = "@text/action-input-workarea-id-label", description = "@text/action-input-workarea-id-desc") @Nullable Long workAreaId,
224+
@ActionInput(name = "start", label = "@text/action-input-start-label", description = "@text/action-input-start-desc") short[] start,
225+
@ActionInput(name = "duration", label = "@text/action-input-duration-label", description = "@text/action-input-duration-desc") short[] duration,
226+
@ActionInput(name = "monday", label = "@text/action-input-monday-label", description = "@text/action-input-monday-desc") boolean[] monday,
227+
@ActionInput(name = "tuesday", label = "@text/action-input-tuesday-label", description = "@text/action-input-tuesday-desc") boolean[] tuesday,
228+
@ActionInput(name = "wednesday", label = "@text/action-input-wednesday-label", description = "@text/action-input-wednesday-desc") boolean[] wednesday,
229+
@ActionInput(name = "thursday", label = "@text/action-input-thursday-label", description = "@text/action-input-thursday-desc") boolean[] thursday,
230+
@ActionInput(name = "friday", label = "@text/action-input-friday-label", description = "@text/action-input-friday-desc") boolean[] friday,
231+
@ActionInput(name = "saturday", label = "@text/action-input-saturday-label", description = "@text/action-input-saturday-desc") boolean[] saturday,
232+
@ActionInput(name = "sunday", label = "@text/action-input-sunday-label", description = "@text/action-input-sunday-desc") boolean[] sunday) {
233+
AutomowerHandler automowerHandler = handler;
234+
if (automowerHandler == null) {
235+
logger.warn("Automower Action service ThingHandler is null!");
236+
} else {
237+
automowerHandler.sendAutomowerCalendarTask(workAreaId, start, duration, monday, tuesday, wednesday,
238+
thursday, friday, saturday, sunday);
239+
}
240+
}
241+
242+
public static void setCalendarTask(ThingActions actions, @Nullable Long workAreaId, short[] start, short[] duration,
243+
boolean[] monday, boolean[] tuesday, boolean[] wednesday, boolean[] thursday, boolean[] friday,
244+
boolean[] saturday, boolean[] sunday) {
245+
((AutomowerActions) actions).setCalendarTask(workAreaId, start, duration, monday, tuesday, wednesday, thursday,
246+
friday, saturday, sunday);
247+
}
248+
249+
@RuleAction(label = "@text/action-poll-label", description = "@text/action-poll-desc")
250+
public void poll() {
251+
AutomowerHandler automowerHandler = handler;
252+
if (automowerHandler == null) {
253+
logger.warn("Automower Action service ThingHandler is null!");
254+
} else {
255+
automowerHandler.poll();
256+
}
257+
}
258+
259+
public static void poll(ThingActions actions) {
260+
((AutomowerActions) actions).poll();
261+
}
134262
}

bundles/org.openhab.binding.automower/src/main/java/org/openhab/binding/automower/internal/bridge/AutomowerBridge.java

+130
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,32 @@
1414

1515
import java.io.IOException;
1616
import java.time.Instant;
17+
import java.util.List;
1718
import java.util.concurrent.ScheduledExecutorService;
1819

1920
import org.eclipse.jdt.annotation.NonNullByDefault;
21+
import org.eclipse.jdt.annotation.Nullable;
2022
import org.eclipse.jetty.client.HttpClient;
2123
import org.openhab.binding.automower.internal.rest.api.automowerconnect.AutomowerConnectApi;
24+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.Calendar;
25+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.CalendarTask;
2226
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.Mower;
27+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerCalendar;
28+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerCalendardRequest;
2329
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerCommand;
2430
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerCommandAttributes;
2531
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerCommandRequest;
2632
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerListResult;
33+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerMessages;
34+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerSettings;
35+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerSettingsRequest;
36+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerStayOutZone;
37+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerStayOutZoneAttributes;
38+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerStayOutZoneRequest;
39+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerWorkArea;
40+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerWorkAreaAttributes;
41+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.MowerWorkAreaRequest;
42+
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.Settings;
2743
import org.openhab.binding.automower.internal.rest.exceptions.AutomowerCommunicationException;
2844
import org.openhab.binding.automower.internal.things.AutomowerCommand;
2945
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
@@ -81,6 +97,15 @@ public Mower getAutomowerStatus(String id) throws AutomowerCommunicationExceptio
8197
return automowerApi.getMower(appKey, authenticate().getAccessToken(), id).getData();
8298
}
8399

100+
/**
101+
* @param id The id of the mower to query
102+
* @return A detailed status of the mower with the specified id
103+
* @throws AutomowerCommunicationException In case the query cannot be executed successfully
104+
*/
105+
public MowerMessages getAutomowerMessages(String id) throws AutomowerCommunicationException {
106+
return automowerApi.getMowerMessages(appKey, authenticate().getAccessToken(), id).getData();
107+
}
108+
84109
/**
85110
* Sends a command to the automower with the specified id
86111
*
@@ -103,4 +128,109 @@ public void sendAutomowerCommand(String id, AutomowerCommand command, long comma
103128
request.setData(mowerCommand);
104129
automowerApi.sendCommand(appKey, authenticate().getAccessToken(), id, request);
105130
}
131+
132+
/**
133+
* Sends a calendarTask to the automower
134+
*
135+
* @param id The id of the mower
136+
* @param hasWorkAreas Work area capability of the mower
137+
* @param workAreaId The Id of the work area this calendar belongs to (or null, if there is no work area support)
138+
* @param calendarTasks The calendar that should be sent. It is using the same json structure (start, duration, ...)
139+
* as provided when reading the channel
140+
* @throws AutomowerCommunicationException In case the query cannot be executed successfully
141+
*/
142+
public void sendAutomowerCalendarTask(String id, boolean hasWorkAreas, @Nullable Long workAreaId,
143+
List<CalendarTask> calendarTasks) throws AutomowerCommunicationException {
144+
Calendar calendar = new Calendar();
145+
calendar.setTasks(calendarTasks);
146+
147+
MowerCalendar mowerCalendar = new MowerCalendar();
148+
mowerCalendar.setType("calendar");
149+
mowerCalendar.setAttributes(calendar);
150+
151+
MowerCalendardRequest calendarRequest = new MowerCalendardRequest();
152+
calendarRequest.setData(mowerCalendar);
153+
154+
automowerApi.sendCalendar(appKey, authenticate().getAccessToken(), id, hasWorkAreas, workAreaId,
155+
calendarRequest);
156+
}
157+
158+
/**
159+
* Sends Settings to the automower
160+
*
161+
* @param id The id of the mower
162+
* @param settings The Settings that should be sent. It is using the same json structure
163+
* as provided when reading the channel
164+
* @throws AutomowerCommunicationException In case the query cannot be executed successfully
165+
*/
166+
public void sendAutomowerSettings(String id, Settings settings) throws AutomowerCommunicationException {
167+
MowerSettings mowerSettings = new MowerSettings();
168+
mowerSettings.setType("settings");
169+
mowerSettings.setAttributes(settings);
170+
171+
MowerSettingsRequest settingsRequest = new MowerSettingsRequest();
172+
settingsRequest.setData(mowerSettings);
173+
174+
automowerApi.sendSettings(appKey, authenticate().getAccessToken(), id, settingsRequest);
175+
}
176+
177+
/**
178+
* Confirm current non fatal error on the mower
179+
*
180+
* @param id The id of the mower
181+
* @throws AutomowerCommunicationException In case the query cannot be executed successfully
182+
*/
183+
public void sendAutomowerConfirmError(String id) throws AutomowerCommunicationException {
184+
automowerApi.sendConfirmError(appKey, authenticate().getAccessToken(), id);
185+
}
186+
187+
/**
188+
* Reset the cutting blade usage time
189+
*
190+
* @param id The id of the mower
191+
* @throws AutomowerCommunicationException In case the query cannot be executed successfully
192+
*/
193+
public void sendAutomowerResetCuttingBladeUsageTime(String id) throws AutomowerCommunicationException {
194+
automowerApi.sendResetCuttingBladeUsageTime(appKey, authenticate().getAccessToken(), id);
195+
}
196+
197+
/**
198+
* Enable or disable stay out zone
199+
*
200+
* @param id The id of the mower
201+
* @param zoneId The id of the stay out zone
202+
* @param zoneAttributes The new zone status
203+
* @throws AutomowerCommunicationException In case the query cannot be executed successfully
204+
*/
205+
public void sendAutomowerStayOutZone(String id, String zoneId, MowerStayOutZoneAttributes zoneAttributes)
206+
throws AutomowerCommunicationException {
207+
MowerStayOutZone zoneData = new MowerStayOutZone();
208+
zoneData.setType("stayOutZone");
209+
zoneData.setId(zoneId);
210+
zoneData.setAttributes(zoneAttributes);
211+
MowerStayOutZoneRequest zoneRequest = new MowerStayOutZoneRequest();
212+
zoneRequest.setData(zoneData);
213+
214+
automowerApi.sendStayOutZone(appKey, authenticate().getAccessToken(), id, zoneId, zoneRequest);
215+
}
216+
217+
/**
218+
* Update a work area setting
219+
*
220+
* @param id The id of the mower
221+
* @param workAreaId The id of the work area
222+
* @param workAreaAttributes The new work area status
223+
* @throws AutomowerCommunicationException In case the query cannot be executed successfully
224+
*/
225+
public void sendAutomowerWorkArea(String id, long workAreaId, MowerWorkAreaAttributes workAreaAttributes)
226+
throws AutomowerCommunicationException {
227+
MowerWorkArea workAreaData = new MowerWorkArea();
228+
workAreaData.setType("workArea");
229+
workAreaData.setId(workAreaId);
230+
workAreaData.setAttributes(workAreaAttributes);
231+
MowerWorkAreaRequest workAreaRequest = new MowerWorkAreaRequest();
232+
workAreaRequest.setData(workAreaData);
233+
234+
automowerApi.sendWorkArea(appKey, authenticate().getAccessToken(), id, workAreaId, workAreaRequest);
235+
}
106236
}

bundles/org.openhab.binding.automower/src/main/java/org/openhab/binding/automower/internal/discovery/AutomowerDiscoveryService.java

+11
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ protected void startScan() {
6363
properties.put(AutomowerBindingConstants.AUTOMOWER_MODEL, mower.getAttributes().getSystem().getModel());
6464
properties.put(AutomowerBindingConstants.AUTOMOWER_NAME, mower.getAttributes().getSystem().getName());
6565

66+
properties.put(AutomowerBindingConstants.AUTOMOWER_CAN_CONFIRM_ERROR,
67+
(mower.getAttributes().getCapabilities().canConfirmError() ? "yes" : "no"));
68+
properties.put(AutomowerBindingConstants.AUTOMOWER_HAS_HEADLIGHTS,
69+
(mower.getAttributes().getCapabilities().hasHeadlights() ? "yes" : "no"));
70+
properties.put(AutomowerBindingConstants.AUTOMOWER_HAS_POSITION,
71+
(mower.getAttributes().getCapabilities().hasPosition() ? "yes" : "no"));
72+
properties.put(AutomowerBindingConstants.AUTOMOWER_HAS_STAY_OUT_ZONES,
73+
(mower.getAttributes().getCapabilities().hasStayOutZones() ? "yes" : "no"));
74+
properties.put(AutomowerBindingConstants.AUTOMOWER_HAS_WORK_AREAS,
75+
(mower.getAttributes().getCapabilities().hasWorkAreas() ? "yes" : "no"));
76+
6677
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(mowerThingUid)
6778
.withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID)
6879
.withRepresentationProperty(AutomowerBindingConstants.AUTOMOWER_ID)

0 commit comments

Comments
 (0)