diff --git a/CODEOWNERS b/CODEOWNERS
index f242cfa913b..73e1ed331a8 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -178,6 +178,7 @@
/bundles/org.openhab.binding.magentatv/ @markus7017
/bundles/org.openhab.binding.mail/ @openhab/add-ons-maintainers
/bundles/org.openhab.binding.max/ @marcelrv
+/bundles/org.openhab.binding.mcd/ @simon-dengler
/bundles/org.openhab.binding.mcp23017/ @aogorek
/bundles/org.openhab.binding.mecmeter/ @kaikreuzer
/bundles/org.openhab.binding.melcloud/ @lucacalcaterra @paulianttila @thewiep
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index c1ef37d9b5a..5de67e9d897 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -886,6 +886,11 @@
org.openhab.binding.max
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.mcd
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.mcp23017
diff --git a/bundles/org.openhab.binding.mcd/NOTICE b/bundles/org.openhab.binding.mcd/NOTICE
new file mode 100644
index 00000000000..38d625e3492
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.mcd/README.md b/bundles/org.openhab.binding.mcd/README.md
new file mode 100644
index 00000000000..17cc1ec11b3
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/README.md
@@ -0,0 +1,143 @@
+# MCD Binding
+
+This binding allows you to send sensor events from your openHAB environment to the cloud application Managing Care Digital (MCD) by [C&S Computer und Software GmbH](https://www.managingcare.de/).
+
+MCD is the platform for inpatient and outpatient nursing services.
+Our REST API allows you to send a variety of sensor events to the system and thus being able to connect your Ambient Assisted Living (AAL) or smart home environment to the documentation software of your nursing service.
+
+Please note that a valid account is needed to access MCD and the Sensor API.
+
+
+## Supported Things
+
+There are two supported things: **MCD Bridge** and **MCD Sensor Thing**.
+
+
+## Discovery
+
+Discovery is not supported.
+
+
+## Thing Configuration
+
+This section shows the configuration parameters of both supported things.
+
+### MCD Bridge
+
+The MCD Bridge (`mcdBridge`) needs to be configured with your valid C&S MCD / sync API credentials.
+
+| parameter | description |
+|-----------|------------------------------------|
+| userEmail | Email of account |
+| userPassword | valid password for the given email |
+
+### MCD Sensor Thing
+
+Each sensor thing (`mcdSensor`) needs to be configured with the identical serial number, that is assigned to this sensor in MCD.
+
+| parameter | description |
+|----------------|------------------------------------|
+| serialNumber | serial number of the sensor in MCD |
+
+## Channels
+
+The `mcdSensor` thing supports the following channels. To see the sensors' events, please visit [Managing Care Digital](https://cundsdokumentation.de/) and navigate to the dashboard.
+
+| channel | type | description |
+|-------------|--------|-----------------------------------------------|
+| lastEvent | String | shows the last event that was sent with date and time |
+| sendEvent | String | stateless channel for sending events to the API, see list below for valid commands |
+
+The channel `sendEvent` accepts valid Sensor Event Definitions as well as the corresponding ID.
+The following table contains all currently accepted Sensor Event Definitions that can be passed as String type commands.
+As soon as new events are added to the API, you can use their ID, even if the Definition is not yet added to this list.
+For more information about the API, you can have a look at the [C&S Sync API](https://cunds-syncapi.azurewebsites.net/ApiDocumentation).
+
+| Valid String Type Commands |
+|------------|
+| BEDEXIT |
+| BEDENTRY |
+| FALL |
+| CHANGEPOSITION |
+| BATTERYSTATE |
+| INACTIVITY |
+| ALARM |
+| OPEN |
+| CLOSE |
+| ON |
+| OFF |
+| ACTIVITY |
+| CAPACITY |
+| GAS |
+| VITALVALUE |
+| ROOMEXIT |
+| ROOMENTRY |
+| REMOVESENSOR |
+| SITDOWN |
+| STANDUP |
+| INACTIVITYROOM |
+| SMOKEALARM |
+| HEAT |
+| COLD |
+| QUALITYAIR |
+| ALARMAIR |
+| ROOMTEMPERATURE |
+| HUMIDITY |
+| AIRPRESSURE |
+| CO2 |
+| INDEXUV |
+| WEARTIME |
+| FIRSTURINE |
+| NEWDIAPER |
+| DIAPERREMOVED |
+| NOCONNECTION |
+| LOWBATTERY |
+| CONTROLLSENSOR |
+| LYING |
+| SPILLED |
+| DAMAGED |
+| GEOEXIT |
+| GEOENTRY |
+| WALKING |
+| RESTING |
+| TURNAROUND |
+| HOMEEMERGENCY |
+| TOILETFLUSH |
+| DORSALPOSITION |
+| ABDOMINALPOSITION |
+| LYINGLEFT |
+| LYINGRIGHT |
+| LYINGHALFLEFT |
+| LYINGHALFRIGHT |
+| MOVEMENT |
+| PRESENCE |
+| NUMBERPERSONS |
+| BRIGHTNESSZONE |
+
+
+## Full Example
+
+Here is an example for the textual configuration. You can of course use the Administration section of the GUI as well.
+
+demo.things:
+
+```
+Bridge mcd:mcdBridge:exampleBridge [userEmail="your.email@examle.com", userPassword="your.password"]{
+ Thing mcd:mcdSensor:examlpeSensor [serialNumber="123"]
+ Thing mcd:mcdSensor:secondExamlpeSensor [serialNumber="456"]
+}
+```
+
+demo.items:
+
+```
+String lastValue "Last Value" {channel="mcd:mcdSensor:examlpeSensor:lastValue"}
+String sendEvent "Send Event" {channel="mcd:mcdSensor:examlpeSensor:sendEvent"}
+```
+
+demo.sitemap:
+
+```
+Text item=sendEvent
+Text item=lastValue
+```
diff --git a/bundles/org.openhab.binding.mcd/pom.xml b/bundles/org.openhab.binding.mcd/pom.xml
new file mode 100644
index 00000000000..12558af5f25
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 3.4.0-SNAPSHOT
+
+
+ org.openhab.binding.mcd
+
+ openHAB Add-ons :: Bundles :: MCD Binding
+
+
diff --git a/bundles/org.openhab.binding.mcd/src/main/feature/feature.xml b/bundles/org.openhab.binding.mcd/src/main/feature/feature.xml
new file mode 100644
index 00000000000..3a05b0e7e3c
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.mcd/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/McdBindingConstants.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/McdBindingConstants.java
new file mode 100644
index 00000000000..0b92f9b5211
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/McdBindingConstants.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link McdBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+public class McdBindingConstants {
+
+ private static final String BINDING_ID = "mcd";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_MCD_BRIDGE = new ThingTypeUID(BINDING_ID, "mcdBridge");
+ public static final ThingTypeUID THING_TYPE_SENSOR = new ThingTypeUID(BINDING_ID, "mcdSensor");
+
+ // List of all Channel ids
+ public static final String LAST_VALUE = "lastValue";
+ public static final String SEND_EVENT = "sendEvent";
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/McdHandlerFactory.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/McdHandlerFactory.java
new file mode 100644
index 00000000000..b044520c601
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/McdHandlerFactory.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal;
+
+import static org.openhab.binding.mcd.internal.McdBindingConstants.THING_TYPE_MCD_BRIDGE;
+import static org.openhab.binding.mcd.internal.McdBindingConstants.THING_TYPE_SENSOR;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.mcd.internal.handler.McdBridgeHandler;
+import org.openhab.binding.mcd.internal.handler.SensorThingHandler;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link McdHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.mcd", service = ThingHandlerFactory.class)
+public class McdHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MCD_BRIDGE,
+ THING_TYPE_SENSOR);
+ private final HttpClient httpClient;
+
+ @Activate
+ public McdHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (THING_TYPE_MCD_BRIDGE.equals(thingTypeUID)) {
+ return new McdBridgeHandler((Bridge) thing, httpClient);
+ }
+
+ if (THING_TYPE_SENSOR.equals(thingTypeUID)) {
+ return new SensorThingHandler(thing, httpClient);
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/McdBridgeConfiguration.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/McdBridgeConfiguration.java
new file mode 100644
index 00000000000..b68fa403eea
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/McdBridgeConfiguration.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link McdBridgeConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+public class McdBridgeConfiguration {
+
+ private @Nullable String userEmail;
+ private @Nullable String userPassword;
+
+ /**
+ * Return user email as string
+ *
+ * @return User email as string
+ */
+ @Nullable
+ String getUserEmail() {
+ return userEmail;
+ }
+
+ /**
+ * Return user password as string
+ *
+ * @return password as string
+ */
+ @Nullable
+ String getUserPassword() {
+ return userPassword;
+ }
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/McdBridgeHandler.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/McdBridgeHandler.java
new file mode 100644
index 00000000000..83f2ae5fa7c
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/McdBridgeHandler.java
@@ -0,0 +1,164 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.handler;
+
+import java.util.HashSet;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.mcd.internal.util.Listener;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link McdBridgeHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+public class McdBridgeHandler extends BaseBridgeHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(McdBridgeHandler.class);
+
+ private @Nullable McdBridgeConfiguration config;
+
+ private final HttpClient httpClient;
+ private final Gson gson;
+
+ private String accessToken = "";
+ private int expiresIn;
+ private @Nullable ScheduledFuture> future = null;
+
+ private HashSet listeners = new HashSet<>();
+
+ public McdBridgeHandler(Bridge bridge, HttpClient httpClient) {
+ super(bridge);
+ this.httpClient = httpClient;
+ gson = new Gson();
+ }
+
+ @Override
+ public void initialize() {
+ config = getConfigAs(McdBridgeConfiguration.class);
+ updateStatus(ThingStatus.UNKNOWN);
+ scheduler.execute(this::logMeIn);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ }
+
+ @Override
+ public void dispose() {
+ ScheduledFuture> localFuture = future;
+ if (localFuture != null) {
+ localFuture.cancel(true);
+ }
+ }
+
+ public void register(Listener listener) {
+ listeners.add(listener);
+ }
+
+ private void triggerEvent() {
+ logger.debug("Event triggered");
+ for (Listener l : listeners) {
+ l.onEvent();
+ }
+ }
+
+ /**
+ * Uses the given credentials to log the user in.
+ */
+ protected void logMeIn() {
+ logger.debug("Logging in...");
+ McdBridgeConfiguration localConfig = config;
+ if (localConfig != null) {
+ try {
+ Request request = httpClient.newRequest("https://cunds-syncapi.azurewebsites.net/token")
+ .method(HttpMethod.POST).header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded")
+ .header(HttpHeader.HOST, "cunds-syncapi.azurewebsites.net")
+ .header(HttpHeader.ACCEPT, "application/json");
+ String content = "grant_type=password&username=" + localConfig.getUserEmail() + "&password="
+ + localConfig.getUserPassword();
+ request.content(new StringContentProvider(content), "application/x-www-form-urlencoded");
+ request.send(new BufferingResponseListener() {
+ @NonNullByDefault({})
+ @Override
+ public void onComplete(Result result) {
+ String contentString = getContentAsString();
+ JsonObject content = gson.fromJson(contentString, JsonObject.class);
+ int responseCode = result.getResponse().getStatus();
+ switch (responseCode) {
+ case 200:
+ if (content != null && content.has("access_token")) {
+ updateStatus(ThingStatus.ONLINE);
+ accessToken = content.get("access_token").getAsString();
+ expiresIn = content.get("expires_in").getAsInt();
+ long delay = ((long) expiresIn) * 1000L - 60000L;
+ Runnable task = () -> {
+ logMeIn();
+ };
+ future = scheduler.schedule(task, delay, TimeUnit.MILLISECONDS);
+ getAccessToken();
+ break;
+ } // else go to default
+ case 400:
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "wrong credentials");
+ break;
+ case 0:
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "please check your internet connection");
+ break;
+ default:
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Login was not successful");
+ }
+ triggerEvent();
+ }
+ });
+ } catch (Exception e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Should be used by C&S binding's things to obtain the bridge's access token
+ *
+ * @return returns the access token as String
+ */
+ protected String getAccessToken() {
+ return accessToken;
+ }
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/SensorThingConfiguration.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/SensorThingConfiguration.java
new file mode 100644
index 00000000000..6f96ff301d6
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/SensorThingConfiguration.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link SensorThingConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+public class SensorThingConfiguration {
+ private @Nullable String serialNumber;
+
+ /**
+ * return serial number as string
+ *
+ * @return serial number as string
+ */
+ public @Nullable String getSerialNumber() {
+ return serialNumber;
+ }
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/SensorThingHandler.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/SensorThingHandler.java
new file mode 100644
index 00000000000..8197da6a7be
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/handler/SensorThingHandler.java
@@ -0,0 +1,427 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.handler;
+
+import static org.openhab.binding.mcd.internal.McdBindingConstants.*;
+
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.mcd.internal.util.Callback;
+import org.openhab.binding.mcd.internal.util.SensorEventDef;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+/**
+ * Handler for the SensorThing of the MCD Binding.
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+public class SensorThingHandler extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(SensorThingHandler.class);
+ private final HttpClient httpClient;
+ private final @Nullable Gson gson;
+ private @Nullable McdBridgeHandler mcdBridgeHandler;
+ private @Nullable String serialNumber = "";
+ private @Nullable SensorThingConfiguration config;
+ private int maxSensorEventId = 0;
+ private boolean initIsDone = false;
+
+ public SensorThingHandler(Thing thing, HttpClient httpClient) {
+ super(thing);
+ this.httpClient = httpClient;
+ gson = new Gson();
+ }
+
+ @Override
+ public void initialize() {
+ config = getConfigAs(SensorThingConfiguration.class);
+ Bridge bridge = getBridge();
+ if (bridge != null) {
+ mcdBridgeHandler = (McdBridgeHandler) bridge.getHandler();
+ } else {
+ mcdBridgeHandler = null;
+ }
+ updateStatus(ThingStatus.UNKNOWN);
+ scheduler.execute(this::init);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType) {
+ refreshChannelValue();
+ } else if (mcdBridgeHandler != null) {
+ String channelId = channelUID.getId();
+ // check for the right channel id
+ if (channelId.equals(SEND_EVENT)) {
+ String commandString = command.toString();
+ int sensorEventId = SensorEventDef.getSensorEventId(commandString);
+ if (sensorEventId < 1 || sensorEventId > maxSensorEventId) {
+ // check, if an id is passed as number
+ try {
+ sensorEventId = Integer.parseInt(commandString);
+ if (sensorEventId < 1 || sensorEventId > maxSensorEventId) {
+ logger.warn("Invalid Command!");
+ } else {
+ sendSensorEvent(serialNumber, sensorEventId);
+ }
+ } catch (Exception e) {
+ logger.warn("Invalid Command!");
+ }
+ } else {
+ // command was valid (and id is between 1 and max)
+ sendSensorEvent(serialNumber, sensorEventId);
+ }
+ } else {
+ logger.warn("Received command for unexpected channel!");
+ }
+ refreshChannelValue();
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Bridge is offline.");
+ }
+ }
+
+ // this is called from initialize()
+ private void init() {
+ SensorThingConfiguration localConfig = config;
+ if (localConfig != null) {
+ serialNumber = localConfig.getSerialNumber();
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Cannot access config data.");
+ }
+ McdBridgeHandler localMcdBridgeHandler = mcdBridgeHandler;
+ if (localMcdBridgeHandler != null) {
+ updateStatus(ThingStatus.ONLINE);
+ if (!initIsDone) {
+ // build and register listener
+ localMcdBridgeHandler.register(() -> {
+ try {
+ // determine, if thing is specified correctly and if it is online
+ fetchDeviceInfo(res -> {
+ if (res != null) {
+ JsonObject result = res.getAsJsonObject();
+ if (result.has("SerialNumber")) {
+ // check for serial number in MCD cloud
+ if (result.get("SerialNumber").isJsonNull()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Serial number does not exist in MCD!");
+ } else {
+ // refresh channel values and set thing status to ONLINE
+ refreshChannelValue();
+ updateStatus(ThingStatus.ONLINE);
+ }
+ }
+ }
+ });
+ fetchEventDef(jsonElement -> {
+ if (jsonElement != null) {
+ JsonArray eventDefArray = jsonElement.getAsJsonArray();
+ maxSensorEventId = eventDefArray.size();
+ }
+ });
+ } catch (Exception e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ });
+ initIsDone = true;
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "unable to access bridge");
+ }
+ }
+
+ /**
+ * This method uses the things serial number in order to obtain the latest
+ * sensor event, that was registered in the
+ * C&S MCD cloud, and then updates the channels with this latest value.
+ */
+ private void refreshChannelValue() {
+ try {
+ /*
+ * First, the device info for the given serial number is requested from the
+ * cloud, which is then used fetch
+ * the latest sensor event and update the channels.
+ */
+ fetchDeviceInfo(deviceInfo -> {
+ // build request URI String
+ String requestUrl = getUrlStringFromDeviceInfo((JsonObject) deviceInfo);
+ try {
+ if (requestUrl != null) {
+ // get latest sensor event
+ fetchLatestValue(requestUrl, result -> {
+ JsonObject latestValue = getLatestValueFromJsonArray((JsonArray) result);
+ // update channels
+ updateChannels(latestValue);
+ });
+ } else {
+ logger.warn(
+ "Unable to synchronize! Please assign sensor to patient or organization unit in MCD!");
+ }
+ } catch (Exception e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ });
+ } catch (Exception e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ /**
+ * Updates the channels of the sensor thing with the latest value.
+ *
+ * @param latestValue the latest value as JsonObject as obtained from the REST
+ * API
+ */
+ private void updateChannels(@Nullable JsonObject latestValue) {
+ if (latestValue != null) {
+ String event = latestValue.get("EventDef").getAsString();
+ String dateString = latestValue.get("DateEntry").getAsString();
+ try {
+ Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(dateString);
+ dateString = new SimpleDateFormat("dd.MM.yyyy', 'HH:mm:ss").format(date);
+ } catch (Exception e) {
+ logger.debug("{}", e.getMessage());
+ }
+ updateState(LAST_VALUE, new StringType(event + ", " + dateString));
+ }
+ }
+
+ /**
+ * Make asynchronous HTTP request to fetch the sensors last value as JsonObject.
+ *
+ * @param urlString Contains the request URI as String
+ * @param callback Implementation of interface Callback
+ * (org.openhab.binding.mcd.internal.util), that includes
+ * the proceeding of the obtained JsonObject.
+ * @throws Exception Throws HTTP related Exceptions.
+ */
+ private void fetchLatestValue(String urlString, Callback callback) throws Exception {
+ McdBridgeHandler localMcdBridgeHandler = mcdBridgeHandler;
+ if (localMcdBridgeHandler != null) {
+ String accessToken = localMcdBridgeHandler.getAccessToken();
+ Request request = httpClient.newRequest(urlString).method(HttpMethod.GET)
+ .header(HttpHeader.HOST, "cunds-syncapi.azurewebsites.net")
+ .header(HttpHeader.ACCEPT, "application/json")
+ .header(HttpHeader.AUTHORIZATION, "Bearer " + accessToken);
+ request.send(new BufferingResponseListener() {
+ @NonNullByDefault({})
+ @Override
+ public void onComplete(Result result) {
+ String contentString = getContentAsString();
+ Gson localGson = gson;
+ if (localGson != null) {
+ JsonArray content = localGson.fromJson(contentString, JsonArray.class);
+ callback.jsonElementTypeCallback(content);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * get device info as json via http request
+ *
+ * @param callback instance of callback interface
+ * @throws Exception throws http related exceptions
+ */
+ private void fetchDeviceInfo(Callback callback) throws Exception {
+ McdBridgeHandler localMcdBridgeHandler = mcdBridgeHandler;
+ if (localMcdBridgeHandler != null) {
+ String accessToken = localMcdBridgeHandler.getAccessToken();
+ Request request = httpClient
+ .newRequest("https://cunds-syncapi.azurewebsites.net/api/Device?serialNumber=" + serialNumber)
+ .method(HttpMethod.GET).header(HttpHeader.HOST, "cunds-syncapi.azurewebsites.net")
+ .header(HttpHeader.ACCEPT, "application/json")
+ .header(HttpHeader.AUTHORIZATION, "Bearer " + accessToken);
+ request.send(new BufferingResponseListener() {
+ @NonNullByDefault({})
+ @Override
+ public void onComplete(Result result) {
+ String contentString = getContentAsString();
+ Gson localGson = gson;
+ if (localGson != null) {
+ JsonObject content = localGson.fromJson(contentString, JsonObject.class);
+ callback.jsonElementTypeCallback(content);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Sends a GET request to the C&S REST API to receive the list of sensor event
+ * definitions.
+ *
+ * @param callback Implementation of interface Callback
+ * (org.openhab.binding.mcd.internal.util), that includes
+ * the proceeding of the obtained JsonObject.
+ * @throws Exception Throws HTTP related Exceptions.
+ */
+ private void fetchEventDef(Callback callback) throws Exception {
+ McdBridgeHandler localMcdBridgeHandler = mcdBridgeHandler;
+ if (localMcdBridgeHandler != null) {
+ String accessToken = localMcdBridgeHandler.getAccessToken();
+ Request request = httpClient.newRequest("https://cunds-syncapi.azurewebsites.net/api/ApiSensor/GetEventDef")
+ .method(HttpMethod.GET).header(HttpHeader.HOST, "cunds-syncapi.azurewebsites.net")
+ .header(HttpHeader.ACCEPT, "application/json")
+ .header(HttpHeader.AUTHORIZATION, "Bearer " + accessToken);
+ request.send(new BufferingResponseListener() {
+ @NonNullByDefault({})
+ @Override
+ public void onComplete(Result result) {
+ String contentString = getContentAsString();
+ Gson localGson = gson;
+ if (localGson != null) {
+ JsonArray content = localGson.fromJson(contentString, JsonArray.class);
+ callback.jsonElementTypeCallback(content);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Builds the URI String for requesting the latest sensor event from the API. In
+ * order to do that, the parameter
+ * deviceInfo is needed.
+ *
+ * @param deviceInfo JsonObject that contains the device info as received from
+ * the C&S API
+ * @return returns the URI as String or null, if no patient or organisation unit
+ * is assigned to the sensor in the
+ * MCD cloud
+ */
+ @Nullable
+ String getUrlStringFromDeviceInfo(@Nullable JsonObject deviceInfo) {
+ if (deviceInfo != null) {
+ if (deviceInfo.has("SerialNumber") && deviceInfo.get("SerialNumber").getAsString().equals(serialNumber)) {
+ if (deviceInfo.has("PatientDevices") && deviceInfo.getAsJsonArray("PatientDevices").size() != 0) {
+ JsonArray array = deviceInfo.getAsJsonArray("PatientDevices");
+ JsonObject patient = array.get(0).getAsJsonObject();
+ if (patient.has("UuidPerson") && !patient.get("UuidPerson").isJsonNull()) {
+ return "https://cunds-syncapi.azurewebsites.net/api/ApiSensor/GetLatestApiSensorEvents"
+ + "?UuidPatient=" + patient.get("UuidPerson").getAsString() + "&SerialNumber="
+ + serialNumber + "&Count=1";
+ }
+ } else if (deviceInfo.has("OrganisationUnitDevices")
+ && deviceInfo.getAsJsonArray("OrganisationUnitDevices").size() != 0) {
+ JsonArray array = deviceInfo.getAsJsonArray("OrganisationUnitDevices");
+ JsonObject orgUnit = array.get(0).getAsJsonObject();
+ if (orgUnit.has("UuidOrganisationUnit") && !orgUnit.get("UuidOrganisationUnit").isJsonNull()) {
+ return "https://cunds-syncapi.azurewebsites.net/api/ApiSensor/GetLatestApiSensorEvents"
+ + "?UuidOrganisationUnit=" + orgUnit.get("UuidOrganisationUnit").getAsString()
+ + "&SerialNumber=" + serialNumber + "&Count=1";
+ }
+ }
+ } else {
+ init();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Extracts the latest value from the JsonArray, that is obtained by the C&S
+ * SensorApi.
+ *
+ * @param jsonArray the array that contains the latest value
+ * @return the latest value as JsonObject or null.
+ */
+ @Nullable
+ static JsonObject getLatestValueFromJsonArray(@Nullable JsonArray jsonArray) {
+ if (jsonArray != null) {
+ if (jsonArray.size() != 0) {
+ JsonObject patientObject = jsonArray.get(0).getAsJsonObject();
+ JsonArray devicesArray = patientObject.getAsJsonArray("Devices");
+ if (devicesArray.size() != 0) {
+ JsonObject deviceObject = devicesArray.get(0).getAsJsonObject();
+ if (deviceObject.has("Events")) {
+ JsonArray eventsArray = deviceObject.getAsJsonArray("Events");
+ if (eventsArray.size() != 0) {
+ return eventsArray.get(0).getAsJsonObject();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sends data to the cloud via POST request and switches the channel states from
+ * ON to OFF for a number of channels.
+ *
+ * @param serialNumber serial number of the sensor in the MCD cloud
+ * @param sensorEventDef specifies the type of sensor event, that will be sent
+ */
+ private void sendSensorEvent(@Nullable String serialNumber, int sensorEventDef) {
+ try {
+ McdBridgeHandler localMcdBridgeHandler = mcdBridgeHandler;
+ if (localMcdBridgeHandler != null) {
+ String accessToken = localMcdBridgeHandler.getAccessToken();
+ Date date = new Date();
+ String dateString = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").format(date);
+ Request request = httpClient.newRequest("https://cunds-syncapi.azurewebsites.net/api/ApiSensor")
+ .method(HttpMethod.POST).header(HttpHeader.CONTENT_TYPE, "application/json")
+ .header(HttpHeader.ACCEPT, "application/json")
+ .header(HttpHeader.AUTHORIZATION, "Bearer " + accessToken);
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("SerialNumber", serialNumber);
+ jsonObject.addProperty("IdApiSensorEventDef", sensorEventDef);
+ jsonObject.addProperty("DateEntry", dateString);
+ jsonObject.addProperty("DateSend", dateString);
+ request.content(
+ new StringContentProvider("application/json", jsonObject.toString(), StandardCharsets.UTF_8));
+ request.send(new BufferingResponseListener() {
+ @NonNullByDefault({})
+ @Override
+ public void onComplete(Result result) {
+ if (result.getResponse().getStatus() != 201) {
+ logger.debug("Unable to send sensor event:\n{}", result.getResponse().toString());
+ } else {
+ logger.debug("Sensor event was stored successfully.");
+ refreshChannelValue();
+ }
+ }
+ });
+ }
+ } catch (Exception e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/Callback.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/Callback.java
new file mode 100644
index 00000000000..fb9cd7f8f33
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/Callback.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.util;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.JsonElement;
+
+/**
+ * This interface is used for callback events.
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+public interface Callback {
+ void jsonElementTypeCallback(@Nullable JsonElement jsonObject);
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/Listener.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/Listener.java
new file mode 100644
index 00000000000..27265ecbd94
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/Listener.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.util;
+
+import java.util.EventListener;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Interface for registration of event listeners.
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+public interface Listener extends EventListener {
+ void onEvent();
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/SensorEventDef.java b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/SensorEventDef.java
new file mode 100644
index 00000000000..021b8c37d14
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/java/org/openhab/binding/mcd/internal/util/SensorEventDef.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * class that contains MCD SensorEventDefinitions
+ *
+ * @author Simon Dengler - Initial contribution
+ */
+@NonNullByDefault
+public class SensorEventDef {
+
+ // Sensor Events in order of their ids as specified by C&S syncapi
+ private static final String[] EVENT_DEFINITION_ARRAY = { "", "UNDEFINED", "BEDEXIT", "BEDENTRY", "FALL",
+ "CHANGEPOSITION", "BATTERYSTATE", "INACTIVITY", "ALARM", "OPEN", "CLOSE", "ON", "OFF", "ACTIVITY",
+ "CAPACITY", "GAS", "VITALVALUE", "ROOMEXIT", "ROOMENTRY", "REMOVESENSOR", "SITDOWN", "STANDUP",
+ "INACTIVITYROOM", "SMOKEALARM", "HEAT", "COLD", "QUALITYAIR", "ALARMAIR", "ROOMTEMPERATURE", "HUMIDITY",
+ "AIRPRESSURE", "CO2", "INDEXUV", "WEARTIME", "FIRSTURINE", "NEWDIAPER", "DIAPERREMOVED", "NOCONNECTION",
+ "LOWBATTERY", "CONTROLLSENSOR", "LYING", "SPILLED", "DAMAGED", "GEOEXIT", "GEOENTRY", "WALKING", "RESTING",
+ "TURNAROUND", "HOMEEMERGENCY", "TOILETFLUSH", "DORSALPOSITION", "ABDOMINALPOSITION", "LYINGLEFT",
+ "LYINGRIGHT", "LYINGHALFLEFT", "LYINGHALFRIGHT", "MOVEMENT", "PRESENCE", "NUMBERPERSONS",
+ "BRIGHTNESSZONE" };
+ private static ArrayList sensorEventDefinition = new ArrayList(
+ Arrays.asList(EVENT_DEFINITION_ARRAY));
+
+ public static ArrayList getSensorEventDefinition() {
+ return sensorEventDefinition;
+ }
+
+ public static int getSensorEventId(String eventName) {
+ return sensorEventDefinition.indexOf(eventName);
+ }
+}
diff --git a/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644
index 00000000000..0114327df0e
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/binding/binding.xml
@@ -0,0 +1,14 @@
+
+
+
+ MCD Binding
+ This binding allows you to send sensor events from your openHAB environment to the cloud application
+ Managing Care Digital (MCD) by C&S Computer und Software GmbH (https://www.managingcare.de/). MCD is the platform
+ for inpatient and outpatient nursing services. Our REST API allows you to send a variety of sensor events to the
+ system and thus being able to connect your Ambient Assisted Living (AAL) or smart home environment to the
+ documentation software of your nursing service. Please note that a valid account is needed to access MCD and the
+ Sensor API.
+
+
diff --git a/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/i18n/mcd.properties b/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/i18n/mcd.properties
new file mode 100644
index 00000000000..696e9b9f275
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/i18n/mcd.properties
@@ -0,0 +1,85 @@
+# binding
+
+binding.mcd.name = MCD Binding
+binding.mcd.description = This binding allows you to send sensor events from your openHAB environment to the cloud application Managing Care Digital (MCD) by C&S Computer und Software GmbH (https://www.managingcare.de/). MCD is the platform for inpatient and outpatient nursing services. Our REST API allows you to send a variety of sensor events to the system and thus being able to connect your Ambient Assisted Living (AAL) or smart home environment to the documentation software of your nursing service. Please note that a valid account is needed to access MCD and the Sensor API.
+
+# thing types
+
+thing-type.mcd.mcdBridge.label = MCD Bridge
+thing-type.mcd.mcdBridge.description = C&S Managing Care Digital (MCD) account that is used to send data.
+thing-type.mcd.mcdSensor.label = MCD Sensor
+thing-type.mcd.mcdSensor.description = Sends data for one mcdSensor to MCD, the C&S cloud application.
+
+# thing types config
+
+thing-type.config.mcd.mcdBridge.userEmail.label = Email
+thing-type.config.mcd.mcdBridge.userEmail.description = Email of your MCD account.
+thing-type.config.mcd.mcdBridge.userPassword.label = Password
+thing-type.config.mcd.mcdBridge.userPassword.description = Password for your MCD account.
+thing-type.config.mcd.mcdSensor.serialNumber.label = Serial Number
+thing-type.config.mcd.mcdSensor.serialNumber.description = Please enter the serial number of the sensor. It must match the serial number of this sensor in MCD.
+
+# channel types
+
+channel-type.mcd.lastValue.label = Last Value
+channel-type.mcd.lastValue.description = Shows time and value of the last sensor event (readonly).
+channel-type.mcd.sendEvent.label = Send Event
+channel-type.mcd.sendEvent.description = A stateless channel for sending sensor events.
+channel-type.mcd.sendEvent.command.option.BEDEXIT = Bed Exit
+channel-type.mcd.sendEvent.command.option.BEDENTRY = Bed Entry
+channel-type.mcd.sendEvent.command.option.FALL = Fall
+channel-type.mcd.sendEvent.command.option.CHANGEPOSITION = Change Position
+channel-type.mcd.sendEvent.command.option.BATTERYSTATE = Battery State
+channel-type.mcd.sendEvent.command.option.INACTIVITY = Inactivity
+channel-type.mcd.sendEvent.command.option.ALARM = Alarm
+channel-type.mcd.sendEvent.command.option.OPEN = Open
+channel-type.mcd.sendEvent.command.option.CLOSE = Close
+channel-type.mcd.sendEvent.command.option.ON = On
+channel-type.mcd.sendEvent.command.option.OFF = Off
+channel-type.mcd.sendEvent.command.option.ACTIVITY = Activity
+channel-type.mcd.sendEvent.command.option.CAPACITY = Capacity
+channel-type.mcd.sendEvent.command.option.GAS = Gas
+channel-type.mcd.sendEvent.command.option.VITALVALUE = Vital Value
+channel-type.mcd.sendEvent.command.option.ROOMEXIT = Room Exit
+channel-type.mcd.sendEvent.command.option.ROOMENTRY = Room Entry
+channel-type.mcd.sendEvent.command.option.REMOVESENSOR = Remove Sensor
+channel-type.mcd.sendEvent.command.option.SITDOWN = Sit Down
+channel-type.mcd.sendEvent.command.option.STANDUP = Stand Up
+channel-type.mcd.sendEvent.command.option.INACTIVITYROOM = Inactivity Room
+channel-type.mcd.sendEvent.command.option.SMOKEALARM = Smoke Alarm
+channel-type.mcd.sendEvent.command.option.HEAT = Heat
+channel-type.mcd.sendEvent.command.option.COLD = Cold
+channel-type.mcd.sendEvent.command.option.QUALITYAIR = Quality Air
+channel-type.mcd.sendEvent.command.option.ALARMAIR = Alarm Air
+channel-type.mcd.sendEvent.command.option.ROOMTEMPERATURE = Room Temperature
+channel-type.mcd.sendEvent.command.option.HUMIDITY = Humidity
+channel-type.mcd.sendEvent.command.option.AIRPRESSURE = Airpressure
+channel-type.mcd.sendEvent.command.option.CO2 = CO2
+channel-type.mcd.sendEvent.command.option.INDEXUV = UV Index
+channel-type.mcd.sendEvent.command.option.WEARTIME = Weartime
+channel-type.mcd.sendEvent.command.option.FIRSTURINE = First Urine
+channel-type.mcd.sendEvent.command.option.NEWDIAPER = New Diaper
+channel-type.mcd.sendEvent.command.option.DIAPERREMOVED = Diaper Removal
+channel-type.mcd.sendEvent.command.option.NOCONNECTION = No Connection
+channel-type.mcd.sendEvent.command.option.LOWBATTERY = Low Battery
+channel-type.mcd.sendEvent.command.option.CONTROLLSENSOR = Controll Sensor
+channel-type.mcd.sendEvent.command.option.LYING = Lying
+channel-type.mcd.sendEvent.command.option.SPILLED = Spilled
+channel-type.mcd.sendEvent.command.option.DAMAGED = Damaged
+channel-type.mcd.sendEvent.command.option.GEOEXIT = Geo Exit
+channel-type.mcd.sendEvent.command.option.GEOENTRY = Geo Entry
+channel-type.mcd.sendEvent.command.option.WALKING = Walking
+channel-type.mcd.sendEvent.command.option.RESTING = Resting
+channel-type.mcd.sendEvent.command.option.TURNAROUND = Turnaround
+channel-type.mcd.sendEvent.command.option.HOMEEMERGENCY = Home Emergency
+channel-type.mcd.sendEvent.command.option.TOILETFLUSH = Toilet Flush
+channel-type.mcd.sendEvent.command.option.DORSALPOSITION = Dorsal Position
+channel-type.mcd.sendEvent.command.option.ABDOMINALPOSITION = Abdominal Position
+channel-type.mcd.sendEvent.command.option.LYINGLEFT = Lying Left
+channel-type.mcd.sendEvent.command.option.LYINGRIGHT = Lying Right
+channel-type.mcd.sendEvent.command.option.LYINGHALFLEFT = Lying Half Left
+channel-type.mcd.sendEvent.command.option.LYINGHALFRIGHT = Lying Half Right
+channel-type.mcd.sendEvent.command.option.MOVEMENT = Movement
+channel-type.mcd.sendEvent.command.option.PRESENCE = Presence
+channel-type.mcd.sendEvent.command.option.NUMBERPERSONS = Number Persons
+channel-type.mcd.sendEvent.command.option.BRIGHTNESSZONE = Brightness Zone
diff --git a/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 00000000000..42f93ce5ba3
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+ C&S Managing Care Digital (MCD) account that is used to send data.
+
+
+ email
+
+ Email of your MCD account.
+
+
+ password
+
+ Password for your MCD account.
+
+
+
+
+
+
+
+
+
+ Sends data for one mcdSensor to MCD, the C&S cloud application.
+
+
+
+
+
+
+
+ Please enter the serial number of the sensor. It must match the serial number of this sensor in MCD.
+
+
+
+
+
+ String
+
+ Shows time and value of the last sensor event (readonly).
+
+
+
+ String
+
+ A stateless channel for sending sensor events.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.mcd/src/test/java/org/openhab/binding/mcd/internal/handler/SensorThingHandlerTest.java b/bundles/org.openhab.binding.mcd/src/test/java/org/openhab/binding/mcd/internal/handler/SensorThingHandlerTest.java
new file mode 100644
index 00000000000..0684a72bd62
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/test/java/org/openhab/binding/mcd/internal/handler/SensorThingHandlerTest.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.handler;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+/**
+ * @author Dengler - Initial contribution
+ */
+@NonNullByDefault
+public class SensorThingHandlerTest {
+
+ private final Gson gson = new Gson();
+
+ @Test
+ public void getLatestValueFromJsonObjectTest() {
+ String arrayString = "[\n" + " {\n" + " \"IdPatient\": 1,\n" + " \"LastName\": \"Mustermann\",\n"
+ + " \"FirstName\": \"Max\",\n" + " \"Devices\": [\n" + " {\n" + " \"IdDevice\": 2,\n"
+ + " \"SerialNumber\": \"001\",\n" + " \"Name\": \"Test Sitzkissen\",\n"
+ + " \"Events\": [\n" + " {\n" + " \"EventDef\": \"Alarm\",\n"
+ + " \"DateEntry\": \"2021-11-22T10:17:56.2866667\"\n" + " }\n" + " ]\n"
+ + " }\n" + " ]\n" + " }\n" + "]";
+ JsonArray array = gson.fromJson(arrayString, JsonArray.class);
+ JsonObject object = SensorThingHandler.getLatestValueFromJsonArray(array);
+ String string = object != null ? object.toString() : null;
+ assertEquals("{\"EventDef\":\"Alarm\",\"DateEntry\":\"2021-11-22T10:17:56.2866667\"}", string);
+ arrayString = "[\n" + " {\n" + " \"IdPatient\": 1,\n" + " \"LastName\": \"Mustermann\",\n"
+ + " \"FirstName\": \"Max\",\n" + " \"Devices\": [\n" + " {\n" + " \"IdDevice\": 2,\n"
+ + " \"SerialNumber\": \"001\",\n" + " \"Name\": \"Test Sitzkissen\"\n" + " }\n"
+ + " ]\n" + " }\n" + "]";
+ array = gson.fromJson(arrayString, JsonArray.class);
+ assertNull(SensorThingHandler.getLatestValueFromJsonArray(array));
+ }
+
+ @Test
+ public void dateFormatTest2() {
+ try {
+ Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse("2021-11-22T14:00:09.9933333");
+ String dateString = new SimpleDateFormat("yyyy-MM-dd', 'HH:mm:ss").format(date);
+ assertEquals("2021-11-22, 14:00:09", dateString);
+ } catch (Exception e) {
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.mcd/src/test/java/org/openhab/binding/mcd/internal/util/SensorEventDefTest.java b/bundles/org.openhab.binding.mcd/src/test/java/org/openhab/binding/mcd/internal/util/SensorEventDefTest.java
new file mode 100644
index 00000000000..6ee65b0fd63
--- /dev/null
+++ b/bundles/org.openhab.binding.mcd/src/test/java/org/openhab/binding/mcd/internal/util/SensorEventDefTest.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mcd.internal.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author Dengler - Initial contribution
+ */
+@NonNullByDefault
+public class SensorEventDefTest {
+ @Test
+ public void testGetSensorEventDefinition() {
+ assertEquals(-1, SensorEventDef.getSensorEventId("banane"));
+ assertEquals(0, SensorEventDef.getSensorEventId(""));
+ assertEquals(2, SensorEventDef.getSensorEventId("BEDEXIT"));
+ }
+}
diff --git a/bundles/pom.xml b/bundles/pom.xml
index fdd49233b87..21155cccccd 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -211,6 +211,7 @@
org.openhab.binding.magentatv
org.openhab.binding.mail
org.openhab.binding.max
+ org.openhab.binding.mcd
org.openhab.binding.mcp23017
org.openhab.binding.mecmeter
org.openhab.binding.melcloud