├── .gitignore ├── .travis.yml ├── Dockerfile ├── src └── main │ ├── resources │ └── OH-INF │ │ ├── i18n │ │ └── synologysurveillancestation_de.properties │ │ ├── addon │ │ └── addon.xml │ │ └── thing │ │ ├── bridge.xml │ │ └── camera.xml │ ├── history │ └── dependencies.xml │ ├── feature │ └── feature.xml │ └── java │ └── org │ └── openhab │ └── binding │ └── synologysurveillancestation │ ├── internal │ ├── webapi │ │ ├── response │ │ │ ├── SimpleResponse.java │ │ │ ├── InfoResponse.java │ │ │ ├── HomeModeResponse.java │ │ │ ├── AuthResponse.java │ │ │ ├── LiveUriResponse.java │ │ │ ├── CameraEventResponseObject.java │ │ │ ├── CameraEventResponse.java │ │ │ ├── EventResponse.java │ │ │ ├── SynoApiResponse.java │ │ │ └── CameraResponse.java │ │ ├── error │ │ │ ├── ErrorCode.java │ │ │ └── WebApiAuthErrorCodes.java │ │ ├── SynoWebApi.java │ │ ├── request │ │ │ ├── SynoApiConfig.java │ │ │ ├── SynoApiLiveUri.java │ │ │ ├── SynoApiExternalEvent.java │ │ │ ├── SynoApiInfo.java │ │ │ ├── SynoApiEvent.java │ │ │ ├── SynoApiHomeMode.java │ │ │ ├── SynoApiExternalRecording.java │ │ │ ├── SynoApi.java │ │ │ ├── SynoApiAuth.java │ │ │ ├── SynoApiCameraEvent.java │ │ │ ├── SynoApiRequest.java │ │ │ ├── SynoApiCamera.java │ │ │ └── SynoApiPTZ.java │ │ ├── WebApiException.java │ │ ├── SynoEvent.java │ │ └── SynoWebApiHandler.java │ ├── thread │ │ ├── SynoApiThreadHomeMode.java │ │ ├── SynoApiThreadCameraEvent.java │ │ ├── SynoApiThreadSnapshot.java │ │ ├── SynoApiThreadLiveUri.java │ │ ├── SynoApiThreadCamera.java │ │ ├── SynoApiThreadEvent.java │ │ └── SynoApiThread.java │ ├── discovery │ │ ├── SynoDynamicStateDescriptionProvider.java │ │ ├── BridgeMdnsDiscoveryService.java │ │ └── CameraDiscoveryService.java │ ├── SynoCameraConfig.java │ ├── SynoConfig.java │ └── SynoHandlerFactory.java │ ├── handler │ ├── SynoHandler.java │ └── SynoBridgeHandler.java │ └── SynoBindingConstants.java ├── docker-compose.yml ├── NOTICE ├── pom.xml ├── .project ├── .classpath └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | services: 4 | - docker 5 | 6 | script: 7 | - docker-compose run build-plugin -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y maven \ 5 | && apt-get clean -------------------------------------------------------------------------------- /src/main/resources/OH-INF/i18n/synologysurveillancestation_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nibi79/synologysurveillancestation/HEAD/src/main/resources/OH-INF/i18n/synologysurveillancestation_de.properties -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.2" 2 | 3 | services: 4 | build-plugin: 5 | image: plugin-sdk 6 | build: 7 | dockerfile: Dockerfile 8 | context: . 9 | volumes: 10 | - .:${PWD} 11 | - ${M2_HOME:-~/.m2}:/root/.m2 12 | working_dir: ${PWD} 13 | command: mvn clean install -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This content is produced and maintained by the openHAB project contributors. 2 | 3 | * Project home: https://www.openhab.org 4 | 5 | == Declared Project Licenses 6 | 7 | This program and the accompanying materials are made available under the terms 8 | of the Eclipse Public License 2.0 which is available at 9 | https://www.eclipse.org/legal/epl-2.0/. 10 | 11 | == Source Code 12 | 13 | https://github.com/nibi79/synologysurveillancestation 14 | -------------------------------------------------------------------------------- /src/main/resources/OH-INF/addon/addon.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | binding 7 | SynologySurveillanceStation Binding 8 | This is the binding for SynologySurveillanceStation. 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/history/dependencies.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | openhab-runtime-base 5 | wrap 6 | mvn:org.openhab.addons.bundles/org.openhab.binding.synologysurveillancestation/4.3.0-SNAPSHOT 7 | wrap:mvn:org.lastnpe.eea/eea-all/2.4.0 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/feature/feature.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features 4 | 5 | 6 | openhab-runtime-base 7 | mvn:org.openhab.addons.bundles/org.openhab.binding.synologysurveillancestation/${project.version} 8 | 9 | 10 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | 8 | org.openhab.addons.bundles 9 | org.openhab.addons.reactor.bundles 10 | 4.3.0-SNAPSHOT 11 | 12 | 13 | org.openhab.binding.synologysurveillancestation 14 | 15 | openHAB Add-ons :: Bundles :: Synology Surveillance Station Binding 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/SimpleResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | /** 18 | * {@link SimpleResponse} is a simplest implementation of an API response 19 | * 20 | * @author Nils - Initial contribution 21 | */ 22 | @NonNullByDefault 23 | public class SimpleResponse extends SynoApiResponse { 24 | 25 | /** 26 | * @param jsonResponse 27 | */ 28 | public SimpleResponse(String jsonResponse) { 29 | super(jsonResponse); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.openhab.binding.synologysurveillancestation 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.pde.ManifestBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.pde.SchemaBuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.pde.ds.core.builder 25 | 26 | 27 | 28 | 29 | org.eclipse.m2e.core.maven2Builder 30 | 31 | 32 | 33 | 34 | 35 | org.eclipse.m2e.core.maven2Nature 36 | org.eclipse.jdt.core.javanature 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/InfoResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | import com.google.gson.JsonElement; 18 | 19 | /** 20 | * {@link InfoResponse} provides information about current camera setup 21 | * 22 | * @author Nils - Initial contribution 23 | * @author Pavion - Contribution 24 | */ 25 | @NonNullByDefault 26 | public class InfoResponse extends SimpleResponse { 27 | 28 | /** 29 | * @param jsonResponse 30 | */ 31 | public InfoResponse(String jsonResponse) { 32 | super(jsonResponse); 33 | } 34 | 35 | public JsonElement getCameras() { 36 | return getData().get("cameras"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/HomeModeResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | /** 18 | * {@link HomeModeResponse} provides response for Home Mode 19 | * 20 | * @author Pavion - Initial contribution 21 | */ 22 | @NonNullByDefault 23 | public class HomeModeResponse extends SimpleResponse { 24 | /** 25 | * @param jsonResponse 26 | */ 27 | public HomeModeResponse(String jsonResponse) { 28 | super(jsonResponse); 29 | } 30 | 31 | /** 32 | * @return 33 | */ 34 | public boolean isHomeMode() { 35 | return getData().get("on").getAsBoolean(); 36 | } 37 | 38 | public int getReason() { 39 | return getData().get("reason").getAsInt(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/AuthResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | import org.openhab.binding.synologysurveillancestation.internal.thread.SynoApiThread; 17 | 18 | /** 19 | * {@link SynoApiThread} handles authentication response 20 | * 21 | * @author Nils - Initial contribution 22 | * @author Pavion - Contribution 23 | */ 24 | @NonNullByDefault 25 | public class AuthResponse extends SimpleResponse { 26 | 27 | /** 28 | * @param jsonResponse 29 | */ 30 | public AuthResponse(String jsonResponse) { 31 | super(jsonResponse); 32 | } 33 | 34 | /** 35 | * @return Session ID 36 | */ 37 | public String getSid() { 38 | return getData().get("sid").getAsString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/handler/SynoHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.handler; 14 | 15 | import java.util.concurrent.ScheduledExecutorService; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.eclipse.jdt.annotation.Nullable; 19 | import org.openhab.binding.synologysurveillancestation.internal.webapi.SynoWebApiHandler; 20 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 21 | 22 | /** 23 | * The {@link SynoHandler} is a generic handler 24 | * 25 | * @author Nils - Initial contribution 26 | * @author Pavion - Contribution 27 | */ 28 | @NonNullByDefault 29 | public interface SynoHandler { 30 | 31 | public ScheduledExecutorService getScheduler(); 32 | 33 | public @Nullable SynoWebApiHandler getSynoWebApiHandler(); 34 | 35 | public boolean reconnect(boolean forceLogout) throws WebApiException; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/error/ErrorCode.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.error; 14 | 15 | /** 16 | * The {@link ErrorCode} is an interface for error codes 17 | * 18 | * @author Nils - Initial contribution 19 | * @author Pavion - Contribution 20 | */ 21 | public interface ErrorCode { 22 | 23 | /** 24 | * @return 25 | */ 26 | public int getCode(); 27 | 28 | /** 29 | * @return 30 | */ 31 | public String getMsg(); 32 | 33 | /** 34 | * 35 | * Lookup ApiErrorCode. 36 | * 37 | * @param e 38 | * @param code 39 | * @return 40 | */ 41 | static WebApiAuthErrorCodes lookup(int code) { 42 | for (WebApiAuthErrorCodes w : WebApiAuthErrorCodes.class.getEnumConstants()) { 43 | if (w.getCode() == code) 44 | return w; 45 | } 46 | return WebApiAuthErrorCodes.NO_ERROR; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/SynoWebApi.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.SimpleResponse; 17 | 18 | /** 19 | * The {@link SynoWebApi} is an interface for the Web API 20 | * 21 | * @author Nils - Initial contribution 22 | * @author Pavion - Contribution 23 | */ 24 | @NonNullByDefault 25 | public interface SynoWebApi { 26 | /** 27 | * Establish connection to Surveillance Station Web API 28 | * 29 | * @return 30 | * @throws WebApiException 31 | */ 32 | public boolean connect(boolean forceLogout) throws WebApiException; 33 | 34 | /** 35 | * 36 | * @return 37 | * @throws WebApiException 38 | */ 39 | public SimpleResponse disconnect() throws WebApiException; 40 | 41 | // ---------------------------- 42 | 43 | /** 44 | * Returns true if connected and sid != null 45 | * 46 | * @return 47 | */ 48 | public boolean isConnected(); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | /** 18 | * API configuration parameters 19 | * 20 | * @author Nils - Initial contribution 21 | * @author Pavion - Contribution 22 | * 23 | */ 24 | @NonNullByDefault 25 | public class SynoApiConfig { 26 | 27 | private final String name; 28 | private final String version; 29 | private final String scriptpath; 30 | 31 | /** 32 | * @param name 33 | * @param version 34 | * @param scriptpath 35 | */ 36 | public SynoApiConfig(String name, String version, String scriptpath) { 37 | this.name = name; 38 | this.version = version; 39 | this.scriptpath = scriptpath; 40 | } 41 | 42 | /** 43 | * 44 | * @return 45 | */ 46 | public final String getName() { 47 | return name; 48 | } 49 | 50 | /** 51 | * @return 52 | */ 53 | public final String getVersion() { 54 | return version; 55 | } 56 | 57 | /** 58 | * @return 59 | */ 60 | public final String getScriptpath() { 61 | return scriptpath; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/LiveUriResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | import com.google.gson.JsonArray; 18 | import com.google.gson.JsonElement; 19 | import com.google.gson.JsonObject; 20 | 21 | /** 22 | * {@link LiveUriResponse} is a response for live URIs 23 | * 24 | * @author Pavion - Initial contribution 25 | */ 26 | @NonNullByDefault 27 | public class LiveUriResponse extends SimpleResponse { 28 | 29 | /** 30 | * @param jsonResponse 31 | */ 32 | public LiveUriResponse(String jsonResponse) { 33 | super(jsonResponse); 34 | } 35 | 36 | public JsonArray getUris() { 37 | return getDataAsJsonArray(); 38 | } 39 | 40 | /** 41 | * Return rtsp URI 42 | * 43 | */ 44 | public String getRtsp() { 45 | for (JsonElement jUri : getUris()) { 46 | if (jUri.isJsonObject()) { 47 | JsonObject uri = jUri.getAsJsonObject(); 48 | return uri.get("rtspPath").getAsString(); 49 | } 50 | } 51 | return ""; 52 | } 53 | 54 | /** 55 | * Return mjpeg over http URI 56 | * 57 | */ 58 | public String getMjpegHttp() { 59 | for (JsonElement jUri : getUris()) { 60 | if (jUri.isJsonObject()) { 61 | JsonObject uri = jUri.getAsJsonObject(); 62 | return uri.get("mjpegHttpPath").getAsString(); 63 | } 64 | } 65 | return ""; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiLiveUri.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.io.IOException; 16 | import java.net.URISyntaxException; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | import org.eclipse.jdt.annotation.NonNullByDefault; 21 | import org.eclipse.jetty.client.HttpClient; 22 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 23 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 24 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.LiveUriResponse; 25 | 26 | /** 27 | * SYNO.SurveillanceStation.LiveUri 28 | * 29 | * This API provides a set of methods to acquire camera live feed URIs 30 | * 31 | * 32 | * @author Pavion - Initial contribution 33 | */ 34 | @NonNullByDefault 35 | public class SynoApiLiveUri extends SynoApiRequest { 36 | // API configuration 37 | private static final String API_NAME = "SYNO.SurveillanceStation.Camera"; 38 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_09, API_SCRIPT_ENTRY); 39 | 40 | /** 41 | * @param config 42 | */ 43 | public SynoApiLiveUri(SynoConfig config, HttpClient httpClient) { 44 | super(API_CONFIG, config, httpClient); 45 | } 46 | 47 | /** 48 | * Get live URIs of the selected camera's live feed 49 | * 50 | * @throws WebApiException 51 | * @throws IOException 52 | * @throws UnsupportedOperationException 53 | * @throws URISyntaxException 54 | * 55 | */ 56 | public LiveUriResponse getLiveUriResponse(String cameraId) throws WebApiException { 57 | Map params = new HashMap<>(); 58 | params.put("idList", cameraId); 59 | 60 | return callApi(METHOD_LIVEVIEWPATH, params); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiExternalEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import org.eclipse.jdt.annotation.NonNullByDefault; 19 | import org.eclipse.jetty.client.HttpClient; 20 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 22 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.CameraResponse; 23 | 24 | /** 25 | * SYNO.SurveillanceStation.ExternalEvent 26 | * 27 | * External event related WebAPI. The method “Trigger” is implemented. 28 | * 29 | * @author Pavion - Initial Contribution 30 | * 31 | */ 32 | @NonNullByDefault 33 | public class SynoApiExternalEvent extends SynoApiRequest { 34 | // private final Logger logger = LoggerFactory.getLogger(SynoApiExternalEvent.class); 35 | 36 | // API configuration 37 | private static final String API_NAME = "SYNO.SurveillanceStation.ExternalEvent"; 38 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_01, API_SCRIPT_ENTRY); 39 | 40 | /** 41 | * @param config 42 | */ 43 | public SynoApiExternalEvent(SynoConfig config, HttpClient httpClient) { 44 | super(API_CONFIG, config, httpClient); 45 | } 46 | 47 | /** 48 | * Triggers the external event 1 to 10 49 | * 50 | * @param event External event to be triggered (1 to 10) 51 | * @return 52 | * @throws WebApiException 53 | */ 54 | public boolean triggerEvent(int event) throws WebApiException { 55 | Map params = new HashMap<>(); 56 | 57 | // API parameters 58 | params.put("eventId", String.valueOf(event)); 59 | return callApi(METHOD_TRIGGER, params).isSuccess(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | import org.eclipse.jetty.client.HttpClient; 17 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 18 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 19 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 20 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.InfoResponse; 21 | 22 | /** 23 | * SYNO.SurveillanceStation.Info 24 | * 25 | * This API provides a method to acquire Surveillance Station related information such as package version, package UI 26 | * path, and the total number of camera and installed licenses. 27 | * 28 | * Method: 29 | * - GetInfo 30 | * 31 | * @author Nils - Initial contribution 32 | * @author Pavion - Contribution 33 | */ 34 | @NonNullByDefault 35 | public class SynoApiInfo extends SynoApiRequest { 36 | 37 | // API Configuration 38 | private static final String API_NAME = "SYNO.SurveillanceStation.Info"; 39 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_05, API_SCRIPT_ENTRY); 40 | 41 | /** 42 | * @param config 43 | */ 44 | public SynoApiInfo(SynoConfig config, HttpClient httpClient) { 45 | super(API_CONFIG, config, httpClient); 46 | } 47 | 48 | /** 49 | * Get Surveillance Station related general information. 50 | * 51 | * @return 52 | * @throws WebApiException 53 | */ 54 | public InfoResponse getInfo() throws WebApiException { 55 | InfoResponse response = callApi(METHOD_GETINFO); 56 | 57 | if (!response.isSuccess()) { 58 | throw new WebApiException(WebApiAuthErrorCodes.getByCode(response.getErrorcode())); 59 | } 60 | 61 | return response; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import org.eclipse.jdt.annotation.NonNullByDefault; 19 | import org.eclipse.jetty.client.HttpClient; 20 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 22 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.EventResponse; 23 | 24 | /** 25 | * SYNO.SurveillanceStation.SynoApiEvent 26 | * 27 | * This API provides a method to acquire Surveillance Station event information as displayed in Timeline 28 | * 29 | * @author Pavion - Initial contribution 30 | */ 31 | @NonNullByDefault 32 | public class SynoApiEvent extends SynoApiRequest { 33 | 34 | // API Configuration 35 | private static final String API_NAME = "SYNO.SurveillanceStation.Event"; 36 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_05, API_SCRIPT_ENTRY); 37 | 38 | /** 39 | * @param config 40 | */ 41 | public SynoApiEvent(SynoConfig config, HttpClient httpClient) { 42 | super(API_CONFIG, config, httpClient); 43 | } 44 | 45 | /** 46 | * Get API events 47 | * 48 | * @return 49 | * @throws WebApiException 50 | */ 51 | public EventResponse getEventResponse(String cameraId, long lastEventTime, int reason) { 52 | Map params = new HashMap<>(); 53 | 54 | params.put("cameraIds", cameraId); 55 | // params.put("fromTime", String.valueOf(lastEventTime)); 56 | params.put("blIncludeSnapshot", API_FALSE); 57 | params.put("limit", "1"); 58 | params.put("reason", String.valueOf(reason)); 59 | 60 | try { 61 | return callApi(METHOD_LIST, params); 62 | } catch (WebApiException e) { 63 | return new EventResponse("{\"data\":{},\"success\":false}"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/WebApiException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi; 14 | 15 | import java.util.Objects; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.ErrorCode; 19 | 20 | /** 21 | * The {@link WebApiException} is a class for handling the binding exceptions 22 | * 23 | * @author Nils - Initial contribution 24 | * @author Pavion - Contribution 25 | */ 26 | @NonNullByDefault 27 | public class WebApiException extends Exception { 28 | 29 | private static final long serialVersionUID = 1L; 30 | 31 | private static final int UNKNOWN = 0; 32 | 33 | private final int errorCode; 34 | private final String errorMsg; 35 | 36 | public WebApiException(int errorCode, String errorMsg, Throwable cause) { 37 | super(errorMsg, cause); 38 | this.errorCode = errorCode; 39 | this.errorMsg = errorMsg; 40 | } 41 | 42 | public WebApiException(int errorCode, String errorMsg) { 43 | super(errorMsg); 44 | this.errorCode = errorCode; 45 | this.errorMsg = errorMsg; 46 | } 47 | 48 | public WebApiException(ErrorCode errorCode) { 49 | super(); 50 | this.errorCode = errorCode.getCode(); 51 | this.errorMsg = errorCode.getMsg(); 52 | } 53 | 54 | public WebApiException(String errorMsg, Throwable cause) { 55 | super(errorMsg); 56 | this.errorCode = UNKNOWN; 57 | this.errorMsg = errorMsg; 58 | } 59 | 60 | public WebApiException(String errorMsg) { 61 | super(errorMsg); 62 | this.errorCode = UNKNOWN; 63 | this.errorMsg = errorMsg; 64 | } 65 | 66 | public WebApiException(Throwable cause) { 67 | super(cause.getMessage(), cause); 68 | this.errorCode = UNKNOWN; 69 | this.errorMsg = Objects.requireNonNullElse(cause.getMessage(), ""); 70 | } 71 | 72 | public int getErrorCode() { 73 | return errorCode; 74 | } 75 | 76 | public String getErrorMsg() { 77 | return errorMsg; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiHomeMode.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import org.eclipse.jdt.annotation.NonNullByDefault; 19 | import org.eclipse.jetty.client.HttpClient; 20 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 22 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.HomeModeResponse; 23 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.SimpleResponse; 24 | 25 | /** 26 | * SYNO.SurveillanceStation.SynoApiHomeMode 27 | * 28 | * This API provides a method to acquire Surveillance Station Home Mode state 29 | * 30 | * @author Pavion - Initial contribution 31 | */ 32 | @NonNullByDefault 33 | public class SynoApiHomeMode extends SynoApiRequest { 34 | private static final String API_NAME = "SYNO.SurveillanceStation.HomeMode"; 35 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_01, API_SCRIPT_ENTRY); 36 | 37 | /** 38 | * @param config 39 | */ 40 | public SynoApiHomeMode(SynoConfig config, HttpClient httpClient) { 41 | super(API_CONFIG, config, httpClient); 42 | } 43 | 44 | /** 45 | * Get API events 46 | * 47 | * @return 48 | * @throws WebApiException 49 | */ 50 | public HomeModeResponse getHomeModeResponse() { 51 | try { 52 | Map params = new HashMap<>(); 53 | return callApi(METHOD_GETINFO, params); 54 | } catch (WebApiException e) { 55 | return new HomeModeResponse("{\"data\":{},\"success\":false}"); 56 | } 57 | } 58 | 59 | /** 60 | * 61 | * @param mode 62 | * @return 63 | * @throws WebApiException 64 | */ 65 | public SimpleResponse setHomeMode(boolean mode) throws WebApiException { 66 | Map params = new HashMap<>(); 67 | params.put("on", mode ? "true" : "false"); 68 | return callApi(METHOD_SWITCH, params); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/thread/SynoApiThreadHomeMode.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.thread; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.CHANNEL_HOMEMODE; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.openhab.binding.synologysurveillancestation.handler.SynoBridgeHandler; 19 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 20 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.HomeModeResponse; 22 | import org.openhab.core.library.types.OnOffType; 23 | import org.openhab.core.thing.Channel; 24 | 25 | /** 26 | * Thread for getting Surveillance Station Home Mode state 27 | * 28 | * @author Pavion - Initial contribution 29 | */ 30 | @NonNullByDefault 31 | public class SynoApiThreadHomeMode extends SynoApiThread { 32 | // private final Logger logger = LoggerFactory.getLogger(SynoApiThreadHomeMode.class); 33 | 34 | public SynoApiThreadHomeMode(SynoBridgeHandler handler, int refreshRate) { 35 | super(SynoApiThread.THREAD_HOMEMODE, handler, refreshRate); 36 | } 37 | 38 | @Override 39 | public boolean isNeeded() { 40 | return (getSynoHandler().isLinked(CHANNEL_HOMEMODE)); 41 | } 42 | 43 | @Override 44 | public boolean refresh() throws Exception { 45 | SynoBridgeHandler bridgeHandler = getSynoHandler(); 46 | HomeModeResponse response = bridgeHandler.getSynoWebApiHandler().getApiHomeMode().getHomeModeResponse(); 47 | if (response.isSuccess()) { 48 | if (getSynoHandler().isLinked(CHANNEL_HOMEMODE)) { 49 | Channel channel = bridgeHandler.getThing().getChannel(CHANNEL_HOMEMODE); 50 | getSynoHandler().updateState(channel.getUID(), response.isHomeMode() ? OnOffType.ON : OnOffType.OFF); 51 | } 52 | return true; 53 | } else if (response.getErrorcode() == 119) { 54 | throw new WebApiException(WebApiAuthErrorCodes.INSUFFICIENT_USER_PRIVILEGE); 55 | } else { 56 | return false; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiExternalRecording.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import org.eclipse.jdt.annotation.NonNullByDefault; 19 | import org.eclipse.jetty.client.HttpClient; 20 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 22 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.SimpleResponse; 23 | 24 | /** 25 | * SYNO.SurveillanceStation.ExternalRecording 26 | * 27 | * This API provides methods to start or stop external recording of a camera. 28 | * 29 | * Method: 30 | * - Record 31 | * 32 | * @author Nils - Initial contribution 33 | * @author Pavion - Contribution 34 | */ 35 | @NonNullByDefault 36 | public class SynoApiExternalRecording extends SynoApiRequest { 37 | 38 | // API configuration 39 | private static final String API_NAME = "SYNO.SurveillanceStation.ExternalRecording"; 40 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_02, API_SCRIPT_ENTRY); 41 | 42 | /** 43 | * @param config 44 | */ 45 | public SynoApiExternalRecording(SynoConfig config, HttpClient httpClient) { 46 | super(API_CONFIG, config, httpClient); 47 | } 48 | 49 | /** 50 | * @param method 51 | * @param cameraId 52 | * @param action 53 | * @return 54 | * @throws WebApiException 55 | */ 56 | private SimpleResponse call(String method, String cameraId, String action) throws WebApiException { 57 | Map params = new HashMap<>(); 58 | 59 | // API parameters 60 | params.put("cameraId", cameraId); 61 | params.put("action", action); 62 | 63 | return callApi(method, params); 64 | } 65 | 66 | /** 67 | * Toggle external recording of a camera. 68 | * 69 | * @param cameraId 70 | * @return 71 | * @throws WebApiException 72 | */ 73 | public SimpleResponse toggleRecording(String cameraId, boolean on) throws WebApiException { 74 | return call(METHOD_RECORD, cameraId, on ? "start" : "stop"); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/SynoEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | /** 18 | * {@link SynoEvent} stores events 19 | * 20 | * @author Pavion - Initial contribution 21 | */ 22 | @NonNullByDefault 23 | public class SynoEvent { 24 | public static final int EVENT_REASON_CONTINUOUS = 1; 25 | public static final int EVENT_REASON_MOTION = 2; 26 | public static final int EVENT_REASON_ALARM = 3; 27 | public static final int EVENT_REASON_CUSTOM = 4; 28 | public static final int EVENT_REASON_MANUAL = 5; 29 | public static final int EVENT_REASON_EXTERNAL = 6; 30 | public static final int EVENT_REASON_ANALYTICS = 7; 31 | public static final int EVENT_REASON_EDGE = 8; 32 | public static final int EVENT_REASON_ACTIONRULE = 9; 33 | 34 | private boolean eventCompleted = true; 35 | private long eventId = -1; 36 | private final int reason; 37 | 38 | /** 39 | * Constructor for OH2 side 40 | * 41 | * @param reason 42 | */ 43 | public SynoEvent(int reason) { 44 | this.reason = reason; 45 | } 46 | 47 | /** 48 | * Constructor for API side 49 | * 50 | * @param eventCompleted 51 | * @param eventId 52 | * @param reason 53 | */ 54 | public SynoEvent(long eventId, boolean eventCompleted, int reason) { 55 | this.eventCompleted = eventCompleted; 56 | this.eventId = eventId; 57 | this.reason = reason; 58 | } 59 | 60 | /** 61 | * @return the eventCompleted 62 | */ 63 | public boolean isEventCompleted() { 64 | return eventCompleted; 65 | } 66 | 67 | /** 68 | * @param eventCompleted the eventCompleted to set 69 | */ 70 | public void setEventCompleted(boolean eventCompleted) { 71 | this.eventCompleted = eventCompleted; 72 | } 73 | 74 | /** 75 | * @return the eventId 76 | */ 77 | public long getEventId() { 78 | return eventId; 79 | } 80 | 81 | /** 82 | * @param eventId the eventId to set 83 | */ 84 | public void setEventId(long eventId) { 85 | this.eventId = eventId; 86 | } 87 | 88 | /** 89 | * @return the reason 90 | */ 91 | public int getReason() { 92 | return reason; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/thread/SynoApiThreadCameraEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.thread; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.*; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.openhab.binding.synologysurveillancestation.handler.SynoCameraHandler; 19 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.CameraEventResponse; 20 | import org.openhab.core.library.types.DecimalType; 21 | import org.openhab.core.library.types.StringType; 22 | 23 | /** 24 | * Thread for getting camera state (enabled, recording) 25 | * 26 | * @author Pavion - Initial contribution 27 | */ 28 | @NonNullByDefault 29 | public class SynoApiThreadCameraEvent extends SynoApiThread { 30 | 31 | public SynoApiThreadCameraEvent(SynoCameraHandler handler, int refreshRate) { 32 | super(SynoApiThread.THREAD_CAMERAEVENT, handler, refreshRate); 33 | } 34 | 35 | @Override 36 | public boolean isNeeded() { 37 | for (String channel : CHANNEL_MDPARAM) { 38 | if (getSynoHandler().isLinked(channel)) { 39 | return true; 40 | } 41 | } 42 | return false; 43 | } 44 | 45 | @Override 46 | public boolean refresh() throws Exception { 47 | SynoCameraHandler cameraHandler = getSynoHandler(); 48 | String cameraId = cameraHandler.getCameraId(); 49 | 50 | CameraEventResponse response = cameraHandler.getSynoWebApiHandler().getApiCameraEvent().getMDParam(cameraId); 51 | if (!response.isSuccess()) { 52 | return false; 53 | } 54 | 55 | cameraHandler.updateState(CHANNEL_MDPARAM_SOURCE, new StringType(response.getSource())); 56 | cameraHandler.updateState(CHANNEL_MDPARAM_SENSITIVITY, new DecimalType(response.getSensitivity().getValue())); 57 | cameraHandler.updateState(CHANNEL_MDPARAM_THRESHOLD, new DecimalType(response.getThreshold().getValue())); 58 | cameraHandler.updateState(CHANNEL_MDPARAM_OBJECTSIZE, new DecimalType(response.getObjectSize().getValue())); 59 | cameraHandler.updateState(CHANNEL_MDPARAM_PERCENTAGE, new DecimalType(response.getPercentage().getValue())); 60 | cameraHandler.updateState(CHANNEL_MDPARAM_SHORTLIVE, new DecimalType(response.getShortLiveSecond().getValue())); 61 | 62 | return true; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/thread/SynoApiThreadSnapshot.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.thread; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.CHANNEL_SNAPSHOT; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.openhab.binding.synologysurveillancestation.handler.SynoCameraHandler; 19 | import org.openhab.binding.synologysurveillancestation.internal.SynoCameraConfig; 20 | import org.openhab.core.library.types.RawType; 21 | import org.openhab.core.thing.Channel; 22 | import org.openhab.core.thing.Thing; 23 | import org.openhab.core.types.UnDefType; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | /** 28 | * Thread for getting snapshots 29 | * 30 | * @author Pavion - Initial contribution 31 | */ 32 | @NonNullByDefault 33 | public class SynoApiThreadSnapshot extends SynoApiThread { 34 | private final Logger logger = LoggerFactory.getLogger(SynoApiThreadSnapshot.class); 35 | 36 | public SynoApiThreadSnapshot(SynoCameraHandler handler, int refreshRate) { 37 | super(SynoApiThread.THREAD_SNAPSHOT, handler, refreshRate); 38 | } 39 | 40 | @Override 41 | public boolean isNeeded() { 42 | return (getSynoHandler().isLinked(CHANNEL_SNAPSHOT)); 43 | } 44 | 45 | @Override 46 | public boolean refresh() throws Exception { 47 | SynoCameraHandler cameraHandler = getSynoHandler(); 48 | 49 | Channel channel = cameraHandler.getThing().getChannel(CHANNEL_SNAPSHOT); 50 | Thing thing = cameraHandler.getThing(); 51 | SynoCameraConfig config = thing.getConfiguration().as(SynoCameraConfig.class); 52 | byte[] snapshot = new byte[0]; 53 | try { 54 | snapshot = cameraHandler.getSynoWebApiHandler().getApiCamera().getSnapshot(getSynoHandler().getCameraId(), 55 | getRefreshRate(), config.getSnapshotStreamId()); 56 | } catch (Exception e) { 57 | logger.error("Unexpected exception while obtaining snapshot, possibly network disconnected", e); 58 | } 59 | if (snapshot.length < 1000) { 60 | getSynoHandler().updateState(channel.getUID(), UnDefType.UNDEF); 61 | return (snapshot.length == 2); 62 | } else { 63 | getSynoHandler().updateState(channel.getUID(), new RawType(snapshot, "image/jpeg")); 64 | return true; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/discovery/SynoDynamicStateDescriptionProvider.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.discovery; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Locale; 18 | import java.util.Map; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | import org.eclipse.jdt.annotation.NonNullByDefault; 22 | import org.eclipse.jdt.annotation.Nullable; 23 | import org.openhab.core.thing.Channel; 24 | import org.openhab.core.thing.ChannelUID; 25 | import org.openhab.core.thing.type.DynamicStateDescriptionProvider; 26 | import org.openhab.core.types.StateDescription; 27 | import org.openhab.core.types.StateDescriptionFragmentBuilder; 28 | import org.openhab.core.types.StateOption; 29 | import org.osgi.service.component.ComponentContext; 30 | import org.osgi.service.component.annotations.Activate; 31 | import org.osgi.service.component.annotations.Component; 32 | import org.osgi.service.component.annotations.Deactivate; 33 | 34 | /** 35 | * Dynamic channel state description provider. 36 | * Overrides the state description for the controls, which receive its configuration in the runtime. 37 | * 38 | * @author Nils - Initial contribution 39 | * @author Pavion - Contribution 40 | */ 41 | @NonNullByDefault 42 | @Component(service = { DynamicStateDescriptionProvider.class, SynoDynamicStateDescriptionProvider.class }) 43 | public class SynoDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider { 44 | 45 | private final Map> channelOptionsMap = new ConcurrentHashMap<>(); 46 | 47 | @Activate 48 | public void activate(ComponentContext context) { 49 | } 50 | 51 | public void setStateOptions(ChannelUID channelUID, List options) { 52 | channelOptionsMap.put(channelUID, options); 53 | } 54 | 55 | @Override 56 | public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original, 57 | @Nullable Locale locale) { 58 | List options = channelOptionsMap.getOrDefault(channel.getUID(), new ArrayList()); 59 | 60 | StateDescriptionFragmentBuilder builder = (original == null) ? StateDescriptionFragmentBuilder.create() 61 | : StateDescriptionFragmentBuilder.create(original); 62 | return builder.withOptions(options).build().toStateDescription(); 63 | } 64 | 65 | @Deactivate 66 | public void deactivate() { 67 | channelOptionsMap.clear(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApi.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | /** 18 | * The {@link SynoApi} is an interface for Synology API codes 19 | * 20 | * @author Nils - Initial contribution 21 | * @author Pavion - Contribution 22 | */ 23 | @NonNullByDefault 24 | public interface SynoApi { 25 | 26 | // API configuration versions 27 | public static final String API_VERSION_01 = "1"; 28 | public static final String API_VERSION_02 = "2"; 29 | public static final String API_VERSION_03 = "3"; 30 | public static final String API_VERSION_04 = "4"; 31 | public static final String API_VERSION_05 = "5"; 32 | public static final String API_VERSION_06 = "6"; 33 | public static final String API_VERSION_07 = "7"; 34 | public static final String API_VERSION_08 = "8"; 35 | public static final String API_VERSION_09 = "9"; 36 | 37 | // API configuration scripts 38 | public static final String API_SCRIPT_AUTH = "/webapi/auth.cgi"; 39 | public static final String API_SCRIPT_ENTRY = "/webapi/entry.cgi"; 40 | public static final String API_SCRIPT_QUERY = "/webapi/query.cgi"; 41 | 42 | // API methods 43 | public static final String METHOD_LOGIN = "Login"; 44 | public static final String METHOD_LOGOUT = "Logout"; 45 | 46 | public static final String METHOD_LIST = "List"; 47 | public static final String METHOD_GETINFO = "GetInfo"; 48 | public static final String METHOD_ENABLE = "Enable"; 49 | public static final String METHOD_DISABLE = "Disable"; 50 | public static final String METHOD_GETSNAPSHOT = "GetSnapshot"; 51 | public static final String METHOD_LIVEVIEWPATH = "GetLiveViewPath"; 52 | 53 | public static final String METHOD_RECORD = "Record"; 54 | 55 | public static final String METHOD_ZOOM = "Zoom"; 56 | public static final String METHOD_MOVE = "Move"; 57 | public static final String METHOD_LISTPRESET = "ListPreset"; 58 | public static final String METHOD_GOPRESET = "GoPreset"; 59 | public static final String METHOD_LISTPATROL = "ListPatrol"; 60 | public static final String METHOD_RUNPATROL = "RunPatrol"; 61 | public static final String METHOD_SWITCH = "Switch"; 62 | public static final String METHOD_TRIGGER = "Trigger"; 63 | 64 | public static final String METHOD_MOTIONENUM = "MotionEnum"; 65 | public static final String METHOD_MDPARAMSAVE = "MDParamSave"; 66 | 67 | public static final int CONNECTION_TIMEOUT = 5000; 68 | 69 | public SynoApiConfig getApiConfig(); 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/thread/SynoApiThreadLiveUri.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.thread; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.*; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.openhab.binding.synologysurveillancestation.handler.SynoCameraHandler; 19 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 20 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.LiveUriResponse; 22 | import org.openhab.core.library.types.StringType; 23 | import org.openhab.core.thing.Channel; 24 | 25 | /** 26 | * Thread for refreshing live URIs (RTSP or MJPEG over HTTP) 27 | * 28 | * @author Pavion - Initial contribution 29 | */ 30 | @NonNullByDefault 31 | public class SynoApiThreadLiveUri extends SynoApiThread { 32 | // private final Logger logger = LoggerFactory.getLogger(SynoApiThreadCamera.class); 33 | 34 | public SynoApiThreadLiveUri(SynoCameraHandler handler, int refreshRate) { 35 | super(SynoApiThread.THREAD_LIVEURI, handler, refreshRate); 36 | } 37 | 38 | @Override 39 | public boolean isNeeded() { 40 | return (getSynoHandler().isLinked(CHANNEL_LIVE_URI_RTSP) 41 | || getSynoHandler().isLinked(CHANNEL_LIVE_URI_MJPEG_HTTP)); 42 | } 43 | 44 | @Override 45 | public boolean refresh() throws Exception { 46 | SynoCameraHandler cameraHandler = getSynoHandler(); 47 | String cameraId = cameraHandler.getCameraId(); 48 | 49 | LiveUriResponse response = cameraHandler.getSynoWebApiHandler().getApiLiveUri().getLiveUriResponse(cameraId); 50 | 51 | if (response.isSuccess()) { 52 | if (cameraHandler.isLinked(CHANNEL_LIVE_URI_RTSP)) { 53 | Channel channel = cameraHandler.getThing().getChannel(CHANNEL_LIVE_URI_RTSP); 54 | String uri = response.getRtsp(); 55 | cameraHandler.updateState(channel.getUID(), new StringType(uri)); 56 | } 57 | 58 | if (cameraHandler.isLinked(CHANNEL_LIVE_URI_MJPEG_HTTP)) { 59 | Channel channel = cameraHandler.getThing().getChannel(CHANNEL_LIVE_URI_MJPEG_HTTP); 60 | String uri = response.getMjpegHttp(); 61 | cameraHandler.updateState(channel.getUID(), new StringType(uri)); 62 | } 63 | 64 | return true; 65 | } else if (response.getErrorcode() == 105) { 66 | throw new WebApiException(WebApiAuthErrorCodes.INSUFFICIENT_USER_PRIVILEGE); 67 | } 68 | 69 | return false; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/CameraEventResponseObject.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | import com.google.gson.JsonObject; 18 | 19 | /** 20 | * {@link CameraEventResponseObject} is a JSON extension for response numeric type 21 | * 22 | * @author Pavion - Initial contribution 23 | */ 24 | @NonNullByDefault 25 | public class CameraEventResponseObject { 26 | 27 | private boolean camCap = false; 28 | private boolean ssCap = false; 29 | private int value = 0; 30 | private int minValue = 0; 31 | private int maxValue = 99; 32 | 33 | /** 34 | * Constructor 35 | * 36 | * @param object 37 | */ 38 | public CameraEventResponseObject(JsonObject object) { 39 | this.camCap = object.get("camCap").getAsBoolean(); 40 | this.ssCap = object.get("ssCap").getAsBoolean(); 41 | this.value = object.get("value").getAsInt(); 42 | try { 43 | this.minValue = object.get("minValue").getAsInt(); 44 | this.maxValue = object.get("maxValue").getAsInt(); 45 | } catch (Exception ex) { 46 | // Keep default values 47 | } 48 | } 49 | 50 | /** 51 | * @return the camCap 52 | */ 53 | public boolean isCamCap() { 54 | return camCap; 55 | } 56 | 57 | /** 58 | * @param camCap the camCap to set 59 | */ 60 | public void setCamCap(boolean camCap) { 61 | this.camCap = camCap; 62 | } 63 | 64 | /** 65 | * @return the ssCap 66 | */ 67 | public boolean isSsCap() { 68 | return ssCap; 69 | } 70 | 71 | /** 72 | * @param ssCap the ssCap to set 73 | */ 74 | public void setSsCap(boolean ssCap) { 75 | this.ssCap = ssCap; 76 | } 77 | 78 | /** 79 | * @return the value 80 | */ 81 | public int getValue() { 82 | return value; 83 | } 84 | 85 | /** 86 | * @param value the value to set 87 | */ 88 | public void setValue(int value) { 89 | this.value = value; 90 | } 91 | 92 | /** 93 | * @return the minValue 94 | */ 95 | public int getMinValue() { 96 | return minValue; 97 | } 98 | 99 | /** 100 | * @param minValue the minValue to set 101 | */ 102 | public void setMinValue(int minValue) { 103 | this.minValue = minValue; 104 | } 105 | 106 | /** 107 | * @return the maxValue 108 | */ 109 | public int getMaxValue() { 110 | return maxValue; 111 | } 112 | 113 | /** 114 | * @param maxValue the maxValue to set 115 | */ 116 | public void setMaxValue(int maxValue) { 117 | this.maxValue = maxValue; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiAuth.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import org.eclipse.jdt.annotation.NonNullByDefault; 19 | import org.eclipse.jetty.client.HttpClient; 20 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 22 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.AuthResponse; 23 | 24 | /** 25 | * 26 | * SYNO.API.Auth 27 | * 28 | * API used to perform session login and logout. 29 | * 30 | * Method: 31 | * - Login 32 | * - Logout 33 | * 34 | * @author Nils - Initial contribution 35 | * @author Pavion - Contribution 36 | * 37 | */ 38 | @NonNullByDefault 39 | public class SynoApiAuth extends SynoApiRequest { 40 | 41 | // API configuration 42 | private static final String API_NAME = "SYNO.API.Auth"; 43 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_06, API_SCRIPT_AUTH); 44 | 45 | /** 46 | * @param config 47 | */ 48 | public SynoApiAuth(SynoConfig config, HttpClient httpClient) { 49 | super(API_CONFIG, config, httpClient); 50 | } 51 | 52 | /** 53 | * Calls the passed method. 54 | * 55 | * @param method 56 | * @return 57 | * @throws WebApiException 58 | */ 59 | private AuthResponse call(String method) throws WebApiException { 60 | Map params = new HashMap<>(); 61 | 62 | // API parameters 63 | params.put("account", getConfig().getUsername()); 64 | params.put("passwd", getConfig().getPassword()); 65 | 66 | params.put("session", "SurveillanceStation"); 67 | params.put("format", "sid"); 68 | 69 | if (getConfig().getUsername().equals("")) { 70 | throw new WebApiException(100, "Empty credentials"); 71 | } 72 | return callApi(method, params); 73 | } 74 | 75 | /** 76 | * Create new login session. 77 | * 78 | * @return 79 | * @throws WebApiException 80 | */ 81 | public AuthResponse login() throws WebApiException { 82 | return call(METHOD_LOGIN); 83 | } 84 | 85 | /** 86 | * Destroy current login session. 87 | * 88 | * @return 89 | * @throws WebApiException 90 | */ 91 | public AuthResponse logout(String sessionId) throws WebApiException { 92 | Map params = new HashMap<>(); 93 | 94 | // API parameters 95 | params.put("session", "SurveillanceStation"); 96 | params.put("_sid", sessionId); 97 | 98 | return callApi(METHOD_LOGOUT, params); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/resources/OH-INF/thing/bridge.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Represents the API for Synology Surveilance Station. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Synology 18 | 19 | 20 | serial 21 | 22 | 23 | 24 | 25 | Protocol (http, https) for accessing Surveillance Station 26 | http 27 | true 28 | 29 | 30 | 31 | Turn on to allow self-signed or invalid SSL certificates (restart on change required) 32 | false 33 | true 34 | 35 | 36 | 37 | network-address 38 | IP of Surveillance Station 39 | true 40 | 41 | 42 | 43 | Port for accessing Surveillance Station 44 | 5000 45 | true 46 | 47 | 48 | 49 | User name for accessing camera 50 | true 51 | 52 | 53 | 54 | password 55 | Password for accessing camera 56 | true 57 | 58 | 59 | 60 | Refresh rate for station global events in seconds (0 to disable) 61 | 3 62 | true 63 | 64 | 65 | 66 | 67 | 68 | Switch 69 | 70 | Home Mode of your Surveillance Station 71 | 72 | 73 | Number 74 | 75 | Trigger external event 1 to 10 76 | 77 | 78 | 79 | String 80 | 81 | Current session ID (SID) 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/error/WebApiAuthErrorCodes.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.error; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | /** 18 | * The {@link WebApiAuthErrorCodes} hosts errorCodes for SYNO.API.Auth. 19 | * 20 | * @author Nils - Initial contribution 21 | * @author Pavion - Contribution 22 | */ 23 | @NonNullByDefault 24 | public enum WebApiAuthErrorCodes implements ErrorCode { 25 | 26 | NO_ERROR(0, "Success"), 27 | UNKNOWN_ERROR(100, "Unknown error."), 28 | PARAM_NOT_SPECIFIED(101, "The account parameter is not specified."), 29 | API_DOES_NOT_EXIST(102, "Surveillance Station is not running."), 30 | METHOD_NOT_EXIST(103, "Method does not exist"), 31 | API_VERSION_NOT_SUPPORTED(104, "This API version is not supported"), 32 | INSUFFICIENT_USER_PRIVILEGE(105, "Insufficient user privilege"), 33 | CONNECT_TIMEOUT(106, "Connection time out"), 34 | MULTIPLE_LOGIN(107, "Multiple login detected"), 35 | NEED_MANAGER_RIGHTS(117, "Need manager rights for operation"), 36 | UNKNOWN_ERROR_119(119, "Unknown API error 119"), 37 | INVALID_PASSWORD(400, "Invalid password."), 38 | DISABLED_ACCOUNT(401, "Guest or disabled account."), 39 | PERMISSION_DENIED(402, "Permission denied."), 40 | ONE_TIME_PASSWD_NOT_SCECIFIED(403, "One time password not specified."), 41 | ONE_TIME_PASSWD_AUTH_FAILED(404, "One time password authenticate failed."), 42 | APPORTAL_INCORRECT(405, "Appportal incorrect."), 43 | OTP_CODE_ENDFORCED(406, "OTP code enforced."), 44 | MAX_TRIES(407, "Max Tries (if auto blocking is set to true)."), 45 | PASSWD_EXP_CAN_NOT_CHANGE(408, "Password Expired Can not Change."), 46 | PASSWD_EXPIRED(409, "Password Expired."), 47 | PASSWD_MUST_CHANGE(410, "Password must change (when first time use or after reset password by admin)."), 48 | ACCOUNT_LOCKED(411, "Account Locked (when account max try exceed)."), 49 | MISSING_LICENSE(412, "Need to add license."), 50 | PLATFORM_MAX_REACHED(413, "Reach the maximum of platform."), 51 | EVENT_NOT_EXIST(414, "Some events not exist."), 52 | MESSAGE_CONNECT_ERROR(415, "message connect failed"), 53 | TEST_CONNETCION_ERROR(417, "Test Connection Error."), 54 | VISUAL_NAME_REPITITION(419, "Visualstation name repetition."), 55 | TOO_MANY_ITEMS(439, "Too many items selected."); 56 | 57 | private final int code; 58 | private final String msg; 59 | 60 | WebApiAuthErrorCodes(int code, String msg) { 61 | this.code = code; 62 | this.msg = msg; 63 | } 64 | 65 | @Override 66 | public int getCode() { 67 | return this.code; 68 | } 69 | 70 | @Override 71 | public String getMsg() { 72 | return this.msg; 73 | } 74 | 75 | /** 76 | * 77 | * @param code 78 | * @return 79 | */ 80 | public static ErrorCode getByCode(int code) { 81 | return ErrorCode.lookup(code); 82 | } 83 | 84 | @Override 85 | public String toString() { 86 | return this.name() + " | ErrorCode: " + this.getCode() + " - " + this.getMsg(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/CameraEventResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | import com.google.gson.JsonObject; 18 | 19 | /** 20 | * {@link CameraEventResponse} is a response for camera information 21 | * 22 | * @author Pavion - Initial contribution 23 | */ 24 | @NonNullByDefault 25 | public class CameraEventResponse extends SimpleResponse { 26 | 27 | /** 28 | * @param jsonResponse 29 | */ 30 | public CameraEventResponse(String jsonResponse) { 31 | super(jsonResponse); 32 | } 33 | 34 | /** 35 | * Returns motion detection parameter as Json Object 36 | * 37 | * @return Motion detection parameter as Json Object 38 | */ 39 | private JsonObject getMDParam() { 40 | return getData().getAsJsonObject("MDParam"); 41 | } 42 | 43 | /** 44 | * What is it? 45 | * 46 | * @return 47 | */ 48 | @SuppressWarnings("unused") 49 | private JsonObject getPDParam() { 50 | return getData().getAsJsonObject("PDParam"); 51 | } 52 | 53 | /** 54 | * Get MD object size 55 | * 56 | * @return 57 | */ 58 | public CameraEventResponseObject getObjectSize() { 59 | return new CameraEventResponseObject(getMDParam().getAsJsonObject("objectSize")); 60 | } 61 | 62 | /** 63 | * Get MD sensitivity 64 | * 65 | * @return 66 | */ 67 | public CameraEventResponseObject getSensitivity() { 68 | return new CameraEventResponseObject(getMDParam().getAsJsonObject("sensitivity")); 69 | } 70 | 71 | /** 72 | * Get MD threshold 73 | * 74 | * @return 75 | */ 76 | public CameraEventResponseObject getThreshold() { 77 | return new CameraEventResponseObject(getMDParam().getAsJsonObject("threshold")); 78 | } 79 | 80 | /** 81 | * Get MD history 82 | * 83 | * @return 84 | */ 85 | public CameraEventResponseObject getHistory() { 86 | return new CameraEventResponseObject(getMDParam().getAsJsonObject("history")); 87 | } 88 | 89 | /** 90 | * Get MD shortLiveSecond 91 | * 92 | * @return 93 | */ 94 | public CameraEventResponseObject getShortLiveSecond() { 95 | return new CameraEventResponseObject(getMDParam().getAsJsonObject("shortLiveSecond")); 96 | } 97 | 98 | /** 99 | * Get MD percentage 100 | * 101 | * @return 102 | */ 103 | public CameraEventResponseObject getPercentage() { 104 | return new CameraEventResponseObject(getMDParam().getAsJsonObject("percentage")); 105 | } 106 | 107 | /** 108 | * Get MD object size 109 | * 110 | * @return 111 | */ 112 | public String getSource() { 113 | return getMDParam().get("source").getAsString(); 114 | } 115 | 116 | /** 117 | * Get MD keep parameter 118 | * 119 | * @return 120 | */ 121 | public boolean getKeep() { 122 | return getMDParam().get("keep").getAsBoolean(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/EventResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import org.eclipse.jdt.annotation.NonNullByDefault; 19 | import org.openhab.binding.synologysurveillancestation.internal.webapi.SynoEvent; 20 | 21 | import com.google.gson.JsonArray; 22 | import com.google.gson.JsonElement; 23 | import com.google.gson.JsonObject; 24 | 25 | /** 26 | * {@link EventResponse} is a response with current events 27 | * 28 | * @author Pavion - Initial contribution 29 | */ 30 | @NonNullByDefault 31 | public class EventResponse extends SimpleResponse { 32 | private static final int EVENT_POLL_OVERHEAD = 30; 33 | 34 | private Map synoEvents = new HashMap<>(); 35 | private long timestamp = 0; 36 | 37 | /** 38 | * Constructs SynoEvents from JSON string. 39 | * 40 | * @param jsonResponse 41 | */ 42 | public EventResponse(String jsonResponse) { 43 | super(jsonResponse); 44 | if (isSuccess()) { 45 | JsonArray events = getData().getAsJsonArray("events"); 46 | timestamp = getData().getAsJsonObject().get("timestamp").getAsLong() - EVENT_POLL_OVERHEAD; 47 | for (JsonElement event : events) { 48 | if (event.isJsonObject()) { 49 | JsonObject cam = event.getAsJsonObject(); 50 | int reason = cam.get("reason").getAsInt(); 51 | if (!hasEvent(reason)) { 52 | long starttime = cam.get("startTime").getAsLong(); 53 | long eventId = cam.get("eventId").getAsLong(); 54 | boolean eventCompleted = cam.get("is_complete").getAsBoolean(); 55 | synoEvents.put(reason, new SynoEvent(eventId, eventCompleted, reason)); 56 | if (!eventCompleted && starttime < timestamp) { 57 | timestamp = starttime; 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * How many events were returned? 67 | * 68 | * @return 69 | */ 70 | public boolean isEmpty() { 71 | return synoEvents.isEmpty(); 72 | } 73 | 74 | /** 75 | * Get the first (latest) event (if available) 76 | * 77 | * @return 78 | */ 79 | public SynoEvent getFirst() { 80 | if (synoEvents.isEmpty()) { 81 | return new SynoEvent(0); 82 | } else { 83 | return synoEvents.getOrDefault(0, new SynoEvent(0)); 84 | } 85 | } 86 | 87 | /** 88 | * @return if the event with specified reason exists 89 | */ 90 | public boolean hasEvent(int eventReason) { 91 | return synoEvents.containsKey(eventReason); 92 | } 93 | 94 | /** 95 | * @return the event with specified reason 96 | */ 97 | public SynoEvent getEvent(int eventReason) { 98 | return synoEvents.getOrDefault(eventReason, new SynoEvent(0)); 99 | } 100 | 101 | /** 102 | * @return the timestamp 103 | */ 104 | public long getTimestamp() { 105 | return timestamp; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/SynoCameraConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal; 14 | 15 | import java.util.Objects; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.eclipse.jdt.annotation.Nullable; 19 | 20 | /** 21 | * The {@link SynoCameraConfig} is class for handling the camera Thing configuration 22 | * 23 | * @author Pavion - Initial contribution 24 | */ 25 | @NonNullByDefault 26 | public class SynoCameraConfig { 27 | private int refreshRateSnapshot = 10; 28 | private int refreshRateEvents = 3; 29 | private int refreshRateMdParam = 0; 30 | private int snapshotStreamId = 1; 31 | 32 | /** 33 | * @return refreshRateSnapshot the refreshRateSnapshot to set 34 | */ 35 | public int getRefreshRateSnapshot() { 36 | return refreshRateSnapshot; 37 | } 38 | 39 | /** 40 | * @param refreshRateSnapshot the refreshRateSnapshot to set 41 | */ 42 | public void setRefreshRateSnapshot(int refreshRateSnapshot) { 43 | this.refreshRateSnapshot = refreshRateSnapshot; 44 | } 45 | 46 | /** 47 | * @return refreshRateEvents 48 | */ 49 | public int getRefreshRateEvents() { 50 | return refreshRateEvents; 51 | } 52 | 53 | /** 54 | * @param refreshRateEvents the refreshRateEvents to set 55 | */ 56 | public void setRefreshRateEvents(int refreshRateEvents) { 57 | this.refreshRateEvents = refreshRateEvents; 58 | } 59 | 60 | /** 61 | * @param refreshRateMdParam the refreshRateMdParam to set 62 | */ 63 | public void setRefreshRateMdParam(int refreshRateMdParam) { 64 | this.refreshRateMdParam = refreshRateMdParam; 65 | } 66 | 67 | /** 68 | * @return refreshRateMdParam 69 | */ 70 | public int getRefreshRateMdParam() { 71 | return refreshRateMdParam; 72 | } 73 | 74 | /** 75 | * @param refreshRateMdParam the refreshRateMdParam to set 76 | */ 77 | public void setSnapshotStreamId(int snapshotStreamId) { 78 | this.snapshotStreamId = snapshotStreamId; 79 | } 80 | 81 | /** 82 | * @return refreshRateMdParam 83 | */ 84 | public int getSnapshotStreamId() { 85 | return snapshotStreamId; 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | return Objects.hash(refreshRateEvents, refreshRateMdParam, refreshRateSnapshot, snapshotStreamId); 91 | } 92 | 93 | @Override 94 | public boolean equals(@Nullable Object obj) { 95 | if (this == obj) { 96 | return true; 97 | } 98 | if (obj == null) { 99 | return false; 100 | } 101 | if (getClass() != obj.getClass()) { 102 | return false; 103 | } 104 | SynoCameraConfig other = (SynoCameraConfig) obj; 105 | return refreshRateEvents == other.refreshRateEvents && refreshRateMdParam == other.refreshRateMdParam 106 | && refreshRateSnapshot == other.refreshRateSnapshot && snapshotStreamId == other.snapshotStreamId; 107 | } 108 | 109 | @Override 110 | public String toString() { 111 | return "SynoCameraConfig [refreshRateSnapshot=" + refreshRateSnapshot + ", refreshRateEvents=" 112 | + refreshRateEvents + ", refreshRateMdParam=" + refreshRateMdParam + ", snapshotStreamId=" 113 | + snapshotStreamId + "]"; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/SynoApiResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | 17 | import com.google.gson.JsonArray; 18 | import com.google.gson.JsonObject; 19 | import com.google.gson.JsonParser; 20 | import com.google.gson.JsonSyntaxException; 21 | 22 | /** 23 | * {@link SynoApiResponse} is an abstract class for an API response 24 | * 25 | * @author Nils - Initial contribution 26 | * @author Pavion - Contribution 27 | */ 28 | @NonNullByDefault 29 | public abstract class SynoApiResponse { 30 | 31 | public static final String PROP_VENDOR = "vendor"; 32 | public static final String PROP_MODEL = "model"; 33 | public static final String PROP_DEVICETYPE = "deviceType"; 34 | public static final String PROP_HOST = "host"; 35 | public static final String PROP_RESOLUTION = "resolution"; 36 | public static final String PROP_TYPE = "type"; 37 | // public static final String PROP_CAMERANUMBER = "cameraNumber"; 38 | 39 | // PTZ capabilities 40 | public static final String PROP_PTZ = "ptz"; 41 | public static final String PROP_PTZ_PAN = "ptz_pan"; 42 | public static final String PROP_PTZ_TILT = "ptz_tilt"; 43 | public static final String PROP_PTZ_ZOOM = "ptz_zoom"; 44 | public static final String PROP_PTZ_HOME = "ptz_home"; 45 | public static final String PROP_PTZ_ABS = "ptz_abs"; 46 | public static final String PROP_PTZ_FOCUS = "ptz_focus"; 47 | public static final String PROP_PTZ_AUTOFOCUS = "ptz_autofocus"; 48 | public static final String PROP_PTZ_IRIS = "ptz_iris"; 49 | public static final String PROP_PTZ_SPEED = "ptz_speed"; 50 | public static final String PROP_PTZ_ZOOM_SPEED = "ptz_zoom_speed"; 51 | 52 | private JsonObject jsonResponse = new JsonParser().parse("{\"data\":{},\"success\":false}").getAsJsonObject(); 53 | 54 | public SynoApiResponse() { 55 | } 56 | 57 | /** 58 | * @param jsonResponse 59 | */ 60 | public SynoApiResponse(String jsonResponse) { 61 | try { 62 | JsonObject json = new JsonParser().parse(jsonResponse).getAsJsonObject(); 63 | this.jsonResponse = json; 64 | } catch (JsonSyntaxException e) { 65 | // keep default value 66 | } 67 | } 68 | 69 | /** 70 | * @return 71 | */ 72 | public JsonObject getData() { 73 | JsonObject ret = jsonResponse.getAsJsonObject("data"); 74 | if (ret == null) { 75 | return new JsonObject(); 76 | } 77 | return ret; 78 | } 79 | 80 | /** 81 | * 82 | * @return 83 | */ 84 | public JsonArray getDataAsJsonArray() { 85 | JsonArray ret = jsonResponse.getAsJsonArray("data"); 86 | if (ret == null) { 87 | return new JsonArray(); 88 | } 89 | return ret; 90 | } 91 | 92 | /** 93 | * @return 94 | */ 95 | public boolean isSuccess() { 96 | if (jsonResponse.has("success")) { 97 | return jsonResponse.get("success").getAsBoolean(); 98 | } 99 | return false; 100 | } 101 | 102 | /** 103 | * @return 104 | */ 105 | public int getErrorcode() { 106 | if (jsonResponse.has("error")) { 107 | if (jsonResponse.getAsJsonObject("error").has("code")) { 108 | return jsonResponse.getAsJsonObject("error").get("code").getAsInt(); 109 | } 110 | } 111 | return 0; 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return jsonResponse.toString(); 117 | } 118 | 119 | /** 120 | * @param hexValue 121 | * @param bitNumber 122 | * @return 123 | */ 124 | protected boolean isBitSet(String hexValue, int bitNumber) { 125 | int val = Integer.valueOf(hexValue, 16); 126 | return (val & (1 << bitNumber)) != 0; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/discovery/BridgeMdnsDiscoveryService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.discovery; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | import javax.jmdns.ServiceInfo; 20 | 21 | import org.eclipse.jdt.annotation.NonNullByDefault; 22 | import org.eclipse.jdt.annotation.Nullable; 23 | import org.openhab.binding.synologysurveillancestation.SynoBindingConstants; 24 | import org.openhab.core.config.discovery.DiscoveryResult; 25 | import org.openhab.core.config.discovery.DiscoveryResultBuilder; 26 | import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; 27 | import org.openhab.core.thing.ThingTypeUID; 28 | import org.openhab.core.thing.ThingUID; 29 | import org.osgi.service.component.ComponentContext; 30 | import org.osgi.service.component.annotations.Activate; 31 | import org.osgi.service.component.annotations.Component; 32 | 33 | /** 34 | * The {@link BridgeMdnsDiscoveryService} is a class for discovering the DiskStation via mDNS service 35 | * 36 | * @author Pavion - Initial contribution 37 | */ 38 | @Component(service = MDNSDiscoveryParticipant.class, immediate = false, configurationPid = "binding.synologysurveillancestation") 39 | @NonNullByDefault 40 | public class BridgeMdnsDiscoveryService implements MDNSDiscoveryParticipant { 41 | 42 | // private final Logger logger = LoggerFactory.getLogger(BridgeMdnsDiscoveryService.class); 43 | 44 | @Activate 45 | public void activate(ComponentContext context) { 46 | } 47 | 48 | @Override 49 | public Set getSupportedThingTypeUIDs() { 50 | return SynoBindingConstants.SUPPORTED_BRIDGE_TYPES; 51 | } 52 | 53 | @Override 54 | public String getServiceType() { 55 | return "_http._tcp.local."; 56 | } 57 | 58 | @Override 59 | @Nullable 60 | public DiscoveryResult createResult(ServiceInfo service) { 61 | ThingUID uid = getThingUID(service); 62 | if (uid != null) { 63 | if (service.getHostAddresses() != null && service.getHostAddresses().length > 0 64 | && !service.getHostAddresses()[0].isEmpty()) { 65 | String name = service.getName(); 66 | String ip = service.getHostAddresses()[0]; 67 | String model = service.getPropertyString("model"); 68 | String serial = service.getPropertyString("serial"); 69 | String port = service.getPropertyString("admin_port"); 70 | 71 | if (name != null && ip != null && model != null && serial != null && port != null) { 72 | String label = String.format("%s (%s)", name, model); 73 | Map properties = new HashMap<>(); 74 | properties.put(SynoBindingConstants.ACCEPT_SSL, false); 75 | properties.put(SynoBindingConstants.PROTOCOL, "http"); 76 | properties.put(SynoBindingConstants.PORT, port); 77 | properties.put(SynoBindingConstants.HOST, ip); 78 | // properties.put(SynoBindingConstants.USER_NAME, ""); 79 | // properties.put(SynoBindingConstants.PASSWORD, ""); 80 | properties.put(SynoBindingConstants.SERIAL, serial.toLowerCase()); 81 | 82 | DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties) 83 | .withRepresentationProperty(SynoBindingConstants.SERIAL).withLabel(label).build(); 84 | return result; 85 | } 86 | } 87 | } 88 | return null; 89 | } 90 | 91 | @Override 92 | @Nullable 93 | public ThingUID getThingUID(ServiceInfo service) { 94 | String vendor = service.getPropertyString("vendor"); 95 | String serial = service.getPropertyString("serial"); 96 | if (vendor != null && serial != null) { 97 | if (vendor.startsWith("Synology")) { 98 | return new ThingUID(SynoBindingConstants.THING_TYPE_STATION, serial.toLowerCase()); 99 | } 100 | } 101 | return null; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/thread/SynoApiThreadCamera.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.thread; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.*; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.openhab.binding.synologysurveillancestation.handler.SynoCameraHandler; 19 | import org.openhab.binding.synologysurveillancestation.internal.SynoCameraConfig; 20 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 22 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.CameraResponse; 23 | import org.openhab.core.library.types.OnOffType; 24 | import org.openhab.core.library.types.StringType; 25 | import org.openhab.core.thing.Channel; 26 | 27 | /** 28 | * Thread for getting camera state (enabled, recording) 29 | * 30 | * @author Pavion - Initial contribution 31 | */ 32 | @NonNullByDefault 33 | public class SynoApiThreadCamera extends SynoApiThread { 34 | // private final Logger logger = LoggerFactory.getLogger(SynoApiThreadCamera.class); 35 | 36 | public SynoApiThreadCamera(SynoCameraHandler handler, int refreshRate) { 37 | super(SynoApiThread.THREAD_CAMERA, handler, refreshRate); 38 | } 39 | 40 | @Override 41 | public boolean isNeeded() { 42 | boolean ret = getSynoHandler().isLinked(CHANNEL_ENABLE) || getSynoHandler().isLinked(CHANNEL_RECORD) 43 | || getSynoHandler().isLinked(CHANNEL_SNAPSHOT_URI_DYNAMIC); 44 | if (getSynoHandler().isPtz()) { 45 | ret = ret || getSynoHandler().isLinked(CHANNEL_MOVEPRESET) || getSynoHandler().isLinked(CHANNEL_RUNPATROL); 46 | } 47 | return ret; 48 | } 49 | 50 | @Override 51 | public boolean refresh() throws Exception { 52 | boolean ret = true; 53 | 54 | SynoCameraHandler cameraHandler = getSynoHandler(); 55 | String cameraId = cameraHandler.getCameraId(); 56 | 57 | if (cameraHandler.isLinked(CHANNEL_SNAPSHOT_URI_DYNAMIC)) { 58 | Channel channel = cameraHandler.getThing().getChannel(CHANNEL_SNAPSHOT_URI_DYNAMIC); 59 | SynoCameraConfig config = cameraHandler.getThing().getConfiguration().as(SynoCameraConfig.class); 60 | String path = cameraHandler.getSynoWebApiHandler().getApiCamera().getSnapshotUri(cameraId, 61 | config.getSnapshotStreamId()); 62 | path += "×tamp=" + String.valueOf(System.currentTimeMillis()); 63 | cameraHandler.updateState(channel.getUID(), new StringType(path)); 64 | } 65 | 66 | if (cameraHandler.isPtz()) { 67 | if (cameraHandler.isLinked(CHANNEL_MOVEPRESET)) { 68 | cameraHandler.updatePresets(); 69 | } 70 | if (cameraHandler.isLinked(CHANNEL_RUNPATROL)) { 71 | cameraHandler.updatePatrols(); 72 | } 73 | } 74 | 75 | if (cameraHandler.isLinked(CHANNEL_ENABLE) || cameraHandler.isLinked(CHANNEL_RECORD)) { 76 | CameraResponse response = cameraHandler.getSynoWebApiHandler().getApiCamera().getInfo(cameraId); 77 | if (response.isSuccess()) { 78 | if (cameraHandler.isLinked(CHANNEL_ENABLE)) { 79 | Channel channel = cameraHandler.getThing().getChannel(CHANNEL_ENABLE); 80 | cameraHandler.updateState(channel.getUID(), 81 | response.isEnabled(cameraId) ? OnOffType.ON : OnOffType.OFF); 82 | } 83 | if (cameraHandler.isLinked(CHANNEL_RECORD)) { 84 | Channel channel = cameraHandler.getThing().getChannel(CHANNEL_RECORD); 85 | cameraHandler.updateState(channel.getUID(), 86 | response.isRecording(cameraId) ? OnOffType.ON : OnOffType.OFF); 87 | } 88 | 89 | ret &= true; 90 | 91 | } else if (response.getErrorcode() == 105) { 92 | throw new WebApiException(WebApiAuthErrorCodes.INSUFFICIENT_USER_PRIVILEGE); 93 | } else { 94 | ret &= false; 95 | } 96 | } 97 | 98 | return ret; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/SynoConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal; 14 | 15 | import org.eclipse.jdt.annotation.NonNullByDefault; 16 | import org.eclipse.jdt.annotation.Nullable; 17 | 18 | /** 19 | * The {@link SynoConfig} is class for handling the binding configuration 20 | * 21 | * @author Nils - Initial contribution 22 | * @author Pavion - Contribution 23 | */ 24 | @NonNullByDefault 25 | public class SynoConfig { 26 | private String protocol = "http"; 27 | private boolean acceptSsl = false; 28 | private String host = ""; 29 | private int port = 5000; 30 | private String username = ""; 31 | private String password = ""; 32 | private int refreshRateEvents = 5; 33 | 34 | /** 35 | * Returns the protocol. 36 | * 37 | * @return the protocol 38 | */ 39 | public String getProtocol() { 40 | return protocol; 41 | } 42 | 43 | /** 44 | * Returns accept SSL state 45 | * 46 | * @return true if to accept everything, false by default 47 | */ 48 | public boolean isAcceptSsl() { 49 | return acceptSsl; 50 | } 51 | 52 | /** 53 | * Returns the host name Surveillance Station. 54 | * 55 | * @return the host address 56 | */ 57 | public String getHost() { 58 | return host; 59 | } 60 | 61 | /** 62 | * Returns the port. 63 | * 64 | * @return the port 65 | */ 66 | public int getPort() { 67 | return port; 68 | } 69 | 70 | /** 71 | * Returns the username. 72 | * 73 | * @return the username 74 | */ 75 | public String getUsername() { 76 | return username; 77 | } 78 | 79 | /** 80 | * Returns the password. 81 | * 82 | * @return the password 83 | */ 84 | public String getPassword() { 85 | return password; 86 | } 87 | 88 | /** 89 | * @return the refreshRateEvents 90 | */ 91 | public int getRefreshRateEvents() { 92 | return refreshRateEvents; 93 | } 94 | 95 | @Override 96 | public String toString() { 97 | return "Config [protocol=" + protocol + ", acceptSsl=" + acceptSsl + ", host=" + host + ", port=" + port 98 | + ", username=" + username + ", password=" + "********, refreshRateEvents=" 99 | + String.valueOf(refreshRateEvents) + "]"; 100 | } 101 | 102 | @Override 103 | public boolean equals(@Nullable Object obj) { 104 | if (obj == null) { 105 | return false; 106 | } 107 | if (!(obj instanceof SynoConfig)) { 108 | return false; 109 | } 110 | SynoConfig cfg = (SynoConfig) obj; 111 | return cfg.getHost().equals(getHost()) && cfg.getPassword().equals(getPassword()) 112 | && cfg.isAcceptSsl() == isAcceptSsl() && cfg.getProtocol().equals(getProtocol()) 113 | && cfg.getPort() == port && cfg.getUsername().equals(getUsername()) 114 | && cfg.getRefreshRateEvents() == refreshRateEvents; 115 | } 116 | 117 | /** 118 | * If but refresh is changed 119 | * 120 | * @param obj 121 | * @return 122 | */ 123 | public boolean equalsButForRefresh(@Nullable Object obj) { 124 | if (obj == null) { 125 | return false; 126 | } 127 | if (!(obj instanceof SynoConfig)) { 128 | return false; 129 | } 130 | SynoConfig cfg = (SynoConfig) obj; 131 | return cfg.getHost().equals(getHost()) && cfg.getPassword().equals(getPassword()) 132 | && cfg.isAcceptSsl() == isAcceptSsl() && cfg.getProtocol().equals(getProtocol()) 133 | && cfg.getPort() == port && cfg.getUsername().equals(getUsername()); 134 | } 135 | 136 | /** 137 | * @param protocol the protocol to set 138 | */ 139 | public void setProtocol(String protocol) { 140 | this.protocol = protocol; 141 | } 142 | 143 | /** 144 | * @param acceptSsl the acceptSsl to set 145 | */ 146 | public void setAcceptSsl(boolean acceptSsl) { 147 | this.acceptSsl = acceptSsl; 148 | } 149 | 150 | /** 151 | * @param host the host to set 152 | */ 153 | public void setHost(String host) { 154 | this.host = host; 155 | } 156 | 157 | /** 158 | * @param port the port to set 159 | */ 160 | public void setPort(int port) { 161 | this.port = port; 162 | } 163 | 164 | /** 165 | * @param username the username to set 166 | */ 167 | public void setUsername(String username) { 168 | this.username = username; 169 | } 170 | 171 | /** 172 | * @param password the password to set 173 | */ 174 | public void setPassword(String password) { 175 | this.password = password; 176 | } 177 | 178 | /** 179 | * @param refreshRateEvents the refreshRateEvents to set 180 | */ 181 | public void setRefreshRateEvents(int refreshRateEvents) { 182 | this.refreshRateEvents = refreshRateEvents; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiCameraEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import org.eclipse.jdt.annotation.NonNullByDefault; 19 | import org.eclipse.jetty.client.HttpClient; 20 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 22 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.CameraEventResponse; 23 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.SimpleResponse; 24 | 25 | /** 26 | * SYNO.SurveillanceStation.Camera.Event 27 | * 28 | * Event Detection related WebAPI. e.g. Enumerate detection parameters or long polling for alarm status or save 29 | * detection parameters. 30 | * 31 | * @author Pavion - Initial contribution 32 | * 33 | */ 34 | @NonNullByDefault 35 | public class SynoApiCameraEvent extends SynoApiRequest { 36 | // private final Logger logger = LoggerFactory.getLogger(SynoApiCameraEvent.class); 37 | 38 | // API configuration 39 | private static final String API_NAME = "SYNO.SurveillanceStation.Camera.Event"; 40 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_01, API_SCRIPT_ENTRY); 41 | 42 | /** 43 | * @param config 44 | */ 45 | public SynoApiCameraEvent(SynoConfig config, HttpClient httpClient) { 46 | super(API_CONFIG, config, httpClient); 47 | } 48 | 49 | /** 50 | * Get motion detection parameter 51 | * 52 | * @param cameraId 53 | * @return 54 | * @throws WebApiException 55 | */ 56 | public CameraEventResponse getMDParam(String cameraId) throws WebApiException { 57 | Map params = new HashMap<>(); 58 | params.put("camId", cameraId); 59 | return callApi(METHOD_MOTIONENUM, params); 60 | } 61 | 62 | public SimpleResponse setSource(String cameraId, String source) throws WebApiException { 63 | if (!("-1".equals(source) || "0".equals(source) || "1".equals(source))) { 64 | return new SimpleResponse("{\"data\":{},\"success\":false}"); 65 | } 66 | Map params = new HashMap<>(); 67 | params.put("camId", cameraId); 68 | params.put("keep", "true"); 69 | params.put("source", source); 70 | return callApi(METHOD_MDPARAMSAVE, params); 71 | } 72 | 73 | // Warning, absolute values are used 74 | public SimpleResponse setSensitivity(String cameraId, int val) throws WebApiException { 75 | if (val < 1 || val > 99) { 76 | return new SimpleResponse("{\"data\":{},\"success\":false}"); 77 | } 78 | Map params = new HashMap<>(); 79 | params.put("camId", cameraId); 80 | params.put("keep", "true"); 81 | params.put("sensitivity", String.valueOf(val)); 82 | return callApi(METHOD_MDPARAMSAVE, params); 83 | } 84 | 85 | public SimpleResponse setThreshold(String cameraId, int val) throws WebApiException { 86 | if (val < 1 || val > 99) { 87 | return new SimpleResponse("{\"data\":{},\"success\":false}"); 88 | } 89 | Map params = new HashMap<>(); 90 | params.put("camId", cameraId); 91 | params.put("keep", "true"); 92 | params.put("threshold", String.valueOf(val)); 93 | return callApi(METHOD_MDPARAMSAVE, params); 94 | } 95 | 96 | public SimpleResponse setObjectSize(String cameraId, int val) throws WebApiException { 97 | if (val < 1 || val > 99) { 98 | return new SimpleResponse("{\"data\":{},\"success\":false}"); 99 | } 100 | Map params = new HashMap<>(); 101 | params.put("camId", cameraId); 102 | params.put("keep", "true"); 103 | params.put("objectSize", String.valueOf(val)); 104 | return callApi(METHOD_MDPARAMSAVE, params); 105 | } 106 | 107 | public SimpleResponse setPercentage(String cameraId, int val) throws WebApiException { 108 | if (val < 1 || val > 99) { 109 | return new SimpleResponse("{\"data\":{},\"success\":false}"); 110 | } 111 | Map params = new HashMap<>(); 112 | params.put("camId", cameraId); 113 | params.put("keep", "true"); 114 | params.put("percentage", String.valueOf(val)); 115 | return callApi(METHOD_MDPARAMSAVE, params); 116 | } 117 | 118 | public SimpleResponse setShortLiveSecond(String cameraId, int val) throws WebApiException { 119 | if (val < 0 || val > 10) { 120 | return new SimpleResponse("{\"data\":{},\"success\":false}"); 121 | } 122 | Map params = new HashMap<>(); 123 | params.put("camId", cameraId); 124 | params.put("keep", "true"); 125 | params.put("shortLiveSecond", String.valueOf(val)); 126 | return callApi(METHOD_MDPARAMSAVE, params); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/thread/SynoApiThreadEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.thread; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.*; 16 | 17 | import java.time.ZonedDateTime; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import org.eclipse.jdt.annotation.NonNullByDefault; 22 | import org.openhab.binding.synologysurveillancestation.handler.SynoCameraHandler; 23 | import org.openhab.binding.synologysurveillancestation.internal.webapi.SynoEvent; 24 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 25 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 26 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.EventResponse; 27 | import org.openhab.core.library.types.OnOffType; 28 | import org.openhab.core.thing.Channel; 29 | 30 | /** 31 | * Thread for getting camera events (motion, alarm) 32 | * 33 | * @author Pavion - Initial contribution 34 | */ 35 | @NonNullByDefault 36 | public class SynoApiThreadEvent extends SynoApiThread { 37 | // private final Logger logger = LoggerFactory.getLogger(SynoApiThreadEvent.class); 38 | 39 | private long lastEventTime; 40 | private Map events = new HashMap<>(); 41 | 42 | public SynoApiThreadEvent(SynoCameraHandler handler, int refreshRate) { 43 | super(SynoApiThread.THREAD_EVENT, handler, refreshRate); 44 | lastEventTime = ZonedDateTime.now().minusSeconds(refreshRate * 2).toEpochSecond(); 45 | events.put(CHANNEL_EVENT_MOTION, new SynoEvent(SynoEvent.EVENT_REASON_MOTION)); 46 | events.put(CHANNEL_EVENT_ALARM, new SynoEvent(SynoEvent.EVENT_REASON_ALARM)); 47 | events.put(CHANNEL_EVENT_MANUAL, new SynoEvent(SynoEvent.EVENT_REASON_MANUAL)); 48 | events.put(CHANNEL_EVENT_CONTINUOUS, new SynoEvent(SynoEvent.EVENT_REASON_CONTINUOUS)); 49 | events.put(CHANNEL_EVENT_EXTERNAL, new SynoEvent(SynoEvent.EVENT_REASON_EXTERNAL)); 50 | events.put(CHANNEL_EVENT_ACTIONRULE, new SynoEvent(SynoEvent.EVENT_REASON_ACTIONRULE)); 51 | } 52 | 53 | @Override 54 | public boolean isNeeded() { 55 | return (getSynoHandler().isLinked(CHANNEL_EVENT_MOTION) || getSynoHandler().isLinked(CHANNEL_EVENT_ALARM) 56 | || getSynoHandler().isLinked(CHANNEL_EVENT_MANUAL) 57 | || getSynoHandler().isLinked(CHANNEL_EVENT_CONTINUOUS) 58 | || getSynoHandler().isLinked(CHANNEL_EVENT_EXTERNAL) 59 | || getSynoHandler().isLinked(CHANNEL_EVENT_ACTIONRULE)); 60 | } 61 | 62 | @Override 63 | public boolean refresh() throws Exception { 64 | SynoCameraHandler cameraHandler = getSynoHandler(); 65 | 66 | for (String eventType : events.keySet()) { 67 | if (getSynoHandler().isLinked(eventType)) { 68 | Channel channel = cameraHandler.getThing().getChannel(eventType); 69 | SynoEvent event = events.get(eventType); 70 | EventResponse response = cameraHandler.getSynoWebApiHandler().getApiEvent() 71 | .getEventResponse(cameraHandler.getCameraId(), lastEventTime, event.getReason()); 72 | if (response.isSuccess()) { 73 | if (!response.isEmpty()) { 74 | SynoEvent responseEvent = response.getFirst(); 75 | if (responseEvent.getEventId() != event.getEventId()) { 76 | event.setEventId(responseEvent.getEventId()); 77 | event.setEventCompleted(responseEvent.isEventCompleted()); 78 | cameraHandler.updateState(channel.getUID(), OnOffType.ON); 79 | if (responseEvent.isEventCompleted()) { 80 | cameraHandler.updateState(channel.getUID(), OnOffType.OFF); 81 | } 82 | } else if (responseEvent.getEventId() == event.getEventId() && responseEvent.isEventCompleted() 83 | && !event.isEventCompleted()) { 84 | event.setEventCompleted(true); 85 | cameraHandler.updateState(channel.getUID(), OnOffType.OFF); 86 | } 87 | } else { 88 | event.setEventCompleted(true); 89 | cameraHandler.updateState(channel.getUID(), OnOffType.OFF); 90 | } 91 | if (response.getTimestamp() > lastEventTime) { 92 | lastEventTime = response.getTimestamp(); 93 | } 94 | } else if (response.getErrorcode() == 105) { 95 | throw new WebApiException(WebApiAuthErrorCodes.INSUFFICIENT_USER_PRIVILEGE); 96 | } else { 97 | return false; 98 | } 99 | } 100 | } 101 | return true; 102 | } 103 | 104 | /** 105 | * @return the events 106 | */ 107 | public Map getEvents() { 108 | return events; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/SynoBindingConstants.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation; 14 | 15 | import java.util.Collections; 16 | import java.util.Set; 17 | import java.util.stream.Collectors; 18 | import java.util.stream.Stream; 19 | 20 | import org.eclipse.jdt.annotation.NonNullByDefault; 21 | import org.openhab.core.thing.ThingTypeUID; 22 | 23 | /** 24 | * The {@link SynoBindingConstants} class defines common constants, which are 25 | * used across the whole binding. 26 | * 27 | * @author Nils - Initial contribution 28 | * @author Pavion - Contribution 29 | */ 30 | @NonNullByDefault 31 | public class SynoBindingConstants { 32 | 33 | public static final String BINDING_ID = "synologysurveillancestation"; 34 | 35 | public static final String DEVICE_ID = "deviceID"; 36 | 37 | // List of all Thing Type UIDs 38 | public static final ThingTypeUID THING_TYPE_CAMERA = new ThingTypeUID(BINDING_ID, "camera"); 39 | public static final ThingTypeUID THING_TYPE_STATION = new ThingTypeUID(BINDING_ID, "station"); 40 | 41 | public static final Set SUPPORTED_BRIDGE_TYPES = Collections.singleton(THING_TYPE_STATION); 42 | public static final Set SUPPORTED_CAMERA_TYPES = Collections.singleton(THING_TYPE_CAMERA); 43 | public static final Set SUPPORTED_THING_TYPES = Collections 44 | .unmodifiableSet(Stream.of(THING_TYPE_CAMERA, THING_TYPE_STATION).collect(Collectors.toSet())); 45 | 46 | /* List of all config properties */ 47 | public static final String PROTOCOL = "protocol"; 48 | public static final String ACCEPT_SSL = "acceptSsl"; 49 | public static final String HOST = "host"; 50 | public static final String PORT = "port"; 51 | public static final String USER_NAME = "username"; 52 | public static final String PASSWORD = "password"; 53 | public static final String SERIAL = "serial"; 54 | public static final String SESSION_ID = "sessionID"; 55 | 56 | // List of all Bridge Channels 57 | public static final String CHANNEL_HOMEMODE = "homemode"; 58 | public static final String CHANNEL_EVENT_TRIGGER = "eventtrigger"; 59 | public static final String CHANNEL_SID = "sid"; 60 | 61 | // List of all Channel ids 62 | public static final String CHANNEL_SNAPSHOT_URI_DYNAMIC = "common#snapshot-uri-dynamic"; 63 | public static final String CHANNEL_SNAPSHOT_URI_STATIC = "common#snapshot-uri-static"; 64 | public static final String CHANNEL_LIVE_URI_RTSP = "common#live-uri-rtsp"; 65 | public static final String CHANNEL_LIVE_URI_MJPEG_HTTP = "common#live-uri-mjpeg-http"; 66 | 67 | public static final Set STATIC_CHANNELS = Collections 68 | .unmodifiableSet(Stream.of(CHANNEL_SNAPSHOT_URI_STATIC, CHANNEL_LIVE_URI_RTSP, CHANNEL_LIVE_URI_MJPEG_HTTP) 69 | .collect(Collectors.toSet())); 70 | 71 | public static final String CHANNEL_SNAPSHOT = "common#snapshot"; 72 | public static final String CHANNEL_RECORD = "common#record"; 73 | public static final String CHANNEL_ENABLE = "common#enable"; 74 | 75 | // List of all PTZ channels 76 | public static final String CHANNEL_ZOOM = "ptz#zoom"; 77 | public static final String CHANNEL_MOVE = "ptz#move"; 78 | public static final String CHANNEL_MOVEPRESET = "ptz#movepreset"; 79 | public static final String CHANNEL_RUNPATROL = "ptz#runpatrol"; 80 | public static final Set CHANNEL_PTZ = Collections.unmodifiableSet( 81 | Stream.of(CHANNEL_ZOOM, CHANNEL_MOVE, CHANNEL_MOVEPRESET, CHANNEL_RUNPATROL).collect(Collectors.toSet())); 82 | 83 | // List of all move commands 84 | public static final String MOVE_COMMAND_EMPTY = ""; 85 | public static final String MOVE_COMMAND_START = "Start"; 86 | public static final String MOVE_COMMAND_STOP = "Stop"; 87 | 88 | // List of all event types (as in thing.xml) 89 | public static final String CHANNEL_EVENT_MOTION = "event#motion"; 90 | public static final String CHANNEL_EVENT_ALARM = "event#alarm"; 91 | public static final String CHANNEL_EVENT_MANUAL = "event#manual"; 92 | public static final String CHANNEL_EVENT_EXTERNAL = "event#external"; 93 | public static final String CHANNEL_EVENT_CONTINUOUS = "event#continuous"; 94 | public static final String CHANNEL_EVENT_ACTIONRULE = "event#actionrule"; 95 | public static final Set CHANNEL_EVENT = Collections.unmodifiableSet( 96 | Stream.of(CHANNEL_EVENT_MOTION, CHANNEL_EVENT_ALARM, CHANNEL_EVENT_MANUAL, CHANNEL_EVENT_CONTINUOUS, 97 | CHANNEL_EVENT_EXTERNAL, CHANNEL_EVENT_ACTIONRULE).collect(Collectors.toSet())); 98 | 99 | // List of all MD parameters 100 | public static final String CHANNEL_MDPARAM_SOURCE = "md-param#md-param-source"; 101 | public static final String CHANNEL_MDPARAM_SENSITIVITY = "md-param#md-param-sensitivity"; 102 | public static final String CHANNEL_MDPARAM_THRESHOLD = "md-param#md-param-threshold"; 103 | public static final String CHANNEL_MDPARAM_OBJECTSIZE = "md-param#md-param-objectsize"; 104 | public static final String CHANNEL_MDPARAM_PERCENTAGE = "md-param#md-param-percentage"; 105 | public static final String CHANNEL_MDPARAM_SHORTLIVE = "md-param#md-param-shortlive"; 106 | public static final Set CHANNEL_MDPARAM = Collections.unmodifiableSet(Stream 107 | .of(CHANNEL_MDPARAM_SOURCE, CHANNEL_MDPARAM_SENSITIVITY, CHANNEL_MDPARAM_THRESHOLD, 108 | CHANNEL_MDPARAM_OBJECTSIZE, CHANNEL_MDPARAM_PERCENTAGE, CHANNEL_MDPARAM_SHORTLIVE) 109 | .collect(Collectors.toSet())); 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/discovery/CameraDiscoveryService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.discovery; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.THING_TYPE_CAMERA; 16 | 17 | import java.util.Map; 18 | import java.util.Set; 19 | 20 | import org.eclipse.jdt.annotation.NonNullByDefault; 21 | import org.eclipse.jdt.annotation.Nullable; 22 | import org.openhab.binding.synologysurveillancestation.SynoBindingConstants; 23 | import org.openhab.binding.synologysurveillancestation.handler.SynoBridgeHandler; 24 | import org.openhab.binding.synologysurveillancestation.internal.webapi.SynoWebApiHandler; 25 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 26 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 27 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.CameraResponse; 28 | import org.openhab.core.config.discovery.AbstractDiscoveryService; 29 | import org.openhab.core.config.discovery.DiscoveryResult; 30 | import org.openhab.core.config.discovery.DiscoveryResultBuilder; 31 | import org.openhab.core.thing.ThingStatus; 32 | import org.openhab.core.thing.ThingTypeUID; 33 | import org.openhab.core.thing.ThingUID; 34 | import org.slf4j.Logger; 35 | import org.slf4j.LoggerFactory; 36 | 37 | import com.google.gson.JsonArray; 38 | import com.google.gson.JsonElement; 39 | import com.google.gson.JsonObject; 40 | 41 | /** 42 | * The {@link CameraDiscoveryService} is a service for discovering your cameras through Synology API 43 | * 44 | * @author Nils - Initial contribution 45 | * @author Pavion - Contribution 46 | */ 47 | @NonNullByDefault 48 | public class CameraDiscoveryService extends AbstractDiscoveryService { 49 | 50 | private final Logger logger = LoggerFactory.getLogger(CameraDiscoveryService.class); 51 | 52 | @Nullable 53 | private SynoBridgeHandler bridgeHandler = null; 54 | 55 | /** 56 | * Maximum time to search for devices in seconds. 57 | */ 58 | private static final int SEARCH_TIME = 20; 59 | 60 | public CameraDiscoveryService() { 61 | super(SynoBindingConstants.SUPPORTED_CAMERA_TYPES, SEARCH_TIME); 62 | } 63 | 64 | public CameraDiscoveryService(SynoBridgeHandler bridgeHandler) throws IllegalArgumentException { 65 | super(SEARCH_TIME); 66 | this.bridgeHandler = bridgeHandler; 67 | } 68 | 69 | /** 70 | * Public method for triggering camera discovery 71 | */ 72 | public void discoverCameras() { 73 | startScan(); 74 | } 75 | 76 | @Override 77 | public Set getSupportedThingTypes() { 78 | return SynoBindingConstants.SUPPORTED_THING_TYPES; 79 | } 80 | 81 | @Override 82 | protected void startScan() { 83 | if (bridgeHandler == null) { 84 | return; 85 | } 86 | // Trigger no scan if offline 87 | if (bridgeHandler.getThing().getStatus() != ThingStatus.ONLINE) { 88 | return; 89 | } 90 | 91 | try { 92 | SynoWebApiHandler apiHandler = bridgeHandler.getSynoWebApiHandler(); 93 | 94 | CameraResponse response = apiHandler.getApiCamera().listCameras(); 95 | 96 | if (response.isSuccess()) { 97 | JsonArray cameras = response.getCameras(); 98 | 99 | ThingUID bridgeUID = bridgeHandler.getThing().getUID(); 100 | 101 | if (cameras != null) { 102 | for (JsonElement camera : cameras) { 103 | 104 | if (camera.isJsonObject()) { 105 | JsonObject cam = camera.getAsJsonObject(); 106 | 107 | String cameraId = cam.get("id").getAsString(); 108 | 109 | CameraResponse cameraDetails = apiHandler.getApiCamera().getInfo(cameraId); 110 | 111 | ThingUID thingUID = new ThingUID(THING_TYPE_CAMERA, bridgeUID, cameraId); 112 | 113 | Map properties = cameraDetails.getCameraProperties(cameraId); 114 | 115 | DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) 116 | .withProperties(properties).withBridge(bridgeHandler.getThing().getUID()) 117 | .withLabel(cam.get("name").getAsString()).build(); 118 | 119 | thingDiscovered(discoveryResult); 120 | 121 | logger.debug("Discovered a camera thing with ID '{}'", cameraId); 122 | } 123 | } 124 | } 125 | } 126 | 127 | } catch (WebApiException e) { 128 | if (e.getCause() instanceof javax.net.ssl.SSLHandshakeException 129 | || e.getCause() instanceof java.io.EOFException 130 | || e.getCause() instanceof java.util.concurrent.ExecutionException) { 131 | logger.error("Possible SSL certificate issue, please consider using http or enabling SSL bypass"); 132 | } else if (e.getErrorCode() == 102) { 133 | logger.error("Discovery Thread; Surveillance Station is disabled or not installed"); 134 | } else if (e.getErrorCode() == WebApiAuthErrorCodes.INSUFFICIENT_USER_PRIVILEGE.getCode()) { 135 | logger.debug("Discovery Thread; Wrong/expired credentials"); 136 | try { 137 | bridgeHandler.reconnect(false); 138 | } catch (WebApiException ee) { 139 | logger.error("Discovery Thread; Attempt to reconnect failed"); 140 | } 141 | } else { 142 | logger.error("Discovery Thread; Unexpected error: {} - {}", e.getErrorCode(), e.getMessage()); 143 | } 144 | } catch (Exception npe) { 145 | logger.error("Error in WebApiException", npe); 146 | } 147 | } 148 | 149 | @Override 150 | protected void startBackgroundDiscovery() { 151 | startScan(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/response/CameraResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.response; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.DEVICE_ID; 16 | 17 | import java.util.LinkedHashMap; 18 | import java.util.Map; 19 | 20 | import org.eclipse.jdt.annotation.NonNullByDefault; 21 | import org.eclipse.jdt.annotation.Nullable; 22 | 23 | import com.google.gson.JsonArray; 24 | import com.google.gson.JsonElement; 25 | import com.google.gson.JsonObject; 26 | 27 | /** 28 | * {@link CameraResponse} is a response for camera information 29 | * 30 | * @author Nils - Initial contribution 31 | * @author Pavion - Contribution 32 | */ 33 | @NonNullByDefault 34 | public class CameraResponse extends SimpleResponse { 35 | 36 | // bits for PTZ capability 37 | // 0x001: Pan 38 | private static final int BIT_PTZ_PAN = 0; 39 | // 0x002: Tilt 40 | private static final int BIT_PTZ_TILT = 1; 41 | // 0x004: Zoom 42 | private static final int BIT_PTZ_ZOOM = 3; 43 | // 0x008: Home 44 | private static final int BIT_PTZ_HOME = 4; 45 | // 0x010: Abs position 46 | private static final int BIT_PTZ_ABS = 5; 47 | // 0x020: Focus 48 | private static final int BIT_PTZ_FOCUS = 6; 49 | // 0x040: Auto focus 50 | private static final int BIT_PTZ_AUTOFOCUS = 7; 51 | // 0x080: Iris 52 | private static final int BIT_PTZ_IRIS = 8; 53 | // 0x100: Ptz speed 54 | private static final int BIT_PTZ_SPEED = 9; 55 | // 0x200: Zoom speed 56 | private static final int BIT_PTZ_ZOOM_SPEED = 10; 57 | 58 | /** 59 | * @param jsonResponse 60 | */ 61 | public CameraResponse(String jsonResponse) { 62 | super(jsonResponse); 63 | } 64 | 65 | @Nullable 66 | public JsonArray getCameras() { 67 | return getData().getAsJsonArray("cameras"); 68 | } 69 | 70 | /** 71 | * If the camera is enabled 72 | * 73 | * @param cameraId 74 | */ 75 | public boolean isEnabled(String cameraId) { 76 | for (JsonElement jcamera : getCameras()) { 77 | if (jcamera.isJsonObject()) { 78 | JsonObject camera = jcamera.getAsJsonObject(); 79 | if (camera.get("id").getAsString().equals(cameraId)) { 80 | return camera.get("enabled").getAsBoolean(); 81 | } 82 | } 83 | } 84 | return false; 85 | } 86 | 87 | /** 88 | * If the camera is recording 89 | * 90 | * @param cameraId 91 | */ 92 | public boolean isRecording(String cameraId) { 93 | for (JsonElement jcamera : getCameras()) { 94 | if (jcamera.isJsonObject()) { 95 | JsonObject camera = jcamera.getAsJsonObject(); 96 | if (camera.get("id").getAsString().equals(cameraId)) { 97 | return (camera.get("recStatus").getAsInt() > 0); 98 | } 99 | } 100 | } 101 | return false; 102 | } 103 | 104 | /** 105 | * Creates all relevant properties from response as key/value map. 106 | * 107 | * @param cameraId 108 | * @return 109 | */ 110 | public Map getCameraProperties(String cameraId) { 111 | JsonArray cameras = this.getCameras().getAsJsonArray(); 112 | 113 | for (JsonElement camera : cameras) { 114 | if (camera.isJsonObject()) { 115 | JsonObject cam = camera.getAsJsonObject(); 116 | 117 | String id = cam.get("id").getAsString(); 118 | 119 | if (cameraId.equals(id)) { 120 | return createProperties(cam, cameraId); 121 | } 122 | } 123 | } 124 | return new LinkedHashMap<>(); 125 | } 126 | 127 | /** 128 | * Creates the thing properties from response details. 129 | * 130 | * @param cam 131 | * @param cameraId 132 | * @return 133 | */ 134 | private Map createProperties(JsonObject cam, String cameraId) { 135 | Map properties = new LinkedHashMap<>(); 136 | 137 | properties.put(DEVICE_ID, cameraId); 138 | properties.put(SynoApiResponse.PROP_VENDOR, cam.get(SynoApiResponse.PROP_VENDOR).getAsString()); 139 | properties.put(SynoApiResponse.PROP_MODEL, cam.get(SynoApiResponse.PROP_MODEL).getAsString()); 140 | properties.put(SynoApiResponse.PROP_DEVICETYPE, cam.get(SynoApiResponse.PROP_DEVICETYPE).getAsString()); 141 | properties.put(SynoApiResponse.PROP_HOST, cam.get(SynoApiResponse.PROP_HOST).getAsString()); 142 | properties.put(SynoApiResponse.PROP_RESOLUTION, cam.get(SynoApiResponse.PROP_RESOLUTION).getAsString()); 143 | properties.put(SynoApiResponse.PROP_TYPE, cam.get(SynoApiResponse.PROP_TYPE).getAsString()); 144 | 145 | // check PTZ capabilities 146 | int ptzCap = cam.get("ptzCap").getAsInt(); 147 | properties.put(SynoApiResponse.PROP_PTZ, (ptzCap > 0) ? "true" : "false"); 148 | 149 | if (ptzCap > 0) { 150 | properties.put(SynoApiResponse.PROP_PTZ_PAN, isBitSetAsString(ptzCap, BIT_PTZ_PAN)); 151 | properties.put(SynoApiResponse.PROP_PTZ_TILT, isBitSetAsString(ptzCap, BIT_PTZ_TILT)); 152 | properties.put(SynoApiResponse.PROP_PTZ_ZOOM, isBitSetAsString(ptzCap, BIT_PTZ_ZOOM)); 153 | properties.put(SynoApiResponse.PROP_PTZ_HOME, isBitSetAsString(ptzCap, BIT_PTZ_HOME)); 154 | properties.put(SynoApiResponse.PROP_PTZ_ABS, isBitSetAsString(ptzCap, BIT_PTZ_ABS)); 155 | properties.put(SynoApiResponse.PROP_PTZ_FOCUS, isBitSetAsString(ptzCap, BIT_PTZ_FOCUS)); 156 | properties.put(SynoApiResponse.PROP_PTZ_AUTOFOCUS, isBitSetAsString(ptzCap, BIT_PTZ_AUTOFOCUS)); 157 | properties.put(SynoApiResponse.PROP_PTZ_IRIS, isBitSetAsString(ptzCap, BIT_PTZ_IRIS)); 158 | properties.put(SynoApiResponse.PROP_PTZ_SPEED, isBitSetAsString(ptzCap, BIT_PTZ_SPEED)); 159 | properties.put(SynoApiResponse.PROP_PTZ_ZOOM_SPEED, isBitSetAsString(ptzCap, BIT_PTZ_ZOOM_SPEED)); 160 | } 161 | 162 | return properties; 163 | } 164 | 165 | /** 166 | * @param ptzCap 167 | * @param bit 168 | * @return 169 | */ 170 | private String isBitSetAsString(int hexValue, int bit) { 171 | return Boolean.toString(isBitSet(String.valueOf(hexValue), bit)); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/SynoHandlerFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.*; 16 | 17 | import java.util.HashMap; 18 | import java.util.Hashtable; 19 | import java.util.Map; 20 | 21 | import org.eclipse.jdt.annotation.Nullable; 22 | import org.eclipse.jetty.client.HttpClient; 23 | import org.eclipse.jetty.util.ssl.SslContextFactory; 24 | import org.openhab.binding.synologysurveillancestation.handler.SynoBridgeHandler; 25 | import org.openhab.binding.synologysurveillancestation.handler.SynoCameraHandler; 26 | import org.openhab.binding.synologysurveillancestation.internal.discovery.CameraDiscoveryService; 27 | import org.openhab.binding.synologysurveillancestation.internal.discovery.SynoDynamicStateDescriptionProvider; 28 | import org.openhab.core.config.core.Configuration; 29 | import org.openhab.core.config.discovery.DiscoveryService; 30 | import org.openhab.core.io.net.http.HttpClientFactory; 31 | import org.openhab.core.thing.Bridge; 32 | import org.openhab.core.thing.Thing; 33 | import org.openhab.core.thing.ThingTypeUID; 34 | import org.openhab.core.thing.ThingUID; 35 | import org.openhab.core.thing.binding.BaseThingHandlerFactory; 36 | import org.openhab.core.thing.binding.ThingHandler; 37 | import org.openhab.core.thing.binding.ThingHandlerFactory; 38 | import org.osgi.framework.ServiceRegistration; 39 | import org.osgi.service.component.ComponentContext; 40 | import org.osgi.service.component.annotations.Activate; 41 | import org.osgi.service.component.annotations.Component; 42 | import org.osgi.service.component.annotations.Deactivate; 43 | import org.osgi.service.component.annotations.Reference; 44 | import org.slf4j.Logger; 45 | import org.slf4j.LoggerFactory; 46 | 47 | /** 48 | * The {@link SynoHandlerFactory} is responsible for creating things and thing 49 | * handlers. 50 | * 51 | * @author Nils - Initial contribution 52 | * @author Pavion - Contribution 53 | */ 54 | @Component(service = ThingHandlerFactory.class, configurationPid = "binding.synologysurveillancestation") 55 | // @NonNullByDefault 56 | public class SynoHandlerFactory extends BaseThingHandlerFactory { 57 | 58 | private final Logger logger = LoggerFactory.getLogger(SynoHandlerFactory.class); 59 | private Map> discoveryServiceRegs = new HashMap<>(); 60 | private HttpClient httpClient; 61 | private boolean acceptSsl = false; 62 | private SynoBridgeHandler bridgeHandler = null; 63 | 64 | private SynoDynamicStateDescriptionProvider stateDescriptionProvider; 65 | 66 | @Reference 67 | protected void setHttpClientFactory(HttpClientFactory httpClientFactory) { 68 | this.httpClient = httpClientFactory.getCommonHttpClient(); 69 | } 70 | 71 | protected void unsetHttpClientFactory(HttpClientFactory httpClientFactory) { 72 | if (this.acceptSsl) { 73 | try { 74 | this.httpClient.stop(); 75 | } catch (Exception e) { 76 | logger.error("Couldn't stop trusting HttpServer, sorry"); 77 | } 78 | } 79 | this.httpClient = null; 80 | } 81 | 82 | @Override 83 | @Activate 84 | protected void activate(ComponentContext componentContext) { 85 | super.activate(componentContext); 86 | } 87 | 88 | @Override 89 | @Deactivate 90 | protected void deactivate(ComponentContext componentContext) { 91 | super.deactivate(componentContext); 92 | } 93 | 94 | @Override 95 | public boolean supportsThingType(ThingTypeUID thingTypeUID) { 96 | return SUPPORTED_THING_TYPES.contains(thingTypeUID); 97 | } 98 | 99 | @Override 100 | public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, 101 | @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { 102 | if (SUPPORTED_BRIDGE_TYPES.contains(thingTypeUID)) { 103 | return super.createThing(thingTypeUID, configuration, thingUID, null); 104 | } else if (SUPPORTED_THING_TYPES.contains(thingTypeUID)) { 105 | ThingUID myUID = thingUID; 106 | if (myUID == null) { 107 | myUID = new ThingUID(thingTypeUID, "camera"); 108 | } 109 | return super.createThing(thingTypeUID, configuration, myUID, bridgeUID); 110 | } 111 | throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by this binding."); 112 | } 113 | 114 | @Override 115 | protected @Nullable ThingHandler createHandler(Thing thing) { 116 | ThingTypeUID thingTypeUID = thing.getThingTypeUID(); 117 | 118 | if (thingTypeUID.equals(THING_TYPE_STATION)) { 119 | SynoConfig config = thing.getConfiguration().as(SynoConfig.class); 120 | if (config.isAcceptSsl()) { 121 | SslContextFactory sslContextFactory = new SslContextFactory(true); 122 | sslContextFactory.setTrustAll(true); 123 | sslContextFactory.setEndpointIdentificationAlgorithm(null); 124 | HttpClient client = new HttpClient(sslContextFactory); 125 | try { 126 | client.start(); 127 | this.httpClient = client; 128 | logger.debug("Trusting HttpServer started"); 129 | this.acceptSsl = true; 130 | } catch (Exception e) { 131 | logger.error("Trusting HttpServer failed"); 132 | this.acceptSsl = false; 133 | } 134 | } 135 | bridgeHandler = new SynoBridgeHandler((Bridge) thing, httpClient); 136 | CameraDiscoveryService discoveryService = new CameraDiscoveryService(bridgeHandler); 137 | bridgeHandler.setDiscovery(discoveryService); 138 | this.discoveryServiceRegs.put(thing.getUID(), bundleContext.registerService( 139 | DiscoveryService.class.getName(), discoveryService, new Hashtable())); 140 | 141 | return bridgeHandler; 142 | 143 | } else if (thingTypeUID.equals(THING_TYPE_CAMERA) && bridgeHandler != null) { 144 | return new SynoCameraHandler(thing, stateDescriptionProvider); 145 | } 146 | return null; 147 | } 148 | 149 | @Override 150 | protected void removeHandler(ThingHandler handler) { 151 | if (handler.getThing().getThingTypeUID().equals(THING_TYPE_STATION)) { 152 | ServiceRegistration serviceReg = this.discoveryServiceRegs.get(handler.getThing().getUID()); 153 | if (serviceReg != null) { 154 | serviceReg.unregister(); 155 | discoveryServiceRegs.remove(handler.getThing().getUID()); 156 | bridgeHandler = null; 157 | } 158 | } 159 | super.removeHandler(handler); 160 | } 161 | 162 | @Reference 163 | protected void setDynamicStateDescriptionProvider(SynoDynamicStateDescriptionProvider stateDescriptionProvider) { 164 | this.stateDescriptionProvider = stateDescriptionProvider; 165 | } 166 | 167 | protected void unsetDynamicStateDescriptionProvider(SynoDynamicStateDescriptionProvider stateDescriptionProvider) { 168 | this.stateDescriptionProvider = null; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/SynoWebApiHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi; 14 | 15 | import java.util.HashMap; 16 | 17 | import org.eclipse.jdt.annotation.NonNullByDefault; 18 | import org.eclipse.jetty.client.HttpClient; 19 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 20 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 21 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiAuth; 22 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiCamera; 23 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiCameraEvent; 24 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiEvent; 25 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiExternalEvent; 26 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiExternalRecording; 27 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiHomeMode; 28 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiInfo; 29 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiLiveUri; 30 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiPTZ; 31 | import org.openhab.binding.synologysurveillancestation.internal.webapi.request.SynoApiRequest; 32 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.AuthResponse; 33 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.SimpleResponse; 34 | 35 | /** 36 | * The {@link SynoWebApiHandler} is a facade for Synology Surveillance Station Web API. 37 | * 38 | * @author Nils - Initial contribution 39 | * @author Pavion - Contribution 40 | */ 41 | @NonNullByDefault 42 | public class SynoWebApiHandler implements SynoWebApi { 43 | 44 | private SynoConfig config; 45 | private String sessionID = ""; 46 | 47 | private final HashMap, SynoApiRequest> api = new HashMap<>(); 48 | 49 | /** 50 | * @param config 51 | */ 52 | public SynoWebApiHandler(SynoConfig config, HttpClient httpClient) { 53 | this.config = config; 54 | api.put(SynoApiAuth.class, new SynoApiAuth(config, httpClient)); 55 | api.put(SynoApiInfo.class, new SynoApiInfo(config, httpClient)); 56 | api.put(SynoApiCamera.class, new SynoApiCamera(config, httpClient)); 57 | api.put(SynoApiEvent.class, new SynoApiEvent(config, httpClient)); 58 | api.put(SynoApiHomeMode.class, new SynoApiHomeMode(config, httpClient)); 59 | api.put(SynoApiExternalRecording.class, new SynoApiExternalRecording(config, httpClient)); 60 | api.put(SynoApiPTZ.class, new SynoApiPTZ(config, httpClient)); 61 | api.put(SynoApiLiveUri.class, new SynoApiLiveUri(config, httpClient)); 62 | api.put(SynoApiExternalEvent.class, new SynoApiExternalEvent(config, httpClient)); 63 | api.put(SynoApiCameraEvent.class, new SynoApiCameraEvent(config, httpClient)); 64 | } 65 | 66 | /** 67 | * @return 68 | */ 69 | public SynoConfig getConfig() { 70 | return config; 71 | } 72 | 73 | /** 74 | * @return 75 | */ 76 | public void setConfig(SynoConfig config) { 77 | this.config = config; 78 | for (SynoApiRequest r : api.values()) { 79 | r.setConfig(config); 80 | } 81 | } 82 | 83 | /** 84 | * @return 85 | */ 86 | public void setSessionID(String sessionID) { 87 | this.sessionID = sessionID; 88 | for (SynoApiRequest r : api.values()) { 89 | r.setSessionId(sessionID); 90 | } 91 | } 92 | 93 | /** 94 | * @return 95 | */ 96 | public String getSessionID() { 97 | return sessionID; 98 | } 99 | 100 | /* 101 | * (non-Javadoc) 102 | * 103 | * @see org.openhab.binding.synologysurveillancestation.internal.webapi.SynoWebApi#connect() 104 | */ 105 | @Override 106 | public boolean connect(boolean forceLogout) throws WebApiException { 107 | AuthResponse response = getApiAuth().login(); 108 | if (response.isSuccess()) { 109 | String sid = response.getSid(); 110 | setSessionID(sid); 111 | return true; 112 | } else { 113 | throw new WebApiException(WebApiAuthErrorCodes.getByCode(response.getErrorcode())); 114 | } 115 | } 116 | 117 | /* 118 | * (non-Javadoc) 119 | * 120 | * @see org.eclipse.smarthome.binding.synologysurveillancestation.internal.webapi.SynoWebApi#disconnect() 121 | */ 122 | @Override 123 | public SimpleResponse disconnect() throws WebApiException { 124 | SimpleResponse response = getApiAuth().logout(sessionID); 125 | setSessionID(""); 126 | 127 | if (response.isSuccess()) { 128 | return response; 129 | } else { 130 | throw new WebApiException(WebApiAuthErrorCodes.getByCode(response.getErrorcode())); 131 | } 132 | } 133 | 134 | @Override 135 | public boolean isConnected() { 136 | return (!this.sessionID.isBlank()); 137 | } 138 | 139 | /** 140 | * @return the apiCameraEvent 141 | */ 142 | public SynoApiCameraEvent getApiCameraEvent() { 143 | return getApi(SynoApiCameraEvent.class); 144 | } 145 | 146 | /** 147 | * @return the apiPTZ 148 | */ 149 | public SynoApiPTZ getApiPTZ() { 150 | return getApi(SynoApiPTZ.class); 151 | } 152 | 153 | /** 154 | * @return the apiCamera 155 | */ 156 | public SynoApiCamera getApiCamera() { 157 | return getApi(SynoApiCamera.class); 158 | } 159 | 160 | /** 161 | * @return the apiExternalRecording 162 | */ 163 | public SynoApiExternalRecording getApiExternalRecording() { 164 | return getApi(SynoApiExternalRecording.class); 165 | } 166 | 167 | /** 168 | * @return the apiInfo 169 | */ 170 | public SynoApiInfo getApiInfo() { 171 | return getApi(SynoApiInfo.class); 172 | } 173 | 174 | /** 175 | * @return the apiEvent 176 | */ 177 | public SynoApiEvent getApiEvent() { 178 | return getApi(SynoApiEvent.class); 179 | } 180 | 181 | /** 182 | * @return the apiHomeMode 183 | */ 184 | public SynoApiHomeMode getApiHomeMode() { 185 | return getApi(SynoApiHomeMode.class); 186 | } 187 | 188 | /** 189 | * @return the apiLiveUri 190 | */ 191 | public SynoApiLiveUri getApiLiveUri() { 192 | return getApi(SynoApiLiveUri.class); 193 | } 194 | 195 | /** 196 | * @return the apiExternalEvent 197 | */ 198 | public SynoApiExternalEvent getApiExternalEvent() { 199 | return getApi(SynoApiExternalEvent.class); 200 | } 201 | 202 | /** 203 | * @return the apiLiveUri 204 | */ 205 | public SynoApiAuth getApiAuth() { 206 | return getApi(SynoApiAuth.class); 207 | } 208 | 209 | /** 210 | * Generic getter 211 | * 212 | * @param cl 213 | * @return 214 | */ 215 | @SuppressWarnings("unchecked") 216 | private > T getApi(Class cl) { 217 | return (T) api.get(cl); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.io.IOException; 16 | import java.io.UnsupportedEncodingException; 17 | import java.lang.reflect.Constructor; 18 | import java.lang.reflect.InvocationTargetException; 19 | import java.lang.reflect.ParameterizedType; 20 | import java.net.MalformedURLException; 21 | import java.net.URI; 22 | import java.net.URISyntaxException; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.concurrent.ExecutionException; 26 | import java.util.concurrent.TimeoutException; 27 | 28 | import org.eclipse.jdt.annotation.NonNullByDefault; 29 | import org.eclipse.jetty.client.HttpClient; 30 | import org.eclipse.jetty.client.api.ContentResponse; 31 | import org.eclipse.jetty.client.api.Request; 32 | import org.eclipse.jetty.util.URIUtil; 33 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 34 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 35 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.SynoApiResponse; 36 | import org.slf4j.Logger; 37 | import org.slf4j.LoggerFactory; 38 | 39 | /** 40 | * API request 41 | * 42 | * @param 43 | * 44 | * @author Nils - Initial contribution 45 | * @author Pavion - Contribution 46 | */ 47 | @NonNullByDefault 48 | public abstract class SynoApiRequest implements SynoApi { 49 | private final Logger logger = LoggerFactory.getLogger(SynoApiRequest.class); 50 | 51 | protected static final String API_TRUE = Boolean.TRUE.toString(); 52 | protected static final String API_FALSE = Boolean.FALSE.toString(); 53 | 54 | private final SynoApiConfig apiConfig; 55 | private final HttpClient httpClient; 56 | private SynoConfig config; 57 | private String sessionId = ""; 58 | 59 | final Class typeParameterClass; 60 | 61 | /** 62 | * @param apiConfig 63 | * @param config 64 | * @param sessionId 65 | */ 66 | @SuppressWarnings({ "unchecked", "rawtypes" }) 67 | public SynoApiRequest(SynoApiConfig apiConfig, SynoConfig config, HttpClient httpClient) { 68 | super(); 69 | 70 | this.typeParameterClass = ((Class) ((ParameterizedType) getClass().getGenericSuperclass()) 71 | .getActualTypeArguments()[0]); 72 | 73 | this.httpClient = httpClient; 74 | this.apiConfig = apiConfig; 75 | this.config = config; 76 | } 77 | 78 | /* 79 | * (non-Javadoc) 80 | * 81 | * @see org.openhab.binding.synologysurveillancestation.internal.webapi.SynoApi#getApiConfig() 82 | */ 83 | @Override 84 | public SynoApiConfig getApiConfig() { 85 | return apiConfig; 86 | } 87 | 88 | /** 89 | * @return 90 | */ 91 | protected SynoConfig getConfig() { 92 | return config; 93 | } 94 | 95 | /** 96 | * @return 97 | */ 98 | public void setConfig(SynoConfig config) { 99 | this.config = config; 100 | } 101 | 102 | /** 103 | * 104 | * @param sessionId 105 | */ 106 | public void setSessionId(String sessionId) { 107 | this.sessionId = sessionId; 108 | } 109 | 110 | /** 111 | * @return 112 | */ 113 | protected String getSessionId() { 114 | return sessionId; 115 | } 116 | 117 | /** 118 | * Creates an URIBuilder to build the url. 119 | * 120 | * @return 121 | */ 122 | protected URI getWebApiUrlBuilder() throws URISyntaxException { 123 | StringBuilder sb = URIUtil.newURIBuilder(getConfig().getProtocol(), getConfig().getHost(), 124 | getConfig().getPort()); 125 | URI uri = new URI(sb.toString()); 126 | uri = URIUtil.addPath(uri, apiConfig.getScriptpath()); 127 | return uri; 128 | } 129 | 130 | /** 131 | * Calls the method. 132 | * 133 | * @param method 134 | * @return 135 | * @throws WebApiException 136 | */ 137 | protected T callApi(String method) throws WebApiException { 138 | return callApi(method, new HashMap<>()); 139 | } 140 | 141 | /** 142 | * Calls the method with the passed parameters. 143 | * 144 | * @param method 145 | * @param params 146 | * @return 147 | * @throws WebApiException 148 | */ 149 | protected T callApi(String method, Map params) throws WebApiException { 150 | Request request = getWebApiUrl(method, params); 151 | return callWebApi(request); 152 | } 153 | 154 | /** 155 | * Builds the url for api. 156 | * 157 | * @param method 158 | * @param params 159 | * @return 160 | * @throws MalformedURLException 161 | * @throws URISyntaxException 162 | */ 163 | protected Request getWebApiUrl(String method, Map params) throws WebApiException { 164 | try { 165 | URI uri = getWebApiUrlBuilder(); 166 | 167 | Request request = httpClient.newRequest(uri); 168 | 169 | // API data 170 | request.param("api", apiConfig.getName()); 171 | request.param("version", apiConfig.getVersion()); 172 | 173 | // API method 174 | request.param("method", method); 175 | 176 | // API session 177 | request.param("_sid", getSessionId()); 178 | 179 | if (!params.isEmpty()) { 180 | for (String key : params.keySet()) { 181 | request.param(key, params.get(key)); 182 | } 183 | } 184 | 185 | return request; 186 | 187 | } catch (URISyntaxException | UnsupportedOperationException e) { 188 | throw new WebApiException(e); 189 | } 190 | } 191 | 192 | /** 193 | * E 194 | * 195 | * @param apiurl 196 | * @return 197 | * @throws WebApiException 198 | * @throws URISyntaxException 199 | * @throws UnsupportedOperationException 200 | * @throws IOException 201 | */ 202 | protected synchronized T callWebApi(Request request) throws WebApiException { 203 | try { 204 | if (logger.isDebugEnabled()) { 205 | logger.debug("URI: {}", request.getURI().toString()); 206 | } 207 | ContentResponse response = request.send(); 208 | 209 | if (response.getStatus() == 200) { 210 | byte[] rawResponse = response.getContent(); 211 | String encoding = response.getEncoding().replaceAll("\"", "").trim(); 212 | String result = new String(rawResponse, encoding); 213 | 214 | if (result.length() > 0) { 215 | if (result.contains("\"success\":true")) { 216 | logger.debug("RESPONSE: {}", result); 217 | } else { 218 | logger.error("RESPONSE: {}", result); 219 | } 220 | 221 | } 222 | 223 | Constructor ctor = typeParameterClass.getConstructor(String.class); 224 | 225 | T vo = ctor.newInstance(new Object[] { result }); 226 | 227 | return vo; 228 | 229 | } else { 230 | throw new WebApiException("Error calling Surveillance Station WebApi!"); 231 | } 232 | 233 | } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException 234 | | NoSuchMethodException | SecurityException | ExecutionException | TimeoutException 235 | | InterruptedException e) { 236 | throw new WebApiException(e); 237 | } catch (UnsupportedEncodingException ee) { 238 | throw new WebApiException(ee); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/thread/SynoApiThread.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.thread; 14 | 15 | import java.util.concurrent.ScheduledExecutorService; 16 | import java.util.concurrent.ScheduledFuture; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.concurrent.atomic.AtomicBoolean; 19 | 20 | import org.eclipse.jdt.annotation.NonNullByDefault; 21 | import org.eclipse.jdt.annotation.Nullable; 22 | import org.openhab.binding.synologysurveillancestation.handler.SynoBridgeHandler; 23 | import org.openhab.binding.synologysurveillancestation.handler.SynoCameraHandler; 24 | import org.openhab.binding.synologysurveillancestation.handler.SynoHandler; 25 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 26 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 27 | import org.openhab.core.thing.ThingStatus; 28 | import org.openhab.core.thing.ThingStatusDetail; 29 | import org.openhab.core.thing.binding.BaseThingHandler; 30 | import org.slf4j.Logger; 31 | import org.slf4j.LoggerFactory; 32 | 33 | /** 34 | * The {@link SynoApiThread} is an abstract class for thread management (events, snapshot and so on) 35 | * 36 | * @author Pavion - Initial contribution 37 | */ 38 | @NonNullByDefault 39 | public abstract class SynoApiThread { 40 | private final Logger logger = LoggerFactory.getLogger(SynoApiThread.class); 41 | 42 | /** 43 | * Thread types. Each thread has its refresh rate and polls the Station for events 44 | */ 45 | public static final String THREAD_SNAPSHOT = "Snapshot"; 46 | public static final String THREAD_EVENT = "Event"; 47 | public static final String THREAD_CAMERA = "Camera"; 48 | public static final String THREAD_HOMEMODE = "HomeMode"; 49 | public static final String THREAD_LIVEURI = "LiveUri"; 50 | public static final String THREAD_CAMERAEVENT = "CameraEvent"; 51 | 52 | private final AtomicBoolean refreshInProgress = new AtomicBoolean(false); 53 | private @Nullable ScheduledFuture future; 54 | private int refreshRate; // Refresh rate in seconds 55 | private final T synoHandler; // Bridge or Camera Thing handler 56 | private final String name; // Thread name / type 57 | private final String deviceId; // Thread name / type 58 | 59 | /** 60 | * Defines a runnable for a refresh job 61 | */ 62 | private Runnable runnable = new Runnable() { 63 | @Override 64 | public void run() { 65 | try { 66 | if (refreshInProgress.compareAndSet(false, true)) { 67 | runOnce(); 68 | refreshInProgress.set(false); 69 | } 70 | } catch (IllegalStateException e) { 71 | logger.debug("Thread {}: Refreshing Thing failed, handler might be OFFLINE", name); 72 | } catch (Exception e) { 73 | logger.error("Thread {}: Unknown error", name, e); 74 | } 75 | } 76 | }; 77 | 78 | /** 79 | * Main constructor 80 | * 81 | * @param threadId ID of this thread for logging purposes 82 | * @param refreshRate Refresh rate of this thread in seconds 83 | * @param handler Camera or bridge handler 84 | */ 85 | public SynoApiThread(String name, T synoHandler, int refreshRate) { 86 | this.name = name; 87 | this.synoHandler = synoHandler; 88 | this.refreshRate = refreshRate; 89 | this.deviceId = synoHandler.getThing().getProperties().getOrDefault("deviceID", "Bridge"); 90 | } 91 | 92 | /** 93 | * Starts the refresh job 94 | */ 95 | public void start() { 96 | if (refreshRate > 0) { 97 | ScheduledExecutorService scheduler = synoHandler.getScheduler(); 98 | 99 | if (scheduler != null) { 100 | if (this.name == THREAD_SNAPSHOT) { 101 | future = scheduler.scheduleAtFixedRate(runnable, 0, refreshRate, TimeUnit.SECONDS); 102 | } else { 103 | future = scheduler.scheduleWithFixedDelay(runnable, 0, refreshRate, TimeUnit.SECONDS); 104 | } 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Stops the refresh job 111 | */ 112 | public void stop() { 113 | if (future != null) { 114 | future.cancel(false); 115 | try { 116 | Thread.sleep(1000); 117 | } catch (InterruptedException e) { 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Abstract dummy for a refresh function 124 | */ 125 | public abstract boolean refresh() throws Exception; 126 | 127 | /** 128 | * Run the runnable just once (for manual refresh) 129 | */ 130 | public void runOnce() { 131 | if (isNeeded()) { 132 | logger.debug("Thread {} tick", name); 133 | boolean success = false; 134 | try { 135 | success = refresh(); 136 | } catch (WebApiException e) { 137 | if (e.getCause() instanceof java.util.concurrent.TimeoutException) { 138 | logger.debug( 139 | "DeviceId: {}; {} API timeout, consider to increase refresh rate ({} s) if seen frequently", 140 | deviceId, name, refreshRate); 141 | success = true; 142 | } else if (e.getErrorCode() == WebApiAuthErrorCodes.INSUFFICIENT_USER_PRIVILEGE.getCode() 143 | || e.getErrorCode() == WebApiAuthErrorCodes.UNKNOWN_ERROR_119.getCode()) { 144 | logger.debug("DeviceId: {}; Thread: {}; SID expired, trying to reconnect", deviceId, name); 145 | try { 146 | getSynoHandler().reconnect(true); 147 | } catch (WebApiException ee) { 148 | logger.error("DeviceId: {}; Thread: {}; Attempt to reconnect failed", deviceId, name); 149 | } 150 | } else { 151 | logger.error( 152 | "DeviceId: {}; Thread: {}; Handler gone offline (Surveillance Station probably disabled)", 153 | deviceId, name); 154 | } 155 | } catch (Exception e) { 156 | logger.error("DeviceId: {}; Thread: {}; Critical error:\n", deviceId, name, e); 157 | } 158 | 159 | updateStatus(success); 160 | } 161 | } 162 | 163 | /** 164 | * Update handler status on runnable feedback 165 | * 166 | * @param success if runnable was successful 167 | */ 168 | private void updateStatus(boolean success) { 169 | if (success && !synoHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) { 170 | if (synoHandler instanceof SynoCameraHandler) { 171 | ((SynoCameraHandler) synoHandler).updateStatus(ThingStatus.ONLINE); 172 | } else if (synoHandler instanceof SynoBridgeHandler) { 173 | ((SynoBridgeHandler) synoHandler).updateStatus(ThingStatus.ONLINE); 174 | } 175 | } else if (!success && synoHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) { 176 | if (synoHandler instanceof SynoCameraHandler) { 177 | ((SynoCameraHandler) synoHandler).updateStatus(ThingStatus.OFFLINE, 178 | ThingStatusDetail.COMMUNICATION_ERROR, "Thread " + name); 179 | } else if (synoHandler instanceof SynoBridgeHandler) { 180 | ((SynoBridgeHandler) synoHandler).updateStatus(ThingStatus.OFFLINE, 181 | ThingStatusDetail.COMMUNICATION_ERROR, "Thread " + name); 182 | } 183 | } 184 | } 185 | 186 | /** 187 | * @return the refreshRate 188 | */ 189 | public int getRefreshRate() { 190 | return refreshRate; 191 | } 192 | 193 | /** 194 | * @param refreshRate The refreshRate to be set 195 | */ 196 | public void setRefreshRate(int refreshRate) { 197 | if (this.refreshRate != refreshRate) { 198 | this.refreshRate = refreshRate; 199 | stop(); 200 | start(); 201 | } 202 | } 203 | 204 | /** 205 | * @return the SynoCameraHandler 206 | */ 207 | public T getSynoHandler() { 208 | return synoHandler; 209 | } 210 | 211 | /** 212 | * @return if thread has to be run 213 | */ 214 | public abstract boolean isNeeded(); 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiCamera.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import java.io.IOException; 16 | import java.net.URISyntaxException; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.concurrent.ExecutionException; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.concurrent.TimeoutException; 22 | 23 | import org.eclipse.jdt.annotation.NonNullByDefault; 24 | import org.eclipse.jetty.client.HttpClient; 25 | import org.eclipse.jetty.client.api.ContentResponse; 26 | import org.eclipse.jetty.client.api.Request; 27 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 28 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 29 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 30 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.CameraResponse; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | 34 | /** 35 | * SYNO.SurveillanceStation.Camera 36 | * 37 | * This API provides a set of methods to acquire camera-related information and to enable/disable cameras. 38 | * 39 | * Method: 40 | * - Save 41 | * - List 42 | * - GetInfo 43 | * - ListGroup 44 | * - GetSnapshot 45 | * - Enable 46 | * - Disable 47 | * - GetCapabilityByCamId 48 | * - MigrationEnum 49 | * - Migrate 50 | * - CountByCategory 51 | * - RecountEventSize 52 | * - SaveOptimizeParam 53 | * - GetOccupiedSize 54 | * - CheckCamValid 55 | * - MigrationCancel 56 | * - Delete 57 | * - GetLiveViewPath 58 | * 59 | * @author Nils - Initial contribution 60 | * @author Pavion - Contribution 61 | * 62 | */ 63 | @NonNullByDefault 64 | public class SynoApiCamera extends SynoApiRequest { 65 | private final Logger logger = LoggerFactory.getLogger(SynoApiCamera.class); 66 | 67 | // API configuration 68 | private static final String API_NAME = "SYNO.SurveillanceStation.Camera"; 69 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_08, API_SCRIPT_ENTRY); 70 | 71 | /** 72 | * @param config 73 | */ 74 | public SynoApiCamera(SynoConfig config, HttpClient httpClient) { 75 | super(API_CONFIG, config, httpClient); 76 | } 77 | 78 | /** 79 | * Calls the passed method for all cameras. 80 | * 81 | * @param method 82 | * @return 83 | * @throws WebApiException 84 | */ 85 | private CameraResponse call(String method) throws WebApiException { 86 | return call(method, ""); 87 | } 88 | 89 | /** 90 | * Calls the passed method. 91 | * 92 | * @param method 93 | * @param cameraId 94 | * @return 95 | * @throws WebApiException 96 | */ 97 | private CameraResponse call(String method, String cameraId) throws WebApiException { 98 | Map params = new HashMap<>(); 99 | 100 | // API parameters 101 | params.put("blFromCamList", API_TRUE); 102 | params.put("privCamType", API_TRUE); 103 | params.put("blIncludeDeletedCam", API_FALSE); 104 | params.put("basic", API_TRUE); 105 | params.put("streamInfo", API_TRUE); 106 | params.put("blPrivilege", API_FALSE); 107 | params.put("cameraIds", cameraId); 108 | 109 | return callApi(method, params); 110 | } 111 | 112 | /** 113 | * Get the up-to-date snapshot of the selected camera in JPEG format. 114 | * 115 | * @throws WebApiException 116 | * @throws IOException 117 | * @throws UnsupportedOperationException 118 | * @throws URISyntaxException 119 | * 120 | */ 121 | public byte[] getSnapshot(String cameraId, int timeout, int streamId) 122 | throws IOException, URISyntaxException, WebApiException { 123 | try { 124 | Map params = new HashMap<>(); 125 | 126 | // API parameters 127 | params.put("cameraId", cameraId); 128 | params.put("camStm", String.valueOf(streamId)); 129 | 130 | Request request = getWebApiUrl(METHOD_GETSNAPSHOT, params); 131 | 132 | long responseTime = System.currentTimeMillis(); 133 | 134 | ContentResponse response = request.timeout(timeout, TimeUnit.SECONDS).send(); 135 | 136 | responseTime = System.currentTimeMillis() - responseTime; 137 | byte[] ret = new byte[0]; 138 | if (response.getStatus() == 200) { 139 | ret = response.getContent(); 140 | if (ret.length < 200) { 141 | String error = new String(ret); 142 | if (error.contains("\"success\":false")) { 143 | if (error.contains("{\"code\":400}")) { 144 | logger.debug("Device: {}, API response time: {} ms, execution failed", cameraId, 145 | responseTime); 146 | return new byte[0]; 147 | } else if (error.contains("{\"code\":401}")) { 148 | logger.debug("Device: {}, API response time: {} ms, parameter invalid", cameraId, 149 | responseTime); 150 | return new byte[0]; 151 | } else if (error.contains("{\"code\":402}")) { 152 | logger.trace("Device: {}, API response time: {} ms, camera disabled", cameraId, 153 | responseTime); 154 | return new byte[2]; 155 | } else if (error.contains("{\"code\":407}")) { 156 | logger.debug("Device: {}, API response time: {} ms, CMS closed", cameraId, responseTime); 157 | return new byte[0]; 158 | } else { 159 | logger.trace("Device: {}, API response time: {} ms, unexpected response: {}", cameraId, 160 | responseTime, error); 161 | throw new WebApiException(WebApiAuthErrorCodes.INSUFFICIENT_USER_PRIVILEGE); 162 | } 163 | } 164 | } 165 | } 166 | logger.trace("Device: {}, API response time: {} ms, stream id: {}", cameraId, responseTime, streamId); 167 | return ret; 168 | } catch (IllegalArgumentException | SecurityException | ExecutionException | TimeoutException 169 | | InterruptedException e) { 170 | throw new WebApiException(e); 171 | } 172 | } 173 | 174 | /** 175 | * Get snapshot URI of the selected camera 176 | * 177 | * @throws WebApiException 178 | * @throws IOException 179 | * @throws UnsupportedOperationException 180 | * @throws URISyntaxException 181 | * 182 | */ 183 | public String getSnapshotUri(String cameraId, int streamId) throws WebApiException { 184 | try { 185 | Map params = new HashMap<>(); 186 | 187 | // API parameters 188 | params.put("cameraId", cameraId); 189 | params.put("camStm", String.valueOf(streamId)); 190 | 191 | Request request = getWebApiUrl(METHOD_GETSNAPSHOT, params); 192 | return request.getURI().toString(); 193 | } catch (Exception e) { 194 | throw new WebApiException(e); 195 | } 196 | } 197 | 198 | /** 199 | * Get the list of all cameras. 200 | * 201 | * @return 202 | * @throws WebApiException 203 | */ 204 | public CameraResponse listCameras() throws WebApiException { 205 | CameraResponse response = call(METHOD_LIST); 206 | 207 | if (!response.isSuccess()) { 208 | throw new WebApiException(WebApiAuthErrorCodes.getByCode(response.getErrorcode())); 209 | } 210 | 211 | return response; 212 | } 213 | 214 | /** 215 | * Get specific camera settings. 216 | * 217 | * @return 218 | * @throws WebApiException 219 | */ 220 | public CameraResponse getInfo(String cameraId) throws WebApiException { 221 | CameraResponse response = call(METHOD_GETINFO, cameraId); 222 | 223 | if (!response.isSuccess()) { 224 | throw new WebApiException(WebApiAuthErrorCodes.getByCode(response.getErrorcode())); 225 | } 226 | 227 | return response; 228 | } 229 | 230 | /** 231 | * Toggle camera. 232 | * 233 | * @param cameraId 234 | * @return 235 | * @throws WebApiException 236 | */ 237 | public CameraResponse toggleCamera(String cameraId, boolean on) throws WebApiException { 238 | Map params = new HashMap<>(); 239 | params.put("cameraIds", cameraId); 240 | 241 | return callApi(on ? METHOD_ENABLE : METHOD_DISABLE, params); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/main/resources/OH-INF/thing/camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Synology Surveillance Station Camera 13 | Camera 14 | 15 | 16 | 17 | 18 | 19 | 20 | Events of your camera 21 | 22 | 23 | 24 | Motion detection parameters 25 | 26 | 27 | 28 | 29 | 30 | 31 | Common settings 32 | false 33 | 34 | 35 | 36 | Refresh rate settings 37 | true 38 | 39 | 40 | 41 | 42 | Refresh rate for camera snapshot in seconds (0 to disable) 43 | 10 44 | true 45 | 46 | 47 | 48 | Refresh rate for events in seconds (0 to disable) 49 | 3 50 | true 51 | 52 | 53 | 54 | Refresh rate for motion detection parameter in seconds (0 to disable) 55 | 0 56 | true 57 | 58 | 59 | 60 | Snapshot video stream ID according to Surveillance Station configuration (default: 1 for 'Stream 1') 61 | 1 62 | true 63 | 64 | 65 | 66 | 67 | 68 | 69 | Common channels of your camera 70 | 71 | 72 | 73 | 74 | 75 | 76 | Dynamic URL of the current snapshot (events refresh rate) 77 | 78 | 79 | 80 | Static URL of the current snapshot 81 | 82 | 83 | 84 | Live feed URI (rtsp) 85 | 86 | 87 | 88 | Live feed URI (mjpeg over http) 89 | 90 | 91 | 92 | 93 | 94 | 95 | Pan/Tilt/Zoom channels of your camera 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | Event of your camera 107 | 108 | 109 | 110 | Last motion event 111 | 112 | 113 | 114 | Last alarm event 115 | 116 | 117 | 118 | Last manual event 119 | 120 | 121 | 122 | Last external event 123 | 124 | 125 | 126 | Last continuous event 127 | 128 | 129 | 130 | Last action rule event 131 | 132 | 133 | 134 | 135 | 136 | 137 | Motion detection parameters 138 | 139 | 140 | 141 | Motion detection source. -1:disable, 0:by camera, 1:by Surveillance Station 142 | 143 | 144 | 145 | Motion detection sensitivity (1 to 99) 146 | 147 | 148 | 149 | Motion detection threshold (1 to 99) 150 | 151 | 152 | 153 | Motion detection object size (1 to 99) 154 | 155 | 156 | 157 | Motion detection percentage (1 to 99) 158 | 159 | 160 | 161 | Ignore short-lived motion for duration of (0 to 10) seconds 162 | 163 | 164 | 165 | 166 | 167 | Image 168 | 169 | Current snapshot of your camera 170 | 171 | 172 | Switch 173 | 174 | Enable or disable your camera 175 | 176 | 177 | Switch 178 | 179 | Start manual recording 180 | 181 | 182 | String 183 | 184 | Current snapshot/live URI of your camera 185 | 186 | 187 | 188 | String 189 | 190 | Zooming channel for your camera 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | String 200 | 201 | Moving channel for your camera 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | String 215 | 216 | Move the camera lens to a pre-defined preset position. 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | String 226 | 227 | Force the camera to execute the specific patrol. 228 | 229 | 230 | 231 | Switch 232 | 233 | Event was detected 234 | 235 | 236 | 237 | Switch 238 | 239 | Event was detected 240 | 241 | 242 | 243 | String 244 | 245 | Motion detection source. -1:disable, 0:by camera, 1:by Surveillance Station 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | Number 256 | 257 | Motion detection parameter 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/handler/SynoBridgeHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.handler; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.*; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.Map.Entry; 20 | import java.util.concurrent.ScheduledExecutorService; 21 | import java.util.concurrent.atomic.AtomicBoolean; 22 | 23 | import org.eclipse.jdt.annotation.NonNullByDefault; 24 | import org.eclipse.jdt.annotation.Nullable; 25 | import org.eclipse.jetty.client.HttpClient; 26 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 27 | import org.openhab.binding.synologysurveillancestation.internal.discovery.CameraDiscoveryService; 28 | import org.openhab.binding.synologysurveillancestation.internal.thread.SynoApiThread; 29 | import org.openhab.binding.synologysurveillancestation.internal.thread.SynoApiThreadHomeMode; 30 | import org.openhab.binding.synologysurveillancestation.internal.webapi.SynoWebApiHandler; 31 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 32 | import org.openhab.core.config.core.Configuration; 33 | import org.openhab.core.library.types.DecimalType; 34 | import org.openhab.core.library.types.StringType; 35 | import org.openhab.core.thing.Bridge; 36 | import org.openhab.core.thing.ChannelUID; 37 | import org.openhab.core.thing.ThingStatus; 38 | import org.openhab.core.thing.ThingStatusDetail; 39 | import org.openhab.core.thing.binding.BaseBridgeHandler; 40 | import org.openhab.core.types.Command; 41 | import org.openhab.core.types.RefreshType; 42 | import org.openhab.core.types.State; 43 | import org.openhab.core.types.UnDefType; 44 | import org.slf4j.Logger; 45 | import org.slf4j.LoggerFactory; 46 | 47 | /** 48 | * The {@link SynoBridgeHandler} is a Bridge handler for the Synology Surveillance Station 49 | * 50 | * @author Nils - Initial contribution 51 | * @author Pavion - Contribution 52 | */ 53 | @NonNullByDefault 54 | public class SynoBridgeHandler extends BaseBridgeHandler implements SynoHandler { 55 | 56 | private final Logger logger = LoggerFactory.getLogger(SynoBridgeHandler.class); 57 | private @Nullable CameraDiscoveryService discoveryService; 58 | private final SynoWebApiHandler apiHandler; 59 | private final Map> threads = new HashMap<>(); 60 | private final AtomicBoolean refreshInProgress = new AtomicBoolean(false); 61 | private SynoConfig config = new SynoConfig(); 62 | 63 | /** 64 | * Defines a runnable for a discovery 65 | */ 66 | Runnable runnable = new Runnable() { 67 | @Override 68 | public void run() { 69 | if (discoveryService != null) { 70 | discoveryService.discoverCameras(); 71 | } 72 | } 73 | }; 74 | 75 | public SynoBridgeHandler(Bridge bridge, HttpClient httpClient) { 76 | super(bridge); 77 | config = getConfigAs(SynoConfig.class); 78 | 79 | apiHandler = new SynoWebApiHandler(config, httpClient); 80 | threads.put(SynoApiThread.THREAD_HOMEMODE, new SynoApiThreadHomeMode(this, config.getRefreshRateEvents())); 81 | try { 82 | reconnect(false); 83 | } catch (WebApiException e) { 84 | } 85 | } 86 | 87 | @Override 88 | @Nullable 89 | public SynoWebApiHandler getSynoWebApiHandler() { 90 | return apiHandler; 91 | } 92 | 93 | @Override 94 | public void handleCommand(ChannelUID channelUID, Command command) { 95 | try { 96 | switch (channelUID.getId()) { 97 | case CHANNEL_HOMEMODE: 98 | if (command.toString().equals("REFRESH")) { 99 | threads.get(SynoApiThread.THREAD_HOMEMODE).runOnce(); 100 | } else { 101 | boolean state = command.toString().equals("ON"); 102 | apiHandler.getApiHomeMode().setHomeMode(state); 103 | } 104 | break; 105 | case CHANNEL_EVENT_TRIGGER: 106 | if (command.toString().equals("REFRESH")) { 107 | updateState(channelUID, UnDefType.UNDEF); 108 | } else { 109 | int event = Integer.parseInt(command.toString()); 110 | boolean ret = false; 111 | if (event >= 1 && event <= 10) { 112 | ret = apiHandler.getApiExternalEvent().triggerEvent(event); 113 | } 114 | updateState(channelUID, ret ? new DecimalType(0) : UnDefType.UNDEF); 115 | } 116 | break; 117 | case CHANNEL_SID: 118 | if (command.toString().equals("REFRESH")) { 119 | updateState(channelUID, new StringType(apiHandler.getSessionID())); 120 | } 121 | break; 122 | } 123 | } catch (Exception e) { 124 | logger.error("handle command: {}::{}", getThing().getLabel(), getThing().getUID()); 125 | } 126 | } 127 | 128 | public void setDiscovery(CameraDiscoveryService discoveryService) { 129 | this.discoveryService = discoveryService; 130 | } 131 | 132 | @Override 133 | public boolean reconnect(boolean forceLogout) throws WebApiException { 134 | if (refreshInProgress.compareAndSet(false, true)) { 135 | boolean ret = false; 136 | try { 137 | ret = apiHandler.connect(forceLogout); 138 | try { 139 | Thread.sleep(1000); 140 | } catch (InterruptedException e) { 141 | } 142 | refreshInProgress.set(false); 143 | } catch (WebApiException e) { 144 | refreshInProgress.set(false); 145 | throw e; 146 | } 147 | if (ret && isInitialized()) { 148 | handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SID), RefreshType.REFRESH); 149 | } 150 | return ret; 151 | } else { 152 | logger.debug("Reconnect already in progress..."); 153 | return false; 154 | } 155 | } 156 | 157 | @Override 158 | public void initialize() { 159 | try { 160 | if (logger.isDebugEnabled()) { 161 | logger.debug("Initialize thing: {}::{}", getThing().getLabel(), getThing().getUID()); 162 | } 163 | 164 | if (!getConfigAs(SynoConfig.class).equals(config)) { 165 | config = getConfigAs(SynoConfig.class); 166 | apiHandler.setConfig(config); 167 | reconnect(false); 168 | } 169 | 170 | // if needed add other infos 171 | // InfoResponse infoResponse = apiHandler.getInfo(); 172 | // getThing().setProperty(SynoApiResponse.PROP_CAMERANUMBER, 173 | // infoResponse.getData().get(SynoApiResponse.PROP_CAMERANUMBER).getAsString()); 174 | 175 | for (SynoApiThread thread : threads.values()) { 176 | thread.start(); 177 | } 178 | 179 | updateStatus(ThingStatus.ONLINE); 180 | handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SID), RefreshType.REFRESH); 181 | 182 | // Trigger discovery of cameras 183 | scheduler.submit(runnable); 184 | 185 | } catch (WebApiException e) { 186 | if (e.getCause() instanceof java.util.concurrent.TimeoutException) { 187 | updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection timeout"); 188 | } else if (e.getErrorCode() == 400) { 189 | updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, 190 | "Please add or check your credentials"); 191 | } else { 192 | updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, 193 | "Errorcode: " + e.getErrorCode()); 194 | } 195 | } 196 | } 197 | 198 | @Override 199 | public void dispose() { 200 | for (SynoApiThread thread : threads.values()) { 201 | thread.stop(); 202 | } 203 | try { 204 | apiHandler.disconnect(); 205 | } catch (WebApiException e) { 206 | logger.error("Error disconnecting: ", e); 207 | } 208 | } 209 | 210 | @Override 211 | public void handleConfigurationUpdate(Map configurationParameters) { 212 | Configuration configuration = editConfiguration(); 213 | 214 | for (Entry configurationParameter : configurationParameters.entrySet()) { 215 | configuration.put(configurationParameter.getKey(), configurationParameter.getValue()); 216 | } 217 | SynoConfig oldConfig = thing.getConfiguration().as(SynoConfig.class); 218 | SynoConfig newConfig = configuration.as(SynoConfig.class); 219 | 220 | if (!oldConfig.equals(newConfig)) { 221 | if (oldConfig.equalsButForRefresh(newConfig)) { 222 | updateConfiguration(configuration); 223 | threads.get(SynoApiThread.THREAD_HOMEMODE).setRefreshRate(newConfig.getRefreshRateEvents()); 224 | } else { 225 | super.handleConfigurationUpdate(configurationParameters); 226 | } 227 | } 228 | } 229 | 230 | @Override 231 | public boolean isLinked(String channelId) { 232 | return super.isLinked(channelId); 233 | } 234 | 235 | @Override 236 | public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) { 237 | super.updateStatus(status, statusDetail, description); 238 | } 239 | 240 | @Override 241 | public void updateStatus(ThingStatus status) { 242 | super.updateStatus(status); 243 | } 244 | 245 | @Override 246 | public void updateState(ChannelUID channelUID, State state) { 247 | super.updateState(channelUID, state); 248 | } 249 | 250 | /** 251 | * @return service scheduler of this Thing 252 | */ 253 | @Override 254 | public ScheduledExecutorService getScheduler() { 255 | return scheduler; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Synology Surveillance Station Binding 2 | 3 | This binding connects openHAB with your surveillance cameras running on Synology© DiskStation using Synology Surveillance Station API. This binding should work with any DiskStation capable of running Surveillance Station as well as with any supported camera. 4 | 5 | # Table of contents 6 | 7 | - [Synology Surveillance Station Binding](#synology-surveillance-station-binding) 8 | - [Table of contents](#table-of-contents) 9 | - [Disclaimer](#disclaimer) 10 | - [Installation and upgrade](#installation-and-upgrade) 11 | - [Supported Things](#supported-things) 12 | - [Discovery](#discovery) 13 | - [Configuration](#configuration) 14 | - [Channels](#channels) 15 | - [File based configuration](#file-based-configuration) 16 | - [.things](#things) 17 | - [.items](#items) 18 | - [.sitemap](#sitemap) 19 | - [Transformation](#transformation) 20 | - [.items](#items-1) 21 | - [transform/liveuri.js](#transformliveurijs) 22 | - [Support](#support) 23 | *** 24 | 25 | ## Disclaimer 26 | 27 | This binding is currently under development. Your help and testing would be greatly appreciated but there is no stability or functionality warranty. 28 | 29 | ## Installation and upgrade 30 | 31 | For an installation the [latest release](https://github.com/nibi79/synologysurveillancestation/releases) should be copied into the /addons folder of your openHAB installation. 32 | For an upgrade the existing file should be overwritten. On major or structural changes existing things might have to be deleted and recreated, existing channels might be kept. For further information please read release notes of a corresponding release. 33 | 34 | ## Supported Things 35 | 36 | Currently following Things are supported: 37 | 38 | - **Bridge** Thing representing the Synology DiskStation / Surveillance Station 39 | - One or many Things for supported **Cameras** 40 | 41 | ## Discovery 42 | 43 | If your openHAB installation is in the same network as your Synology DiskStation, the discovery will automatically find your Surveillance Station. You can now add its **Bridge** Thing, which will first be _OFFLINE_. You should now configure the **Bridge** and enter your Surveillance Station credentials. For security reasons it's always a better practice to create a separate user for this. After entering correct credentials and updating the **Bridge**, automatic discovery for **cameras** will start. If successful, your **cameras** will be found and can be added without further configuration. 44 | 45 | ## Configuration 46 | 47 | Following options can be set for the **Bridge**: 48 | 49 | - Access protocol of the DiskStation 50 | - Host/IP of the DiskStation 51 | - Port of the DiskStation 52 | - User name for the DiskStation / Surveillance Station 53 | - Password for the DiskStation / Surveillance Station 54 | - Refresh rate for DiskStation events (Home Mode) 55 | - (**advanced**) Enable support for self-signed / invalid SSL certificates (binding or openHAB restart required on change) 56 | 57 | Following options can be set for the **Camera**: 58 | 59 | - Snapshot refresh rate 60 | - Refresh rate for all other **Camera** events and dynamic channels 61 | - Refresh rate for motion detection parameter (defaults to 0 = no autorefresh) 62 | 63 | ## Channels 64 | 65 | Currently following **Channels** are supported on the **Bridge**: 66 | 67 | - Home mode _SWITCH_ 68 | - External event trigger _NUMBER_ (1 to 10, write-only) 69 | - Current session ID (SID) _STRING_ 70 | 71 | Currently following **Channels** are supported on the **Camera**: 72 | 73 | - Common channels: 74 | - Snapshot _IMAGE_ 75 | - Camera recording _SWITCH_ 76 | - Enable camera _SWITCH_ 77 | - URIs: 78 | - Snapshot static URI _STRING_ 79 | - Snapshot dynamic URI (refreshes with event refresh rate) _STRING_ 80 | - Snapshot static live feed URI (rtsp) _STRING_ 81 | - Snapshot static live feed URI (mjpeg over http) _STRING_ 82 | - PTZ (Pan/Tilt/Zoom) for PTZ cameras only: 83 | - Zoom `IN`/`OUT` 84 | - Move `UP`/`DOWN`/`LEFT`/`RIGHT`/`HOME` 85 | - Continuous Move/Zoom with `START_` and `STOP_` (e.g. `START_IN`) 86 | - Move to preset 87 | - Run patrol 88 | - Event channels: 89 | - Motion event _SWITCH_ (read-only) 90 | - Alarm event _SWITCH_ (read-only) 91 | - Manual event _SWITCH_ (read-only) 92 | - Continuous recording event _SWITCH_ (read-only) 93 | - External event _SWITCH_ (read-only) 94 | - Action rule event _SWITCH_ (read-only) 95 | - Motion detection channels (if available): 96 | - Motion detection source _STRING_ (-1:disable, 0:by camera, 1:by Surveillance Station) 97 | - Motion detection sensitivity _NUMBER_ (1 to 99) 98 | - Motion detection threshold _NUMBER_ (1 to 99) 99 | - Motion detection object size _NUMBER_ (1 to 99) 100 | - Motion detection percentage _NUMBER_ (1 to 99) 101 | - Ignore short-lived motion for _NUMBER_ (0 to 10) seconds 102 | 103 | ## File based configuration 104 | 105 | ### .things 106 | 107 | ``` 108 | Bridge synologysurveillancestation:station:diskstation "DiskStation" @ "ServerRoom" [ protocol="http", host="192.168.0.1", port="5000", username="my username", password="my password", acceptSsl="false" ] { 109 | Thing camera CameraID "Camera 1" @ "Outside" [ refreshRateEvents=5, refreshRateSnapshot=10, refreshRateMdParam=120, snapshotStreamId=1 ] 110 | } 111 | ``` 112 | 113 | or for a self-signed SSL certificate: 114 | 115 | ``` 116 | Bridge synologysurveillancestation:station:diskstation "DiskStation" @ "ServerRoom" [ protocol="https", host="192.168.0.1", port="5001", username="my username", password="my password", acceptSsl="true" ] { 117 | Thing camera CameraID "Camera 1" @ "Outside" [ refreshRateEvents=5, refreshRateSnapshot=10, refreshRateMdParam=120, snapshotStreamId=1 ] 118 | } 119 | ``` 120 | 121 | Here the **CameraID** is a numeric ID of your surveillance camera in Surveillance Station (e.g. 1) and snapshot stream ID is the ID of the preferred stream in Surveillance Station (e.g. 1 for 'Stream 1') 122 | 123 | ### .items 124 | 125 | ``` 126 | Switch Surveillance_HomeMode "Home Mode" {channel="synologysurveillancestation:station:diskstation:homemode"} 127 | Number:Dimensionless Surveillance_Event_Trigger "External event trigger" {channel="synologysurveillancestation:station:diskstation:eventtrigger"} 128 | String Surveillance_SID "Current SID" {channel="synologysurveillancestation:station:diskstation:sid"} 129 | 130 | Image Surveillance_Snapshot "Snapshot" {channel="synologysurveillancestation:camera:diskstation:1:common#snapshot"} 131 | 132 | String Surveillance_Snapshot_Uri_Dynamic "Dynamic snapshot URI" {channel="synologysurveillancestation:camera:diskstation:1:common#snapshot-uri-dynamic"} 133 | String Surveillance_Snapshot_Uri_Static "Static snapshot URI" {channel="synologysurveillancestation:camera:diskstation:1:common#snapshot-uri-static"} 134 | String Surveillance_Snapshot_Live_Uri_Rtsp "Live feed URI (rtsp)" {channel="synologysurveillancestation:camera:diskstation:1:common#live-uri-rtsp"} 135 | String Surveillance_Snapshot_Live_Uri_Mjpeg_Http "Live feed URI (mjpeg over http)" {channel="synologysurveillancestation:camera:diskstation:1:common#live-uri-mjpeg-http"} 136 | 137 | Switch Surveillance_Recording "Camera recording" {channel="synologysurveillancestation:camera:diskstation:1:common#record"} 138 | Switch Surveillance_Enabled "Camera enabled" {channel="synologysurveillancestation:camera:diskstation:1:common#enable"} 139 | 140 | Switch Surveillance_Event_Motion "Camera motion event" {channel="synologysurveillancestation:camera:diskstation:1:event#motion"} 141 | Switch Surveillance_Event_Alarm "Camera alarm event" {channel="synologysurveillancestation:camera:diskstation:1:event#alarm"} 142 | Switch Surveillance_Event_Manual "Camera manual event" {channel="synologysurveillancestation:camera:diskstation:1:event#manual"} 143 | Switch Surveillance_Event_Continuous "Camera continuous recording event" {channel="synologysurveillancestation:camera:diskstation:1:event#continuous"} 144 | Switch Surveillance_Event_External "Camera external event" {channel="synologysurveillancestation:camera:diskstation:1:event#external"} 145 | Switch Surveillance_Event_ActionRule "Camera action rule event" {channel="synologysurveillancestation:camera:diskstation:1:event#actionrule"} 146 | 147 | String Surveillance_Zooming "Camera zooming" {channel="synologysurveillancestation:camera:diskstation:1:ptz#zoom"} 148 | String Surveillance_Moving "Camera moving" {channel="synologysurveillancestation:camera:diskstation:1:ptz#move"} 149 | String Surveillance_Presets "Camera moving to preset" {channel="synologysurveillancestation:camera:diskstation:1:ptz#movepreset"} 150 | String Surveillance_Patrols "Camera run patrol" {channel="synologysurveillancestation:camera:diskstation:1:ptz#runpatrol"} 151 | 152 | String Surveillance_MD_Source "Motion detection source" {channel="synologysurveillancestation:camera:diskstation:1:md-param#md-param-source"} 153 | Number:Dimensionless Surveillance_MD_Sensitivity "Motion detection sensitivity" {channel="synologysurveillancestation:camera:diskstation:1:md-param#md-param-sensitivity"} 154 | Number:Dimensionless Surveillance_MD_Threshold "Motion detection threshold" {channel="synologysurveillancestation:camera:diskstation:1:md-param#md-param-threshold"} 155 | Number:Dimensionless Surveillance_MD_Objectsize "Motion detection objectsize" {channel="synologysurveillancestation:camera:diskstation:1:md-param#md-param-objectsize"} 156 | Number:Dimensionless Surveillance_MD_Percentage "Motion detection percentage" {channel="synologysurveillancestation:camera:diskstation:1:md-param#md-param-percentage"} 157 | Number:Dimensionless Surveillance_MD_Shortlive "Ignore short-lived motion" {channel="synologysurveillancestation:camera:diskstation:1:md-param#md-param-shortlive"} 158 | ``` 159 | 160 | Here `:1` is yet again the numeric ID of your surveillance camera from a previous step. 161 | 162 | ### .sitemap 163 | 164 | ``` 165 | Switch item=Surveillance_Zooming mappings=[IN="IN", OUT="OUT"] 166 | 167 | // Some cameras like Reolink do not support simple stepping 168 | Switch item=Surveillance_ContinuousZoomingIn mappings=[START_IN="Start ZoomIn", STOP_IN="Stop ZoomIn"] 169 | Switch item=Surveillance_ContinuousZoomingOut mappings=[START_OUT="Start ZoomOut", STOP_OUT="Stop ZoomOut"] 170 | 171 | Switch item=Surveillance_Moving mappings=[UP="UP", DOWN="DOWN", LEFT="LEFT", RIGHT="RIGHT"] 172 | 173 | Selection item=Surveillance_Presets label="Surveillance_Presets Selection" 174 | Switch item=Surveillance_Presets label="Surveillance_Presets Mapping" mappings=[preset1="Preset 1",preset2="Preset 2"] 175 | 176 | Image item=Surveillance_Snapshot_Uri_Static url="[%s]" refresh=5000 177 | Video item=Surveillance_Snapshot_Live_Uri_Mjpeg_Http url="[%s]" encoding="mjpeg" 178 | ``` 179 | 180 | ## Transformation 181 | 182 | Existing URIs can also be transformed using JS transformation to build similar URIs. Most requests can be extended or constructed manually using SID (session ID) for authentication by adding `&_sid=your-current-SID` to the query string. Please refer to [Synology Surveillance Station API documentation](https://global.download.synology.com/download/Document/DeveloperGuide/Surveillance_Station_Web_API_v2.8.pdf) for more details. 183 | 184 | Example: SID-based stream URI (over http). 185 | 186 | ### .items 187 | 188 | ``` 189 | String Surveillance_Snapshot_Live_Uri_Static "SID-based URI" {channel="synologysurveillancestation:camera:diskstation:1:common#snapshot-uri-static"[profile="transform:JS", function="liveuri.js"]} 190 | ``` 191 | 192 | ### transform/liveuri.js 193 | 194 | ``` 195 | (function(i) { 196 | return i.replace("entry.cgi", "SurveillanceStation/videoStreaming.cgi").replace(".Camera", ".VideoStream").replace("version=8", "version=1").replace("GetSnapshot", "Stream&format=mjpeg") 197 | })(input) 198 | ``` 199 | 200 | Please note, **[Javascript Transformation](https://www.openhab.org/addons/transformations/javascript/)** add-on must be installed for the transformation to work properly. 201 | 202 | ## Support 203 | 204 | If you encounter critical issues with this binding, please consider to: 205 | 206 | - create an [issue](https://github.com/nibi79/synologysurveillancestation/issues) on GitHub 207 | - search [community forum](https://community.openhab.org/t/binding-request-synology-surveillance-station/8200) for answers already given 208 | - or make a new post there, if nothing was found 209 | 210 | In any case please provide some information about your problem: 211 | 212 | - openHAB and binding version 213 | - error description and steps to retrace if applicable 214 | - any related `[WARN]`/`[ERROR]` from openhab.log 215 | - whether it's the binding, bridge, camera or channel related issue 216 | 217 | For the sake of documentation please use English language. 218 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/binding/synologysurveillancestation/internal/webapi/request/SynoApiPTZ.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010-2024 Contributors to the openHAB project 3 | * 4 | * See the NOTICE file(s) distributed with this work for additional 5 | * information. 6 | * 7 | * This program and the accompanying materials are made available under the 8 | * terms of the Eclipse Public License 2.0 which is available at 9 | * http://www.eclipse.org/legal/epl-2.0 10 | * 11 | * SPDX-License-Identifier: EPL-2.0 12 | */ 13 | package org.openhab.binding.synologysurveillancestation.internal.webapi.request; 14 | 15 | import static org.openhab.binding.synologysurveillancestation.SynoBindingConstants.*; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | import org.eclipse.jdt.annotation.NonNullByDefault; 21 | import org.eclipse.jetty.client.HttpClient; 22 | import org.openhab.binding.synologysurveillancestation.internal.SynoConfig; 23 | import org.openhab.binding.synologysurveillancestation.internal.webapi.WebApiException; 24 | import org.openhab.binding.synologysurveillancestation.internal.webapi.error.WebApiAuthErrorCodes; 25 | import org.openhab.binding.synologysurveillancestation.internal.webapi.response.SimpleResponse; 26 | 27 | /** 28 | * SYNO.SurveillanceStation.SynoApiPTZ 29 | * 30 | * This API provides a set of methods to execute PTZ action, and to acquire PTZ related information such as 31 | * patrol list or patrol schedule of a camera. 32 | * 33 | * Method: 34 | * - Move 35 | * - Zoom 36 | * - ListPreset 37 | * - GoPreset 38 | * - ListPatrol 39 | * - RunPatrol 40 | * - Focus 41 | * - Iris 42 | * - AutoFocus 43 | * - AbsPtz 44 | * - Home 45 | * - AutoPan 46 | * - ObjTracking 47 | * 48 | * @author Nils - Initial contribution 49 | * @author Pavion - Contribution 50 | */ 51 | @NonNullByDefault 52 | public class SynoApiPTZ extends SynoApiRequest { 53 | 54 | // API configuration 55 | private static final String API_NAME = "SYNO.SurveillanceStation.PTZ"; 56 | private static final SynoApiConfig API_CONFIG = new SynoApiConfig(API_NAME, API_VERSION_03, API_SCRIPT_ENTRY); 57 | 58 | /** 59 | * @param config 60 | */ 61 | public SynoApiPTZ(SynoConfig config, HttpClient httpClient) { 62 | super(API_CONFIG, config, httpClient); 63 | } 64 | 65 | /** 66 | * Execute the given PTZ method for the passed camera. 67 | * 68 | * @param cameraId 69 | * @param method 70 | * @param command 71 | * @throws WebApiException 72 | */ 73 | public void execute(String cameraId, String method, String command) throws WebApiException { 74 | switch (method) { 75 | case CHANNEL_ZOOM: 76 | switch (command) { 77 | case "IN": 78 | zoomIn(cameraId, MOVE_COMMAND_EMPTY); 79 | break; 80 | case "OUT": 81 | zoomOut(cameraId, MOVE_COMMAND_EMPTY); 82 | break; 83 | // START 84 | case "START_IN": 85 | zoomIn(cameraId, MOVE_COMMAND_START); 86 | break; 87 | case "START_OUT": 88 | zoomOut(cameraId, MOVE_COMMAND_START); 89 | break; 90 | // STOP 91 | case "STOP_IN": 92 | zoomIn(cameraId, MOVE_COMMAND_STOP); 93 | break; 94 | case "STOP_OUT": 95 | zoomOut(cameraId, MOVE_COMMAND_STOP); 96 | break; 97 | } 98 | break; 99 | case CHANNEL_MOVE: 100 | switch (command) { 101 | case "UP": 102 | moveUp(cameraId, MOVE_COMMAND_EMPTY); 103 | break; 104 | case "DOWN": 105 | moveDown(cameraId, MOVE_COMMAND_EMPTY); 106 | break; 107 | case "LEFT": 108 | moveLeft(cameraId, MOVE_COMMAND_EMPTY); 109 | break; 110 | case "RIGHT": 111 | moveRight(cameraId, MOVE_COMMAND_EMPTY); 112 | break; 113 | case "HOME": 114 | moveHome(cameraId, MOVE_COMMAND_EMPTY); 115 | break; 116 | // START 117 | case "START_UP": 118 | moveUp(cameraId, MOVE_COMMAND_START); 119 | break; 120 | case "START_DOWN": 121 | moveDown(cameraId, MOVE_COMMAND_START); 122 | break; 123 | case "START_LEFT": 124 | moveLeft(cameraId, MOVE_COMMAND_START); 125 | break; 126 | case "START_RIGHT": 127 | moveRight(cameraId, MOVE_COMMAND_START); 128 | break; 129 | case "START_HOME": 130 | moveHome(cameraId, MOVE_COMMAND_START); 131 | // STOP 132 | case "STOP_UP": 133 | moveUp(cameraId, MOVE_COMMAND_STOP); 134 | break; 135 | case "STOP_DOWN": 136 | moveDown(cameraId, MOVE_COMMAND_STOP); 137 | break; 138 | case "STOP_LEFT": 139 | moveLeft(cameraId, MOVE_COMMAND_STOP); 140 | break; 141 | case "STOP_RIGHT": 142 | moveRight(cameraId, MOVE_COMMAND_STOP); 143 | break; 144 | case "STOP_HOME": 145 | moveHome(cameraId, MOVE_COMMAND_STOP); 146 | } 147 | break; 148 | default: 149 | break; 150 | } 151 | } 152 | 153 | /** 154 | * calls api method 'zoom' with passed control 155 | * 156 | * @param cameraId 157 | * @param control 158 | * @param moveType 159 | * @return 160 | * @throws WebApiException 161 | */ 162 | private SimpleResponse callZoom(String cameraId, String control, String moveType) throws WebApiException { 163 | Map params = new HashMap<>(); 164 | 165 | // API parameters 166 | params.put("cameraId", cameraId); 167 | params.put("control", control); 168 | if (!moveType.equals(MOVE_COMMAND_EMPTY)) { 169 | Integer version = Integer.parseInt(API_CONFIG.getVersion()); 170 | if (version < 3) { 171 | throw new WebApiException(WebApiAuthErrorCodes.API_VERSION_NOT_SUPPORTED); 172 | } 173 | params.put("moveType", moveType); 174 | } 175 | 176 | return callApi(METHOD_ZOOM, params); 177 | } 178 | 179 | /** 180 | * calls api method 'move' with passed direction and speed 181 | * 182 | * @param cameraId 183 | * @param direction 184 | * @param speed 185 | * @param moveType 186 | * @return 187 | * @throws WebApiException 188 | */ 189 | private SimpleResponse callMove(String cameraId, String direction, int speed, String moveType) 190 | throws WebApiException { 191 | Map params = new HashMap<>(); 192 | 193 | // API Parameters 194 | params.put("cameraId", cameraId); 195 | params.put("direction", direction); 196 | params.put("speed", String.valueOf(speed)); 197 | if (!moveType.equals(MOVE_COMMAND_EMPTY)) { 198 | Integer version = Integer.parseInt(API_CONFIG.getVersion()); 199 | if (version < 3) { 200 | throw new WebApiException(WebApiAuthErrorCodes.API_VERSION_NOT_SUPPORTED); 201 | } 202 | params.put("moveType", moveType); 203 | } 204 | 205 | return callApi(METHOD_MOVE, params); 206 | } 207 | 208 | /** 209 | * Control the PTZ camera to zoom out. 210 | * 211 | * @param cameraId 212 | * @return 213 | * @throws WebApiException 214 | */ 215 | public SimpleResponse zoomOut(String cameraId, String moveType) throws WebApiException { 216 | return callZoom(cameraId, "out", moveType); 217 | } 218 | 219 | /** 220 | * Control the PTZ camera to zoom in. 221 | * 222 | * @param cameraId 223 | * @return 224 | * @throws WebApiException 225 | */ 226 | public SimpleResponse zoomIn(String cameraId, String moveType) throws WebApiException { 227 | return callZoom(cameraId, "in", moveType); 228 | } 229 | 230 | /** 231 | * Control the PTZ camera to move its lens up. 232 | * 233 | * @param cameraId 234 | * @return 235 | * @throws WebApiException 236 | */ 237 | public SimpleResponse moveUp(String cameraId, String moveType) throws WebApiException { 238 | return callMove(cameraId, "up", 1, moveType); 239 | } 240 | 241 | /** 242 | * Control the PTZ camera to move its lens down. 243 | * 244 | * @param cameraId 245 | * @return 246 | * @throws WebApiException 247 | */ 248 | public SimpleResponse moveDown(String cameraId, String moveType) throws WebApiException { 249 | return callMove(cameraId, "down", 1, moveType); 250 | } 251 | 252 | /** 253 | * Control the PTZ camera to move its lens left. 254 | * 255 | * @param cameraId 256 | * @return 257 | * @throws WebApiException 258 | */ 259 | public SimpleResponse moveLeft(String cameraId, String moveType) throws WebApiException { 260 | return callMove(cameraId, "left", 1, moveType); 261 | } 262 | 263 | /** 264 | * Control the PTZ camera to move its lens right. 265 | * 266 | * @param cameraId 267 | * @return 268 | * @throws WebApiException 269 | */ 270 | public SimpleResponse moveRight(String cameraId, String moveType) throws WebApiException { 271 | return callMove(cameraId, "right", 1, moveType); 272 | } 273 | 274 | /** 275 | * Control the PTZ camera to move to preset HOME. 276 | * 277 | * @param cameraId 278 | * @return 279 | * @throws WebApiException 280 | */ 281 | public SimpleResponse moveHome(String cameraId, String moveType) throws WebApiException { 282 | return callMove(cameraId, "home", 1, moveType); 283 | } 284 | 285 | /** 286 | * calls api method 'ListPreset' and list all presets for moving. 287 | * 288 | * @param cameraId 289 | * @return 290 | * @throws WebApiException 291 | */ 292 | public SimpleResponse listPresets(String cameraId) throws WebApiException { 293 | Map params = new HashMap<>(); 294 | 295 | // API Parameters 296 | params.put("cameraId", cameraId); 297 | 298 | SimpleResponse response = callApi(METHOD_LISTPRESET, params); 299 | 300 | return response; 301 | } 302 | 303 | /** 304 | * calls api method 'GoPreset' and move the camera lens to a pre-defined preset position. 305 | * 306 | * @param cameraId 307 | * @param presetId 308 | * @return 309 | * @throws WebApiException 310 | */ 311 | public SimpleResponse goPreset(String cameraId, String presetId) throws WebApiException { 312 | Map params = new HashMap<>(); 313 | 314 | // API Parameters 315 | params.put("cameraId", cameraId); 316 | params.put("presetId", presetId); 317 | // params.put("position", ???); 318 | // params.put("speed", ???); 319 | // params.put("type", ???); 320 | // params.put("isPatrol", ???); 321 | 322 | SimpleResponse response = callApi(METHOD_GOPRESET, params); 323 | 324 | return response; 325 | } 326 | 327 | /** 328 | * calls api method 'ListPatrol' and list all patrols. 329 | * 330 | * @param cameraId 331 | * @return 332 | * @throws WebApiException 333 | */ 334 | public SimpleResponse listPatrol(String cameraId) throws WebApiException { 335 | Map params = new HashMap<>(); 336 | 337 | // API Parameters 338 | params.put("cameraId", cameraId); 339 | 340 | SimpleResponse response = callApi(METHOD_LISTPATROL, params); 341 | 342 | return response; 343 | } 344 | 345 | /** 346 | * calls api method 'RunPatrol' and execute the given patrol. 347 | * 348 | * @param cameraId 349 | * @param patrolId 350 | * @return 351 | * @throws WebApiException 352 | */ 353 | public SimpleResponse runPatrol(String cameraId, String patrolId) throws WebApiException { 354 | Map params = new HashMap<>(); 355 | 356 | // API Parameters 357 | params.put("cameraId", cameraId); 358 | params.put("patrolId", patrolId); 359 | 360 | SimpleResponse response = callApi(METHOD_RUNPATROL, params); 361 | 362 | return response; 363 | } 364 | } 365 | --------------------------------------------------------------------------------