import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.binding.gardena.internal.model.dto.api.CreateWebSocketRequest;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
+import org.openhab.binding.gardena.internal.model.dto.api.Location;
import org.openhab.binding.gardena.internal.model.dto.api.LocationDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.LocationResponse;
import org.openhab.binding.gardena.internal.model.dto.api.LocationsResponse;
import org.openhab.binding.gardena.internal.model.dto.api.PostOAuth2Response;
+import org.openhab.binding.gardena.internal.model.dto.api.WebSocket;
import org.openhab.binding.gardena.internal.model.dto.api.WebSocketCreatedResponse;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommand;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommandRequest;
private void startWebsockets() throws Exception {
for (LocationDataItem location : locationsResponse.data) {
WebSocketCreatedResponse webSocketCreatedResponse = getWebsocketInfo(location.id);
- String socketId = id + "-" + location.attributes.name;
+ Location locationAttributes = location.attributes;
+ WebSocket webSocketAttributes = webSocketCreatedResponse.data.attributes;
+ if (locationAttributes == null || webSocketAttributes == null) {
+ continue;
+ }
+ String socketId = id + "-" + locationAttributes.name;
webSockets.put(location.id, new GardenaSmartWebSocket(this, webSocketClient, scheduler,
- webSocketCreatedResponse.data.attributes.url, token, socketId, location.id));
+ webSocketAttributes.url, token, socketId, location.id));
}
}
Thread.sleep(3000);
WebSocketCreatedResponse webSocketCreatedResponse = getWebsocketInfo(socket.getLocationID());
// only restart single socket, do not restart binding
- socket.restart(webSocketCreatedResponse.data.attributes.url);
+ WebSocket webSocketAttributes = webSocketCreatedResponse.data.attributes;
+ if (webSocketAttributes != null) {
+ socket.restart(webSocketAttributes.url);
+ }
} catch (Exception ex) {
// restart binding on error
logger.warn("Restarting GardenaSmart Webservice failed ({}): {}, restarting binding", socket.getSocketID(),
import org.openhab.binding.gardena.internal.exception.GardenaDeviceNotFoundException;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.dto.Device;
+import org.openhab.binding.gardena.internal.model.dto.api.CommonService;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommand;
import org.openhab.binding.gardena.internal.model.dto.command.MowerCommand;
ThingStatus newStatus = ThingStatus.ONLINE;
ThingStatusDetail newDetail = ThingStatusDetail.NONE;
- if (!CONNECTION_STATUS_ONLINE.equals(device.common.attributes.rfLinkState.value)) {
+ CommonService commonServiceAttributes = device.common.attributes;
+ if (commonServiceAttributes == null
+ || !CONNECTION_STATUS_ONLINE.equals(commonServiceAttributes.rfLinkState.value)) {
newStatus = ThingStatus.OFFLINE;
newDetail = ThingStatusDetail.COMMUNICATION_ERROR;
}
import java.util.Map;
import org.openhab.binding.gardena.internal.exception.GardenaException;
+import org.openhab.binding.gardena.internal.model.dto.api.CommonService;
import org.openhab.binding.gardena.internal.model.dto.api.CommonServiceDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import org.openhab.binding.gardena.internal.model.dto.api.DeviceDataItem;
+import org.openhab.binding.gardena.internal.model.dto.api.Location;
import org.openhab.binding.gardena.internal.model.dto.api.LocationDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.MowerServiceDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.PowerSocketServiceDataItem;
*/
public void evaluateDeviceType() {
if (deviceType == null) {
- if (common.attributes.modelType.value.toLowerCase().startsWith(DEVICE_TYPE_PREFIX)) {
- String modelType = common.attributes.modelType.value.toLowerCase();
+ CommonService commonServiceAttributes = common.attributes;
+ if (commonServiceAttributes != null
+ && commonServiceAttributes.modelType.value.toLowerCase().startsWith(DEVICE_TYPE_PREFIX)) {
+ String modelType = commonServiceAttributes.modelType.value.toLowerCase();
modelType = modelType.substring(14);
deviceType = modelType.replace(" ", "_");
} else {
// ignore
} else if (dataItem instanceof LocationDataItem) {
LocationDataItem locationDataItem = (LocationDataItem) dataItem;
- if (locationDataItem.attributes != null) {
- location = locationDataItem.attributes.name;
+ Location locationAttributes = locationDataItem.attributes;
+ if (locationAttributes != null) {
+ location = locationAttributes.name;
}
} else if (dataItem instanceof CommonServiceDataItem) {
common = (CommonServiceDataItem) dataItem;
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class CommonServiceDataItem extends DataItem<CommonService> {
+public class CommonServiceDataItem extends DataItem<@NonNull CommonService> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class CreateWebSocketDataItem extends DataItem<CreateWebSocket> {
+public class CreateWebSocketDataItem extends DataItem<@NonNull CreateWebSocket> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
+@NonNullByDefault
public class CreateWebSocketRequest {
public CreateWebSocketDataItem data;
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gardena.internal.util.StringUtils;
/**
* @author Gerhard Riegler - Initial contribution
*/
-public class DataItem<T> {
+public class DataItem<@NonNull T> {
public String id;
public String type;
return StringUtils.substringBeforeLast(id, ":");
}
- public T attributes;
+ public @Nullable T attributes;
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class DeviceDataItem extends DataItem<Void> {
+public class DeviceDataItem extends DataItem<@NonNull Void> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class LocationDataItem extends DataItem<Location> {
+public class LocationDataItem extends DataItem<@NonNull Location> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class MowerServiceDataItem extends DataItem<MowerService> {
+public class MowerServiceDataItem extends DataItem<@NonNull MowerService> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class PowerSocketServiceDataItem extends DataItem<PowerSocketService> {
+public class PowerSocketServiceDataItem extends DataItem<@NonNull PowerSocketService> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class SensorServiceDataItem extends DataItem<SensorService> {
+public class SensorServiceDataItem extends DataItem<@NonNull SensorService> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class ValveServiceDataItem extends DataItem<ValveService> {
+public class ValveServiceDataItem extends DataItem<@NonNull ValveService> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class ValveSetServiceDataItem extends DataItem<ValveSetService> {
+public class ValveSetServiceDataItem extends DataItem<@NonNull ValveSetService> {
}
*/
package org.openhab.binding.gardena.internal.model.dto.api;
+import org.eclipse.jdt.annotation.NonNull;
+
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
-public class WebSocketDataItem extends DataItem<WebSocket> {
+public class WebSocketDataItem extends DataItem<@NonNull WebSocket> {
}
--- /dev/null
+/**
+ * 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.gardena;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.gardena.internal.model.DataItemDeserializer;
+import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
+import org.openhab.binding.gardena.internal.model.dto.api.DeviceDataItem;
+import org.openhab.binding.gardena.internal.model.dto.api.SensorService;
+import org.openhab.binding.gardena.internal.model.dto.api.SensorServiceDataItem;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * Tests for {@link DataItem} deserialization.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class DataItemTest {
+
+ private Gson gson = new GsonBuilder().registerTypeAdapter(DataItem.class, new DataItemDeserializer()).create();
+
+ public <T> T getObjectFromJson(String filename, Class<T> clazz) throws IOException {
+ try (InputStream inputStream = DataItemTest.class.getResourceAsStream(filename)) {
+ if (inputStream == null) {
+ throw new IOException("inputstream is null");
+ }
+ byte[] bytes = inputStream.readAllBytes();
+ if (bytes == null) {
+ throw new IOException("Resulting byte-array empty");
+ }
+ String json = new String(bytes, StandardCharsets.UTF_8);
+ return Objects.requireNonNull(gson.fromJson(json, clazz));
+ }
+ }
+
+ @Test
+ public void sensorServiceWellformed() throws IOException {
+ DataItem<?> dataItem = getObjectFromJson("SensorServiceDataItem.json", DataItem.class);
+ assertInstanceOf(SensorServiceDataItem.class, dataItem);
+ assertEquals("SENSOR", dataItem.type);
+ SensorService attributes = ((SensorServiceDataItem) dataItem).attributes;
+ assertNotNull(attributes);
+ assertEquals(55, attributes.soilHumidity.value);
+ }
+
+ @Test
+ public void sensorServiceNoAttributes() throws IOException {
+ DataItem<?> dataItem = getObjectFromJson("SensorServiceDataItemNoAttributes.json", DataItem.class);
+ assertInstanceOf(SensorServiceDataItem.class, dataItem);
+ assertEquals("SENSOR", dataItem.type);
+ assertNull((SensorServiceDataItem) dataItem.attributes);
+ }
+
+ @Test
+ public void device() throws IOException {
+ DataItem<?> dataItem = getObjectFromJson("DeviceDataItem.json", DataItem.class);
+ assertInstanceOf(DeviceDataItem.class, dataItem);
+ assertEquals("DEVICE", dataItem.type);
+ assertNull((SensorServiceDataItem) dataItem.attributes);
+ }
+}
--- /dev/null
+{
+ "id": "0162efc2-0b62-4983-aa1d-d72442008b7c",
+ "type": "DEVICE",
+ "relationships": {
+ "location": {
+ "data": {
+ "id": "d01b4d00-945a-4639-bf5a-f73d2030dfe0",
+ "type": "LOCATION"
+ }
+ },
+ "services": {
+ "data": [
+ {
+ "id": "0162efc2-0b62-4983-aa1d-d72442008b7c",
+ "type": "SENSOR"
+ },
+ {
+ "id": "0162efc2-0b62-4983-aa1d-d72442008b7c",
+ "type": "COMMON"
+ }
+ ]
+ }
+ }
+}
--- /dev/null
+{
+ "id": "638e82cd-774c-4f34-be10-2eec41d1c0a6",
+ "type": "SENSOR",
+ "relationships": {
+ "device": {
+ "data": {
+ "id": "638e82cd-774c-4f34-be10-2eec41d1c0a6",
+ "type": "DEVICE"
+ }
+ }
+ },
+ "attributes": {
+ "soilHumidity": {
+ "value": 55,
+ "timestamp": "2022-06-19T09:30:55.546+00:00"
+ },
+ "soilTemperature": {
+ "value": 18,
+ "timestamp": "2022-06-19T09:30:55.747+00:00"
+ }
+ }
+}
--- /dev/null
+{
+ "id": "638e82cd-774c-4f34-be10-2eec41d1c0a6",
+ "type": "SENSOR",
+ "relationships": {
+ "device": {
+ "data": {
+ "id": "638e82cd-774c-4f34-be10-2eec41d1c0a6",
+ "type": "DEVICE"
+ }
+ }
+ }
+}