diff --git a/CODEOWNERS b/CODEOWNERS
index 2569b21c93306..e129d743c8bf3 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -218,6 +218,7 @@
/bundles/org.openhab.binding.vektiva/ @octa22
/bundles/org.openhab.binding.velbus/ @cedricboon
/bundles/org.openhab.binding.velux/ @gs4711
+/bundles/org.openhab.binding.verisure/ @jannegpriv
/bundles/org.openhab.binding.vigicrues/ @clinique
/bundles/org.openhab.binding.vitotronic/ @steand
/bundles/org.openhab.binding.volvooncall/ @clinique
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 82ffd78cf1fff..c210a5b03f7a5 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -1089,6 +1089,11 @@
org.openhab.binding.velux
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.verisure
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.vigicrues
diff --git a/bundles/org.openhab.binding.verisure/.classpath b/bundles/org.openhab.binding.verisure/.classpath
new file mode 100644
index 0000000000000..615608997a6c5
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/.classpath
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.verisure/.project b/bundles/org.openhab.binding.verisure/.project
new file mode 100644
index 0000000000000..10bc8e87a0831
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.binding.verisure
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/bundles/org.openhab.binding.verisure/NOTICE b/bundles/org.openhab.binding.verisure/NOTICE
new file mode 100644
index 0000000000000..0c13fa7419c02
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/NOTICE
@@ -0,0 +1,20 @@
+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/openhab2-addons
+
+== Third-party Content
+
+jsoup
+* License: MIT License
+* Project: https://jsoup.org/
+* Source: https://github.com/jhy/jsoup
diff --git a/bundles/org.openhab.binding.verisure/README.md b/bundles/org.openhab.binding.verisure/README.md
new file mode 100644
index 0000000000000..646264cd14236
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/README.md
@@ -0,0 +1,585 @@
+# Verisure Binding
+
+This is an openHAB binding for Verisure Alarm System, by Securitas Direct.
+
+This binding uses the rest API behind the Verisure My Pages:
+
+https://mypages.verisure.com/login.html.
+
+Be aware that Verisure don't approve if you update to often, I have gotten no complaints running with a 10 minutes update interval, but officially you should use 30 minutes.
+
+
+## Supported Things
+
+This binding supports the following thing types:
+
+- Bridge
+- Alarm
+- Smoke Detector (climate)
+- Water Detector (climate)
+- Siren (climate)
+- Night Control
+- Yaleman SmartLock
+- SmartPlug
+- Door/Window Status
+- User Presence Status
+- Broadband Connection Status
+- Mice Detection Status (incl. climate)
+- Event Log
+- Gateway
+
+
+## Binding Configuration
+
+You will have to configure the bridge with username and password, these must be the same credentials as used when logging into https://mypages.verisure.com.
+
+You must also configure your pin-code(s) to be able to lock/unlock the SmartLock(s) and arm/unarm the Alarm(s).
+
+**NOTE:** To be able to have full control over all SmartLock functionality, the user has to have Administrator rights.
+
+## Discovery
+
+After the configuration of the Verisure Bridge all of the available Sensors, Alarms, SmartPlugs, SmartLocks, Climate and Mice Detection devices will be discovered and placed as things in the inbox.
+
+## Thing Configuration
+
+Only the bridge require manual configuration. The devices and sensors can be added by hand, or you can let the discovery mechanism automatically find all of your Verisure things.
+
+## Enable Debugging
+
+To enable DEBUG logging for the binding, login to Karaf console and enter:
+
+`openhab> log:set DEBUG org.openhab.binding.verisure`
+
+## Supported Things and Channels
+
+### Verisure Bridge
+
+#### Configuration Options
+
+* `username` - The username used to connect to http://mypage.verisure.com
+ * The user has to have Administrator rights to have full SmartLock functionality
+
+* `password` - The password used to connect to http://mypage.verisure.com
+
+* `refresh` - Specifies the refresh interval in seconds
+
+* `pin` - The username's pin code to arm/disarm alarm and lock/unlock door. In the case of more than one installation and different pin-codes, use a comma separated string where pin-code matches order of installations. The installation order can be found using DEBUG log settings.
+ * Two installations where the first listed installation uses a 6 digit pin-code and second listed installation uses a 4 digit pin-code: 123456,1234
+
+If you define the bridge in a things-file the bridge type id is defined as `bridge`, e.g.:
+
+`Bridge verisure:bridge:myverisureBridge verisure:bridge:myverisure`
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------------------------------------------------------------------------------------------|
+| status | String | This channel can be used to trigger an instant refresh by sending a RefreshType.REFRESH command.|
+
+
+### Verisure Alarm
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Since Alarm lacks a Verisure ID, the following naming convention is used for alarm on installation ID 123456789: 'alarm123456789'. Installation ID can be found using DEBUG log settings
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|---------------------|-----------|-------------------------------------------------------------------------------------------|
+| changedByUser | String | This channel reports the user that last changed the state of the alarm. |
+| changedVia | String | This channel reports the method used to change the status. |
+| timestamp | DateTime | This channel reports the last time the alarm status was changed. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| alarmStatus | String | This channel is used to arm/disarm the alarm. Available alarm status are "DISARMED", "ARMED_HOME" and "ARMED_AWAY".|
+| alarmTriggerChannel | trigger | This is a trigger channel that receives events.|
+
+### Verisure Yaleman SmartLock
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 5A4C35FT (Note: Verisure ID, found in the Verisure App or My Pages)
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|-------------------------|-----------|----------------------------------------------------------------------------------------------------------|
+| changedByUser | String | This channel reports the user that last changed the state of the alarm. |
+| timestamp | DateTime | This channel reports the last time the alarm status was changed. |
+| changedVia | String | This channel reports the method used to change the status. |
+| motorJam | Switch | This channel reports if the SmartLock motor has jammed. |
+| location | String | This channel reports the location of the device. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| smartLockStatus | Switch | This channel is used to lock/unlock. |
+| autoRelock | Switch | This channel is used to configure auto-lock functionality. Only supported for users with Administrator rights. |
+| smartLockVolume | String | This channel is used to set the volume level. Available volume settings are "SILENCE", "LOW" and "HIGH". Only supported for users with Administrator rights.|
+| smartLockVoiceLevel | String | This channel is used to set the voice level. Available voice level settings are "ESSENTIAL" and "NORMAL". Only supported for users with Administrator rights.|
+| smartLockTriggerChannel | trigger | This is a trigger channel that receives events. |
+
+### Verisure SmartPlug
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 5A4C35FT (Note: Verisure ID, found in the Verisure App or My Pages or on the sensor itself)
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|-------------------------|-----------|-------------------------------------------------------------------|
+| hazardous | Switch | This channel reports if the smart plug is configured as hazardous.|
+| location | String | This channel reports the location of the device. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| smartPlugStatus | Switch | This channel is used to turn smart plug on/off. |
+| smartPlugTriggerChannel | trigger | This is a trigger channel that receives events. |
+
+### Verisure Smoke Detector
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 5A4C35FT (Note: Verisure ID, found in the Verisure App or on the sensor itself)
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|-----------------------------|-----------------------|-----------------------------------------------------------------------------|
+| temperature | Number:Temperature | This channel reports the current temperature. |
+| humidity | Number | This channel reports the current humidity in percentage. |
+| humidityEnabled | Switch | This channel reports if the Climate is device capable of reporting humidity.|
+| timestamp | DateTime | This channel reports the last time this sensor was updated. |
+| location | String | This channel reports the location of the device. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| smokeDetectorTriggerChannel | trigger | This is a trigger channel that receives events.|
+
+### Verisure Water Detector
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 5A4C35FT (Note: Verisure ID, found in the Verisure App or My Pages or on the sensor itself)
+
+#### Channels
+
+The following channels are supported:
+
+
+| Channel Type ID | Item Type | Description |
+|-----------------------------|-----------------------|--------------------------------------------------------------|
+| temperature | Number:Temperature | This channel reports the current temperature. |
+| timestamp | DateTime | This channel reports the last time this sensor was updated. |
+| location | String | This channel reports the location of the device. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| waterDetectorTriggerChannel | trigger | This is a trigger channel that receives events. |
+
+
+### Verisure Siren
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 5A4C35FT (Note: Verisure ID, found in the Verisure App or My Pages or on the sensor itself)
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|---------------------|-----------------------|------------------------------------------------------------|
+| temperature | Number:Temperature | This channel reports the current temperature. |
+| timestamp | DateTime | This channel reports the last time this sensor was updated.|
+| location | String | This channel reports the location. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| sirenTriggerChannel | trigger | This is a trigger channel that receives events. |
+
+### Verisure Night Control
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 5A4C35FT (Note: Verisure ID, found in the Verisure App or My Pages or on the sensor itself)
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|----------------------------|-----------------------|------------------------------------------------------------|
+| temperature | Number:Temperature | This channel reports the current temperature. |
+| timestamp | DateTime | This channel reports the last time this sensor was updated.|
+| location | String | This channel reports the location. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| nightControlTriggerChannel | trigger | This is a trigger channel that receives events. |
+
+### Verisure DoorWindow Sensor
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 5A4C35FT (Note: Verisure ID, found in the Verisure App or My Pages or on the sensor itself)
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|--------------------------|-----------|-----------------------------------------------------------------------------|
+| state | Contact | This channel reports the if the door/window is open or closed (OPEN/CLOSED).|
+| timestamp | DateTime | This channel reports the last time this sensor was updated. |
+| location | String | This channel reports the location of the device. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| doorWindowTriggerChannel | trigger | This is a trigger channel that receives events. |
+
+
+### Verisure User Presence
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Since User presence lacks a Verisure ID, it is constructed from the user's email address, where the '@' sign is removed, and the site id. The following naming convention is used for User presence on site id 123456789 for a user with email address test@gmail.com: 'uptestgmailcom123456789'. Installation ID can be found using DEBUG log settings.
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|--------------------|-----------|-------------------------------------------------------------------------|
+| userLocationStatus | String | This channel reports the user presence status (HOME/AWAY). |
+| timestamp | DateTime | This channel reports the last time the User Presence status was changed.|
+| userName | String | This channel reports the user's name. |
+| webAccount | String | This channel reports the user's email address. |
+| userDeviceName | String | This channel reports the name of the user device. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+
+### Verisure Broadband Connection
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Since Broadband connection lacks a Verisure ID, the following naming convention is used for Broadband connection on site id 123456789: 'bc123456789'. Installation ID can be found using DEBUG log settings.
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|--------------------------------------------------------------------------------|
+| connected | String | This channel reports the broadband connection status (true means connected). |
+| timestamp | DateTime | This channel reports the last time the Broadband connection status was checked.|
+| installationName| String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+
+### Verisure Mice Detection
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 5A4C35FT (Note: Verisure ID, found in the Verisure App or My Pages or on the sensor itself)
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|-----------------------------|--------------------|-------------------------------------------------------------------------------------|
+| countLatestDetection | Number | This channel reports the number of mice counts the latest detection during last 24. |
+| countLast24Hours | Number | This channel reports the total number of mice counts the last 24h. |
+| durationLatestDetection | Number:Time | This channel reports the detection duration in min of latest detection. |
+| durationLast24Hours | Number:Time | This channel reports the total detection duration in min for the last 24 hours. |
+| timestamp | DateTime | This channel reports time for the last mouse detection. |
+| temperature | Number:Temperature | This channel reports the current temperature. |
+| temperatureTimestamp | DateTime | This channel reports the time for the last temperature reading. |
+| location | String | This channel reports the location of the device. |
+| installationName | String | This channel reports the installation name. |
+| installationId | Number | This channel reports the installation ID. |
+| miceDetectionTriggerChannel | trigger | This is a trigger channel that receives events. |
+
+### Verisure Event Log
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Since Event Log lacks a Verisure ID, the following naming convention is used for Event Log on site id 123456789: 'el123456789'. Installation ID can be found using DEBUG log settings.
+
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|---------------------|-----------|-------------------------------------------------------------------------|
+| lastEventLocation | String | This channel reports location for last event in event log. |
+| lastEventDeviceId | String | This channel reports device ID for last event in event log. |
+| lastEventDeviceType | String | This channel reports device type for last event in event log. |
+| lastEventType | String | This channel reports type for last event in event log. |
+| lastEventCategory | String | This channel reports category for last event in event log. |
+| lastEventTime | DateTime | This channel reports time for last event in event log. |
+| lastEventUserName | String | This channel reports user name for last event in event log. |
+| eventLog | String | This channel reports the last 15 events from event log in a JSON array. |
+
+### Verisure Gateway
+
+#### Configuration Options
+
+* `deviceId` - Device Id
+ * Sensor Id. Example 3B4C35FT (Note: Verisure ID, found in the Verisure App or My Pages or on the Gateway itself)
+
+#### Channels
+
+The following channels are supported:
+
+| Channel Type ID | Item Type | Description |
+|---------------------|-----------|----------------------------------------------------------------------|
+| model | String | This channel reports gateway model. |
+| location | String | This channel reports gateway location. |
+| statusGSMOverUDP | String | This channel reports communication status for GSM over UDP. |
+| testTimeGSMOverUDP | DateTime | This channel reports last communication test time for GSM over UDP. |
+| statusGSMOverSMS | String | This channel reports communication status for GSM over SMS. |
+| testTimeGSMOverSMS | DateTime | This channel reports last communication test time for GSM over SMS. |
+| statusGPRSOverUDP | String | This channel reports communication status for GPRS over UDP. |
+| testTimeGPRSOverUDP | DateTime | This channel reports last communication test time for GPRS over UDP. |
+| statusETHOverUDP | String | This channel reports communication status for ETH over UDP. |
+| testTimeETHOverUDP | DateTime | This channel reports last communication test time for ETH over UDP. |
+
+## Trigger Events
+
+To be able to get trigger events you need an active Event Log thing, you can either get it via auto-detection or create your own in a things-file.
+The following trigger events are defined per thing type:
+
+| Event | Thing Type | Description |
+|-------------------|---------------|------------------------------------------------------------|
+| LOCK | SmartLock | SmartLock has been locked. |
+| UNLOCK | SmartLock | SmartLock has been locked. |
+| LOCK_FAILURE | SmartLock | SmartLock has failed to lock/unlock. |
+| ARM | Alarm | Alarm has been armed. |
+| DISARM | Alarm | Alarm has been disarmed. |
+| DOORWINDOW_OPENED | DoorWindow | DoorWindow has detected a door/window that opened. |
+| DOORWINDOW_CLOSED | DoorWindow | DoorWindow has detected a door/window that closed. |
+| INTRUSION | DoorWindow | DoorWindow has detected an intrusion. |
+| FIRE | SmokeDetector | SmokeDetector has detected fire/smoke. |
+| WATER | WaterDetector | WaterDetector has detected a water leak. |
+| MICE | MiceDetector | WaterMiceDetector has detected a mouse. |
+| COM_FAILURE | All | Communication failure detected. |
+| COM_RESTORED | All | Communication restored. |
+| COM_TEST | All | Communication test. |
+| BATTERY_LOW | All | Battery low level detected. |
+| BATTERY_RESTORED | All | Battery level restored. |
+| SABOTAGE_ALARM | All | Sabotage alarm detected. |
+| SABOTAGE_RESTORED | All | Sabotage alarm restored. |
+
+## Example
+
+### Things-file
+
+````
+// Bridge configuration
+Bridge verisure:bridge:myverisure "Verisure Bridge" [username="x@y.com", password="1234", refresh="600", pin="111111"] {
+
+ Thing alarm JannesAlarm "Verisure Alarm" [ deviceId="alarm123456789" ]
+ Thing smartLock JannesSmartLock "Verisure Entrance Yale Doorman" [ deviceId="3C446NPO" ]
+ Thing smartPlug JannesSmartPlug "Verisure SmartPlug" [ deviceId="3D7GMANV" ]
+ Thing waterDetector JannesWaterDetector "Verisure Water Detector" [ deviceId="3WETQRH5" ]
+ Thing userPresence JannesUserPresence "Verisure User Presence" [ deviceId="uptestgmailcom123456789" ]
+ Thing eventLog JannesEventLog "Verisure Event Log" [ deviceId="el123456789" ]
+ Thing gateway JannesGateway "Verisure Gateway" [ deviceId="3AFG5673" ]
+}
+````
+
+### Items-file
+
+````
+Group gVerisureMiceDetection
+Group gVerisureEventLog
+Group gVerisureGateway
+
+// SmartLock and Alarm
+Switch SmartLock "Verisure SmartLock" [ "Switchable" ] {channel="verisure:smartLock:myverisure:JannesSmartLock:smartLockStatus"}
+Switch AutoLock "AutoLock" [ "Switchable" ] {channel="verisure:smartLock:myverisure:JannesSmartLock:autoRelock"}
+String SmartLockVolume "SmartLock Volume" {channel="verisure:smartLock:myverisure:JannesSmartLock:smartLockVolume"}
+DateTime SmartLockLastUpdated "SmartLock Last Updated [%1$tY-%1$tm-%1$td %1$tR]" {channel="verisure:smartLock:myverisure:JannesSmartLock:timestamp"}
+String AlarmHome "Alarm Home" {channel="verisure:alarm:myverisure:JannesAlarm:alarmStatus"}
+DateTime AlarmLastUpdated "Verisure Alarm Last Updated [%1$tY-%1$tm.%1$td %1$tR]" {channel="verisure:alarm:myverisure:JannesAlarm:timestamp"}
+String AlarmChangedByUser "Verisure Alarm Changed By User" {channel="verisure:alarm:myverisure:JannesAlarm:changedByUser"}
+
+
+// SmartPlugs
+Switch SmartPlugLamp "SmartPlug" [ "Switchable" ] {channel="verisure:smartPlug:myverisure:4ED5ZXYC:smartPlugStatus"}
+Switch SmartPlugGlavaRouter "SmartPlug Glava Router" [ "Switchable" ] {channel="verisure:smartPlug:myverisure:JannesSmartPlug:smartPlugStatus"}
+
+// DoorWindow
+String DoorWindowLocation "Door Window Location" {channel="verisure:doorWindowSensor:myverisure:1SG5GHGT:location"}
+String DoorWindowStatus "Door Window Status" {channel="verisure:doorWindowSensor:myverisure:1SG5GHGT:state"}
+
+// UserPresence
+String UserName "User Name" {channel="verisure:userPresence:myverisure:JannesUserPresence:userName"}
+String UserLocationEmail "User Location Email" {channel="verisure:userPresence:myverisure:JannesUserPresence:webAccount"}
+String UserLocationName "User Location Name" {channel="verisure:userPresence:myverisure:JannesUserPresence:userLocationStatus"}
+String UserNameGlava "User Name Glava" {channel="verisure:userPresence:myverisure:userpresencetestgmailcom123456789:userName"}
+String UserLocationEmailGlava "User Location Email Glava" {channel="verisure:userPresence:myverisure:userpresencetestgmailcom123456789:webAccount"}
+String UserLocationNameGlava "User Location Name Glava" {channel="verisure:userPresence:myverisure:userpresencetestgmailcom1123456789:userLocationStatus"}
+
+// EventLog
+String LastEventLocation "Last Event Location" (gVerisureEventLog) {channel="verisure:eventLog:myverisure:JannesEventLog:lastEventLocation"}
+String LastEventDeviceId "Last Event Device ID" (gVerisureEventLog) {channel="verisure:eventLog:myverisure:JannesEventLog:lastEventDeviceId"}
+String LastEventDeviceType "Last Event Device Type" (gVerisureEventLog) {channel="verisure:eventLog:myverisure:JannesEventLog:lastEventDeviceType"}
+String LastEventType "Last Event Type" (gVerisureEventLog) {channel="verisure:eventLog:myverisure:JannesEventLog:lastEventType"}
+String LastEventCategory "Last Event Category" (gVerisureEventLog) {channel="verisure:eventLog:myverisure:JannesEventLog:lastEventCategory"}
+DateTime LastEventTime "Last Event Time [%1$tY-%1$tm-%1$td %1$tR]" (gVerisureEventLog) {channel="verisure:eventLog:myverisure:JannesEventLog:lastEventTime"}
+String LastEventUserName "Last Event User Name" (gVerisureEventLog) {channel="verisure:eventLog:myverisure:JannesEventLog:lastEventUserName"}
+String EventLog "Event Log" {channel="verisure:eventLog:myverisure:JannesEventLog:eventLog"}
+
+// Gateway
+String VerisureGatewayModel "Gateway Model" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:model"}
+String VerisureGatewayLocation "Gateway Location" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:location"}
+String VerisureGWStatusGSMOverUDP "Gateway Status GSMOverUDP" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:statusGSMOverUDP"}
+DateTime VerisureGWTestTimeGSMOverUDP "Gateway Test Time GSMOverUDP" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:testTimeGSMOverUDP"}
+String VerisureGWStatusGSMOverSMS "Gateway Status GSMOverSMS" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:statusGSMOverSMS"}
+DateTime VerisureGWTestTimeGSMOverSMS "Gateway Test Time GSMOverSMS" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:testTimeGSMOverSMS"}
+String VerisureGWStatusGPRSOverUDP "Gateway Status GPRSOverUDP" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:statusGPRSOverUDP"}
+DateTime VerisureGWTestTimeGPRSOverUDP "Gateway Test Time GPRSOverUDP" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:testTimeGPRSOverUDP"}
+String VerisureGWStatusETHOverUDP "Gateway Status ETHOverUDP" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:statusETHOverUDP"}
+DateTime VerisureGWTestTimeETHOverUDP "Gateway Test Time ETHOverUDP" (gVerisureGateway) {channel="verisure:gateway:myverisure:JannesGateway:testTimeETHOverUDP"}
+
+// Broadband Connection
+String CurrentBBStatus "Broadband Connection Status" {channel="verisure:broadbandConnection:myverisure:bc123456789:connected"}
+
+// Verisure Mice Detection
+Number MouseCountLastDetection "Mouse Count Last Detection" (gVerisureMiceDetection) {channel="verisure:miceDetection:myverisure:2CFZH80U:countLatestDetection"}
+Number MouseCountLast24Hours "Mouse Count Last 24 Hours" (gVerisureMiceDetection) {channel="verisure:miceDetection:myverisure:2CFZH80U:countLast24Hours"}
+DateTime MouseLastDetectionTime "Mouse Last Detection Time [%1$tY-%1$tm-%1$td %1$tR]" (gVerisureMiceDetection) {channel="verisure:miceDetection:myverisure:2CFZH80U:timestamp"}
+Number MouseDurationLastDetection "Mouse Duration Last Detection" (gVerisureMiceDetection) {channel="verisure:miceDetection:myverisure:2CFZH80U:durationLatestDetection"}
+Number MouseDurationLast24Hours "Mouse Duration Last 24 Hours" (gVerisureMiceDetection) {channel="verisure:miceDetection:myverisure:2CFZH80U:durationLast24Hours"}
+Number MouseDetectionTemperature "Mouse Detection Temperature [%.1f C]" (gTemperaturesVerisure, gVerisureMiceDetection) ["CurrentTemperature"] {channel="verisure:miceDetection:myverisure:2CFZH80U:temperature"}
+DateTime MouseDetectionTemperatureTime "Mouse Detection Temperature Time [%1$tY-%1$tm-%1$td %1$tR]" (gVerisureMiceDetection) {channel="verisure:miceDetection:myverisure:2CFZH80U:temperatureTimestamp"}
+String MouseDetectionLocation "Mouse Detection Location" (gVerisureMiceDetection) {channel="verisure:miceDetection:myverisure:2CFZH80U:location"}
+
+````
+
+### Sitemap
+
+````
+ Frame label="SmartLock and Alarm" {
+ Text label="SmartLock and Alarm" icon="groundfloor" {
+ Frame label="Yale Doorman SmartLock" {
+ Switch item=SmartLock label="Yale Doorman SmartLock" icon="lock.png"
+ }
+ Frame label="Verisure Alarm" {
+ Switch item=AlarmHome icon="alarm" label="Verisure Alarm" mappings=["DISARMED"="Disarm", "ARMED_HOME"="Arm Home", "ARMED_AWAY"="Arm Away"]
+ }
+ Frame label="Yale Doorman SmartLock AutoLock" {
+ Switch item=AutoLock label="Yale Doorman SmartLock AutoLock" icon="lock.png"
+ }
+ Frame label="Yale Doorman SmartLock Volume" {
+ Switch item=SmartLockVolume icon="lock" label="Yale Doorman SmartLock Volume" mappings=["SILENCE"="Silence", "LOW"="Low", "HIGH"="High"]
+ }
+ Text item=AlarmHomeInstallationName label="Alarm Installation [%s]"
+ Text item=AlarmChangedByUser label="Changed by user [%s]"
+ Text item=AlarmLastUpdated
+ Text item=SmartLockStatus label="SmartLock status [%s]"
+ Text item=SmartLockLastUpdated
+ Text item=SmartLockOperatedBy label="Changed by user [%s]"
+ Text item=DoorWindowStatus label="Door State"
+ Text item=DoorWindowLocation
+ }
+ }
+
+ Frame label="SmartPlugs" {
+ Text label="SmartPlugs" icon="attic" {
+ Frame label="SmartPlug Lamp" {
+ Switch item=SmartPlugLamp label="Verisure SmartPlug Lamp" icon="smartheater.png"
+ }
+ }
+ }
+
+ Frame label="User Presence" {
+ Text label="User Presence" icon="attic" {
+ Frame label="User Presence Champinjonvägen" {
+ Text item=UserName label="User Name [%s]"
+ Text item=UserLocationEmail label="User Email [%s]"
+ Text item=UserLocationStatus label="User Location Status [%s]"
+ }
+ }
+ }
+
+ Frame label="Broadband Connection" {
+ Text label="Broadband Connection" icon="attic" {
+ Frame label="Broadband Connection Champinjonvägen" {
+ Text item=CurrentBBStatus label="Broadband Connection Status [%s]"
+ }
+ }
+ }
+
+ Frame label="Mice Detection" {
+ Group item=gVerisureMiceDetection label="Verisure Mice Detection"
+ }
+
+ Frame label="Event Log" {
+ Group item=gVerisureEventLog label="Verisure Event Log"
+ }
+
+ Frame label="Gateway" {
+ Group item=gVerisureGateway label="Verisure Gateway"
+ }
+
+````
+
+### Rules
+
+````
+import org.eclipse.smarthome.core.types.RefreshType
+
+rule "Handle Refesh of Verisure"
+when
+ Item RefreshVerisure received command
+then
+ var String command = RefreshVerisure.state.toString.toLowerCase
+ logDebug("RULES","RefreshVerisure Rule command: " + command)
+ sendCommand(VerisureBridgeStatus, RefreshType.REFRESH)
+end
+
+rule "Verisure SmartLock Event Triggers"
+when
+ Channel "verisure:smartLock:myverisure:JannesSmartLock:smartLockTriggerChannel" triggered
+then
+ logInfo("RULES", "A SmartLock trigger event was detected:" + receivedEvent.toString())
+end
+
+rule "Verisure Gateway Event Triggers"
+when
+ Channel "verisure:gateway:myverisure:JannesGateway:gatewayTriggerChannel" triggered
+then
+ logInfo("RULES", "A Gateway trigger event was detected:" + receivedEvent.toString())
+end
+
+rule "Verisure DoorWindow Event Triggers"
+when
+ Channel "verisure:doorWindowSensor:myverisure:1SG5GHGT:doorWindowTriggerChannel" triggered
+then
+ logInfo("RULES", "A DoorWindow trigger event was detected:" + receivedEvent.toString())
+end
+
+
+````
+
diff --git a/bundles/org.openhab.binding.verisure/pom.xml b/bundles/org.openhab.binding.verisure/pom.xml
new file mode 100644
index 0000000000000..f20a67e2624b9
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 2.5.7-SNAPSHOT
+
+
+ org.openhab.binding.verisure
+
+ openHAB Add-ons :: Bundles :: Verisure Binding
+
+
+
+ org.jsoup
+ jsoup
+ 1.8.3
+ compile
+
+
+
+
diff --git a/bundles/org.openhab.binding.verisure/src/main/feature/feature.xml b/bundles/org.openhab.binding.verisure/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..64a788f07f1d5
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/feature/feature.xml
@@ -0,0 +1,10 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+ openhab-runtime-base
+ openhab-transport-serial
+ mvn:org.jsoup/jsoup/1.8.3
+ mvn:org.openhab.addons.bundles/org.openhab.binding.verisure/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/DeviceStatusListener.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/DeviceStatusListener.java
new file mode 100644
index 0000000000000..fe8e55a718f17
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/DeviceStatusListener.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2020 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.verisure.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
+
+/**
+ * The {@link DeviceStatusListener} is notified when a device status has changed
+ * or a device has been removed or added.
+ *
+ * @author Jarle Hjortland - Initial contribution
+ * @author Jan Gustafsson - Updated after code review comments
+ *
+ */
+@NonNullByDefault
+public interface DeviceStatusListener {
+
+ /**
+ * This method is called whenever the state of the given device has changed.
+ *
+ * @param thing
+ * The thing that was changed.
+ */
+ void onDeviceStateChanged(T thing);
+
+ /**
+ * This method returns the thing's class
+ */
+ public Class getVerisureThingClass();
+}
diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBindingConstants.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBindingConstants.java
new file mode 100644
index 0000000000000..d4b10d5c9157f
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBindingConstants.java
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2010-2020 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.verisure.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+
+/**
+ * The {@link VerisureBinding} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author l3rum - Initial contribution
+ * @author Jan Gustafsson - Furher development
+ */
+@NonNullByDefault
+public class VerisureBindingConstants {
+
+ public static final String BINDING_ID = "verisure";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
+ public static final ThingTypeUID THING_TYPE_ALARM = new ThingTypeUID(BINDING_ID, "alarm");
+ public static final ThingTypeUID THING_TYPE_SMARTPLUG = new ThingTypeUID(BINDING_ID, "smartPlug");
+ public static final ThingTypeUID THING_TYPE_SMOKEDETECTOR = new ThingTypeUID(BINDING_ID, "smokeDetector");
+ public static final ThingTypeUID THING_TYPE_WATERDETECTOR = new ThingTypeUID(BINDING_ID, "waterDetector");
+ public static final ThingTypeUID THING_TYPE_SIREN = new ThingTypeUID(BINDING_ID, "siren");
+ public static final ThingTypeUID THING_TYPE_DOORWINDOW = new ThingTypeUID(BINDING_ID, "doorWindowSensor");
+ public static final ThingTypeUID THING_TYPE_USERPRESENCE = new ThingTypeUID(BINDING_ID, "userPresence");
+ public static final ThingTypeUID THING_TYPE_SMARTLOCK = new ThingTypeUID(BINDING_ID, "smartLock");
+ public static final ThingTypeUID THING_TYPE_BROADBAND_CONNECTION = new ThingTypeUID(BINDING_ID,
+ "broadbandConnection");
+ public static final ThingTypeUID THING_TYPE_NIGHT_CONTROL = new ThingTypeUID(BINDING_ID, "nightControl");
+ public static final ThingTypeUID THING_TYPE_MICE_DETECTION = new ThingTypeUID(BINDING_ID, "miceDetection");
+ public static final ThingTypeUID THING_TYPE_EVENT_LOG = new ThingTypeUID(BINDING_ID, "eventLog");
+ public static final ThingTypeUID THING_TYPE_GATEWAY = new ThingTypeUID(BINDING_ID, "gateway");
+
+ // List of all Channel ids
+ public static final String CHANNEL_NUMERIC_STATUS = "numericStatus";
+ public static final String CHANNEL_TEMPERATURE = "temperature";
+ public static final String CHANNEL_HUMIDITY = "humidity";
+ public static final String CHANNEL_HUMIDITY_ENABLED = "humidityEnabled";
+ public static final String CHANNEL_LOCATION = "location";
+ public static final String CHANNEL_STATUS = "status";
+ public static final String CHANNEL_CONNECTED = "connected";
+ public static final String CHANNEL_STATE = "state";
+ public static final String CHANNEL_LABEL = "label";
+ public static final String CHANNEL_USER_NAME = "userName";
+ public static final String CHANNEL_WEBACCOUNT = "webAccount";
+ public static final String CHANNEL_USER_LOCATION_STATUS = "userLocationStatus";
+ public static final String CHANNEL_USER_DEVICE_NAME = "userDeviceName";
+ public static final String CHANNEL_SMARTLOCK_VOLUME = "smartLockVolume";
+ public static final String CHANNEL_SMARTLOCK_VOICE_LEVEL = "smartLockVoiceLevel";
+ public static final String CHANNEL_SMARTLOCK_TRIGGER_CHANNEL = "smartLockTriggerChannel";
+ public static final String CHANNEL_AUTO_RELOCK = "autoRelock";
+ public static final String CHANNEL_SMARTPLUG_STATUS = "smartPlugStatus";
+ public static final String CHANNEL_SMARTPLUG_TRIGGER_CHANNEL = "smartPlugTriggerChannel";
+ public static final String CHANNEL_ALARM_STATUS = "alarmStatus";
+ public static final String CHANNEL_ALARM_TRIGGER_CHANNEL = "alarmTriggerChannel";
+ public static final String CHANNEL_SMARTLOCK_STATUS = "smartLockStatus";
+ public static final String CHANNEL_CHANGED_BY_USER = "changedByUser";
+ public static final String CHANNEL_CHANGED_VIA = "changedVia";
+ public static final String CHANNEL_TIMESTAMP = "timestamp";
+ public static final String CHANNEL_TEMPERATURE_TIMESTAMP = "temperatureTimestamp";
+ public static final String CHANNEL_HAZARDOUS = "hazardous";
+ public static final String CHANNEL_MOTOR_JAM = "motorJam";
+ public static final String CHANNEL_INSTALLATION_NAME = "installationName";
+ public static final String CHANNEL_INSTALLATION_ID = "installationId";
+ public static final String CHANNEL_COUNT_LATEST_DETECTION = "countLatestDetection";
+ public static final String CHANNEL_COUNT_LAST_24_HOURS = "countLast24Hours";
+ public static final String CHANNEL_DURATION_LATEST_DETECTION = "durationLatestDetection";
+ public static final String CHANNEL_DURATION_LAST_24_HOURS = "durationLast24Hours";
+ public static final String CHANNEL_LAST_EVENT_LOCATION = "lastEventLocation";
+ public static final String CHANNEL_LAST_EVENT_ID = "lastEventId";
+ public static final String CHANNEL_LAST_EVENT_DEVICE_ID = "lastEventDeviceId";
+ public static final String CHANNEL_LAST_EVENT_DEVICE_TYPE = "lastEventDeviceType";
+ public static final String CHANNEL_LAST_EVENT_TYPE = "lastEventType";
+ public static final String CHANNEL_LAST_EVENT_CATEGORY = "lastEventCategory";
+ public static final String CHANNEL_LAST_EVENT_TIME = "lastEventTime";
+ public static final String CHANNEL_LAST_EVENT_USER_NAME = "lastEventUserName";
+ public static final String CHANNEL_EVENT_LOG = "eventLog";
+ public static final String CHANNEL_STATUS_GSM_OVER_UDP = "statusGSMOverUDP";
+ public static final String CHANNEL_STATUS_GSM_OVER_SMS = "statusGSMOverSMS";
+ public static final String CHANNEL_STATUS_GPRS_OVER_UDP = "statusGPRSOverUDP";
+ public static final String CHANNEL_STATUS_ETH_OVER_UDP = "statusETHOverUDP";
+ public static final String CHANNEL_TEST_TIME_GSM_OVER_UDP = "testTimeGSMOverUDP";
+ public static final String CHANNEL_TEST_TIME_GSM_OVER_SMS = "testTimeGSMOverSMS";
+ public static final String CHANNEL_TEST_TIME_GPRS_OVER_UDP = "testTimeGPRSOverUDP";
+ public static final String CHANNEL_TEST_TIME_ETH_OVER_UDP = "testTimeETHOverUDP";
+ public static final String CHANNEL_GATEWAY_MODEL = "model";
+ public static final String CHANNEL_SMOKE_DETECTION_TRIGGER_CHANNEL = "smokeDetectionTriggerChannel";
+ public static final String CHANNEL_MICE_DETECTION_TRIGGER_CHANNEL = "miceDetectionTriggerChannel";
+ public static final String CHANNEL_WATER_DETECTION_TRIGGER_CHANNEL = "waterDetectionTriggerChannel";
+ public static final String CHANNEL_SIREN_TRIGGER_CHANNEL = "sirenTriggerChannel";
+ public static final String CHANNEL_NIGHT_CONTROL_TRIGGER_CHANNEL = "nightControlTriggerChannel";
+ public static final String CHANNEL_DOOR_WINDOW_TRIGGER_CHANNEL = "doorWindowTriggerChannel";
+ public static final String CHANNEL_GATEWAY_TRIGGER_CHANNEL = "gatewayTriggerChannel";
+
+ // Trigger channel events
+ public static final String TRIGGER_EVENT_LOCK = "LOCK";
+ public static final String TRIGGER_EVENT_UNLOCK = "UNLOCK";
+ public static final String TRIGGER_EVENT_LOCK_FAILURE = "LOCK_FAILURE";
+ public static final String TRIGGER_EVENT_ARM = "ARM";
+ public static final String TRIGGER_EVENT_DISARM = "DISARM";
+ public static final String TRIGGER_EVENT_FIRE = "FIRE";
+ public static final String TRIGGER_EVENT_INSTRUSION = "INTRUSION";
+ public static final String TRIGGER_EVENT_WATER = "WATER";
+ public static final String TRIGGER_EVENT_MICE = "MICE";
+ public static final String TRIGGER_EVENT_BATTERY_LOW = "BATTERY_LOW";
+ public static final String TRIGGER_EVENT_BATTERY_RESTORED = "BATTERY_RESTORED";
+ public static final String TRIGGER_EVENT_COM_FAILURE = "COM_FAILURE";
+ public static final String TRIGGER_EVENT_COM_RESTORED = "COM_RESTORED";
+ public static final String TRIGGER_EVENT_COM_TEST = "COM_TEST";
+ public static final String TRIGGER_EVENT_SABOTAGE_ALARM = "SABOTAGE_ALARM";
+ public static final String TRIGGER_EVENT_SABOTAGE_RESTORED = "SABOTAGE_RESTORED";
+ public static final String TRIGGER_EVENT_DOORWINDOW_OPENED = "DOORWINDOW_OPENED";
+ public static final String TRIGGER_EVENT_DOORWINDOW_CLOSED = "DOORWINDOW_CLOSED";
+ public static final String TRIGGER_EVENT_LOCATION_HOME = "LOCATION_HOME";
+ public static final String TRIGGER_EVENT_LOCATION_AWAY = "LOCATION_AWAY";
+
+ // REST URI constants
+ public static final String USERNAME = "username";
+ public static final String PASSWORD = "password";
+ public static final String BASEURL = "https://mypages.verisure.com";
+ public static final String LOGON_SUF = BASEURL + "/j_spring_security_check?locale=en_GB";
+ public static final String ALARM_COMMAND = BASEURL + "/remotecontrol/armstatechange.cmd";
+ public static final String SMARTLOCK_LOCK_COMMAND = BASEURL + "/remotecontrol/lockunlock.cmd";
+ public static final String SMARTLOCK_SET_COMMAND = BASEURL + "/overview/setdoorlock.cmd";
+ public static final String SMARTLOCK_AUTORELOCK_COMMAND = BASEURL + "/settings/setautorelock.cmd";
+ public static final String SMARTLOCK_VOLUME_COMMAND = BASEURL + "/settings/setvolume.cmd";
+
+ public static final String SMARTPLUG_COMMAND = BASEURL + "/settings/smartplug/onoffplug.cmd";
+ public static final String START_REDIRECT = "/uk/start.html";
+ public static final String START_SUF = BASEURL + START_REDIRECT;
+
+ // GraphQL constants
+ public static final String STATUS = BASEURL + "/uk/status";
+ public static final String SETTINGS = BASEURL + "/uk/settings.html?giid=";
+ public static final String SET_INSTALLATION = BASEURL + "/setinstallation?giid=";
+ public static final String BASEURL_API = "https://m-api02.verisure.com";
+ public static final String START_GRAPHQL = "/graphql";
+ public static final String AUTH_TOKEN = "/auth/token";
+ public static final String AUTH_LOGIN = "/auth/login";
+
+ public static final String ALARMSTATUS_PATH = "/remotecontrol";
+ public static final String SMARTLOCK_PATH = "/overview/doorlock/";
+ public static final String DOORWINDOW_PATH = "/settings/doorwindow";
+ public static final String USERTRACKING_PATH = "/overview/usertrackingcontacts";
+ public static final String CLIMATEDEVICE_PATH = "/overview/climatedevice";
+ public static final String SMARTPLUG_PATH = "/settings/smartplug";
+ public static final String ETHERNETSTATUS_PATH = "/overview/ethernetstatus";
+ public static final String VACATIONMODE_PATH = "/overview/vacationmode";
+ public static final String TEMPERATURE_CONTROL_PATH = "/overview/temperaturecontrol";
+ public static final String MOUSEDETECTION_PATH = "/overview/mousedetection";
+ public static final String CAMERA_PATH = "/overview/camera";
+}
diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBridgeConfiguration.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBridgeConfiguration.java
new file mode 100644
index 0000000000000..0c3f01481eaa5
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureBridgeConfiguration.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2020 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.verisure.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Configuration class for VerisureBridgeHandler bridge used to connect to the
+ * Verisure MyPage.
+ *
+ * @author Jarle Hjortland - Initial contribution
+ */
+@NonNullByDefault
+public class VerisureBridgeConfiguration {
+ public @Nullable String username;
+ public @Nullable String password;
+ public int refresh;
+ public @Nullable String pin;
+}
diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureHandlerFactory.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureHandlerFactory.java
new file mode 100644
index 0000000000000..0768b577fd5aa
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureHandlerFactory.java
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2010-2020 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.verisure.internal;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.smarthome.core.thing.Bridge;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
+import org.eclipse.smarthome.io.net.http.HttpClientFactory;
+import org.openhab.binding.verisure.internal.handler.VerisureAlarmThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureBridgeHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureBroadbandConnectionThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureClimateDeviceThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureDoorWindowThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureEventLogThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureGatewayThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureMiceDetectionThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureSmartLockThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureSmartPlugThingHandler;
+import org.openhab.binding.verisure.internal.handler.VerisureUserPresenceThingHandler;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link VerisureHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Jarle Hjortland - Initial contribution
+ * @author Jan Gustafsson - Further development
+ */
+@NonNullByDefault
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.verisure")
+public class VerisureHandlerFactory extends BaseThingHandlerFactory {
+
+ public static final Set SUPPORTED_THING_TYPES = new HashSet();
+ static {
+ SUPPORTED_THING_TYPES.addAll(VerisureBridgeHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureAlarmThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureSmartLockThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureSmartPlugThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureClimateDeviceThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureBroadbandConnectionThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureDoorWindowThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureUserPresenceThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureMiceDetectionThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureEventLogThingHandler.SUPPORTED_THING_TYPES);
+ SUPPORTED_THING_TYPES.addAll(VerisureGatewayThingHandler.SUPPORTED_THING_TYPES);
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(VerisureHandlerFactory.class);
+ private final HttpClient httpClient;
+
+ @Activate
+ public VerisureHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ logger.debug("createHandler this: {}", thing);
+ final ThingHandler thingHandler;
+ if (VerisureBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureBridgeHandler");
+ thingHandler = new VerisureBridgeHandler((Bridge) thing, httpClient);
+ } else if (VerisureAlarmThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureAlarmThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureAlarmThingHandler(thing);
+ } else if (VerisureSmartLockThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureSmartLockThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureSmartLockThingHandler(thing);
+ } else if (VerisureSmartPlugThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureSmartPlugThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureSmartPlugThingHandler(thing);
+ } else if (VerisureClimateDeviceThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureClimateDeviceThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureClimateDeviceThingHandler(thing);
+ } else if (VerisureBroadbandConnectionThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureBroadbandConnectionThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureBroadbandConnectionThingHandler(thing);
+ } else if (VerisureDoorWindowThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureDoorWindowThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureDoorWindowThingHandler(thing);
+ } else if (VerisureUserPresenceThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureUserPresenceThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureUserPresenceThingHandler(thing);
+ } else if (VerisureMiceDetectionThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureMiceDetectionThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureMiceDetectionThingHandler(thing);
+ } else if (VerisureEventLogThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureEventLogThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureEventLogThingHandler(thing);
+ } else if (VerisureGatewayThingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("Create VerisureGatewayThingHandler {}", thing.getThingTypeUID());
+ thingHandler = new VerisureGatewayThingHandler(thing);
+ } else {
+ logger.debug("Not possible to create thing handler for thing {}", thing);
+ thingHandler = null;
+ }
+ return thingHandler;
+ }
+}
diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureSession.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureSession.java
new file mode 100644
index 0000000000000..179425df21ced
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureSession.java
@@ -0,0 +1,1012 @@
+/**
+ * Copyright (c) 2010-2020 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.verisure.internal;
+
+import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
+
+import java.math.BigDecimal;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpResponseException;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.openhab.binding.verisure.internal.dto.VerisureAlarmsDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureBroadbandConnectionsDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureClimatesDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureDoorWindowsDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureEventLogDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureGatewayDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureGatewayDTO.CommunicationState;
+import org.openhab.binding.verisure.internal.dto.VerisureInstallationsDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureInstallationsDTO.Owainstallation;
+import org.openhab.binding.verisure.internal.dto.VerisureMiceDetectionDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureSmartLockDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureSmartLocksDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureSmartPlugsDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
+import org.openhab.binding.verisure.internal.dto.VerisureUserPresencesDTO;
+import org.openhab.binding.verisure.internal.handler.VerisureThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * This class performs the communication with Verisure My Pages.
+ *
+ * @author Jarle Hjortland - Initial contribution
+ * @author Jan Gustafsson - Re-design and support for several sites and update to new Verisure API
+ *
+ */
+@NonNullByDefault
+public class VerisureSession {
+
+ @NonNullByDefault({})
+ private final Map verisureThings = new ConcurrentHashMap<>();
+ private final Map> verisureHandlers = new ConcurrentHashMap<>();
+ private final Logger logger = LoggerFactory.getLogger(VerisureSession.class);
+ private final Gson gson = new Gson();
+ private final List> deviceStatusListeners = new CopyOnWriteArrayList<>();
+ private final Map verisureInstallations = new ConcurrentHashMap<>();
+ private static final List APISERVERLIST = Arrays.asList("https://m-api01.verisure.com",
+ "https://m-api02.verisure.com");
+ private int apiServerInUseIndex = 0;
+ private int numberOfEvents = 15;
+ private static final String USER_NAME = "username";
+ private static final String PASSWORD_NAME = "vid";
+ private String apiServerInUse = APISERVERLIST.get(apiServerInUseIndex);
+ private String authstring = "";
+ private @Nullable String csrf;
+ private @Nullable String pinCode;
+ private HttpClient httpClient;
+ private @Nullable String userName = "";
+ private @Nullable String password = "";
+
+ public VerisureSession(HttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public boolean initialize(@Nullable String authstring, @Nullable String pinCode, @Nullable String userName) {
+ if (authstring != null) {
+ this.authstring = authstring.substring(0);
+ this.pinCode = pinCode;
+ this.userName = userName;
+ // Try to login to Verisure
+ if (logIn()) {
+ return getInstallations();
+ } else {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ public boolean refresh() {
+ try {
+ if (logIn()) {
+ updateStatus();
+ return true;
+ } else {
+ return false;
+ }
+ } catch (HttpResponseException e) {
+ logger.warn("Failed to do a refresh {}", e.getMessage());
+ return false;
+ }
+ }
+
+ public int sendCommand(String url, String data, BigDecimal installationId) {
+ logger.debug("Sending command with URL {} and data {}", url, data);
+ try {
+ configureInstallationInstance(installationId);
+ int httpResultCode = setSessionCookieAuthLogin();
+ if (httpResultCode == HttpStatus.OK_200) {
+ return postVerisureAPI(url, data);
+ } else {
+ return httpResultCode;
+ }
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ logger.debug("Failed to send command {}", e.getMessage());
+ }
+ return HttpStatus.BAD_REQUEST_400;
+ }
+
+ public boolean unregisterDeviceStatusListener(
+ DeviceStatusListener extends VerisureThingDTO> deviceStatusListener) {
+ return deviceStatusListeners.remove(deviceStatusListener);
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean registerDeviceStatusListener(DeviceStatusListener extends VerisureThingDTO> deviceStatusListener) {
+ return deviceStatusListeners.add((DeviceStatusListener) deviceStatusListener);
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ public @Nullable T getVerisureThing(String deviceId, Class thingType) {
+ VerisureThingDTO thing = verisureThings.get(deviceId);
+ if (thingType.isInstance(thing)) {
+ return (T) thing;
+ }
+ return null;
+ }
+
+ public @Nullable T getVerisureThing(String deviceId) {
+ VerisureThingDTO thing = verisureThings.get(deviceId);
+ if (thing != null) {
+ @SuppressWarnings("unchecked")
+ T thing2 = (T) thing;
+ return thing2;
+ }
+ return null;
+ }
+
+ public @Nullable VerisureThingHandler> getVerisureThinghandler(String deviceId) {
+ VerisureThingHandler> thingHandler = verisureHandlers.get(deviceId);
+ return thingHandler;
+ }
+
+ public void setVerisureThingHandler(VerisureThingHandler> vth, String deviceId) {
+ verisureHandlers.put(deviceId, vth);
+ };
+
+ public void removeVerisureThingHandler(String deviceId) {
+ verisureHandlers.remove(deviceId);
+ }
+
+ public Collection getVerisureThings() {
+ return verisureThings.values();
+ }
+
+ public @Nullable String getCsrf() {
+ return csrf;
+ }
+
+ public @Nullable String getPinCode() {
+ return pinCode;
+ }
+
+ public String getApiServerInUse() {
+ return apiServerInUse;
+ }
+
+ public void setApiServerInUse(String apiServerInUse) {
+ this.apiServerInUse = apiServerInUse;
+ }
+
+ public String getNextApiServer() {
+ apiServerInUseIndex++;
+ if (apiServerInUseIndex > (APISERVERLIST.size() - 1)) {
+ apiServerInUseIndex = 0;
+ }
+ return APISERVERLIST.get(apiServerInUseIndex);
+ }
+
+ public void setNumberOfEvents(int numberOfEvents) {
+ this.numberOfEvents = numberOfEvents;
+ }
+
+ public void configureInstallationInstance(BigDecimal installationId)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ csrf = getCsrfToken(installationId);
+ logger.debug("Got CSRF: {}", csrf);
+ // Set installation
+ String url = SET_INSTALLATION + installationId;
+ httpClient.GET(url);
+ }
+
+ public @Nullable String getCsrfToken(BigDecimal installationId)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ String html = null;
+ String url = SETTINGS + installationId;
+
+ ContentResponse resp = httpClient.GET(url);
+ html = resp.getContentAsString();
+ logger.trace("url: {} html: {}", url, html);
+
+ Document htmlDocument = Jsoup.parse(html);
+ Element nameInput = htmlDocument.select("input[name=_csrf]").first();
+ if (nameInput != null) {
+ return nameInput.attr("value");
+ } else {
+ return null;
+ }
+ }
+
+ public @Nullable String getPinCode(BigDecimal installationId) {
+ return verisureInstallations.get(installationId).getPinCode();
+ }
+
+ private void setPasswordFromCookie() {
+ CookieStore c = httpClient.getCookieStore();
+ List cookies = c.getCookies();
+ cookies.forEach(cookie -> {
+ logger.trace("Response Cookie: {}", cookie);
+ if (cookie.getName().equals(PASSWORD_NAME)) {
+ password = cookie.getValue();
+ logger.debug("Fetching vid {} from cookie", password);
+ }
+ });
+ }
+
+ private void logTraceWithPattern(int responseStatus, String content) {
+ if (logger.isTraceEnabled()) {
+ String pattern = "(?m)^\\s*\\r?\\n|\\r?\\n\\s*(?!.*\\r?\\n)";
+ String replacement = "";
+ logger.trace("HTTP Response ({}) Body:{}", responseStatus, content.replaceAll(pattern, replacement));
+ }
+ }
+
+ private boolean areWeLoggedIn() throws ExecutionException, InterruptedException, TimeoutException {
+ logger.debug("Checking if we are logged in");
+ String url = STATUS;
+
+ ContentResponse response = httpClient.newRequest(url).method(HttpMethod.GET).send();
+ String content = response.getContentAsString();
+ logTraceWithPattern(response.getStatus(), content);
+
+ switch (response.getStatus()) {
+ case HttpStatus.OK_200:
+ if (content.contains("MyPages")) {
+ setPasswordFromCookie();
+ return true;
+ } else {
+ logger.debug("Not on mypages,verisure.com, we need to login again!");
+ return false;
+ }
+ case HttpStatus.MOVED_TEMPORARILY_302:
+ // Redirection
+ logger.debug("Status code 302. Redirected. Probably not logged in");
+ return false;
+ case HttpStatus.INTERNAL_SERVER_ERROR_500:
+ case HttpStatus.SERVICE_UNAVAILABLE_503:
+ throw new HttpResponseException(
+ "Status code " + response.getStatus() + ". Verisure service temporarily down", response);
+ default:
+ logger.debug("Status code {} body {}", response.getStatus(), content);
+ break;
+ }
+ return false;
+ }
+
+ private @Nullable T getJSONVerisureAPI(String url, Class jsonClass)
+ throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
+ logger.debug("HTTP GET: {}", BASEURL + url);
+
+ ContentResponse response = httpClient.GET(BASEURL + url + "?_=" + System.currentTimeMillis());
+ String content = response.getContentAsString();
+ logTraceWithPattern(response.getStatus(), content);
+
+ return gson.fromJson(content, jsonClass);
+ }
+
+ private ContentResponse postVerisureAPI(String url, String data, boolean isJSON)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ logger.debug("postVerisureAPI URL: {} Data:{}", url, data);
+ Request request = httpClient.newRequest(url).method(HttpMethod.POST);
+ if (isJSON) {
+ request.header("content-type", "application/json");
+ } else {
+ if (csrf != null) {
+ request.header("X-CSRF-TOKEN", csrf);
+ }
+ }
+ request.header("Accept", "application/json");
+ if (!data.equals("empty")) {
+ request.content(new BytesContentProvider(data.getBytes(StandardCharsets.UTF_8)),
+ "application/x-www-form-urlencoded; charset=UTF-8");
+ } else {
+ logger.debug("Setting cookie with username {} and vid {}", userName, password);
+ request.cookie(new HttpCookie(USER_NAME, userName));
+ request.cookie(new HttpCookie(PASSWORD_NAME, password));
+ }
+ logger.debug("HTTP POST Request {}.", request.toString());
+ return request.send();
+ }
+
+ private T postJSONVerisureAPI(String url, String data, Class jsonClass)
+ throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException, PostToAPIException {
+ for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
+ ContentResponse response = postVerisureAPI(apiServerInUse + url, data, Boolean.TRUE);
+ logger.debug("HTTP Response ({})", response.getStatus());
+ if (response.getStatus() == HttpStatus.OK_200) {
+ String content = response.getContentAsString();
+ if (content.contains("\"message\":\"Request Failed") && content.contains("503")) {
+ // Maybe Verisure has switched API server in use?
+ logger.debug("Changed API server! Response: {}", content);
+ setApiServerInUse(getNextApiServer());
+ } else {
+ String contentChomped = content.trim();
+ logger.trace("Response body: {}", content);
+ return gson.fromJson(contentChomped, jsonClass);
+ }
+ } else {
+ logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
+ }
+ }
+ throw new PostToAPIException("Failed to POST to API");
+ }
+
+ private int postVerisureAPI(String urlString, String data) {
+ String url;
+ if (urlString.contains("https://mypages")) {
+ url = urlString;
+ } else {
+ url = apiServerInUse + urlString;
+ }
+
+ for (int cnt = 0; cnt < APISERVERLIST.size(); cnt++) {
+ try {
+ ContentResponse response = postVerisureAPI(url, data, Boolean.FALSE);
+ logger.debug("HTTP Response ({})", response.getStatus());
+ int httpStatus = response.getStatus();
+ if (httpStatus == HttpStatus.OK_200) {
+ String content = response.getContentAsString();
+ if (content.contains("\"message\":\"Request Failed. Code 503 from")) {
+ if (url.contains("https://mypages")) {
+ // Not an API URL
+ return HttpStatus.SERVICE_UNAVAILABLE_503;
+ } else {
+ // Maybe Verisure has switched API server in use
+ setApiServerInUse(getNextApiServer());
+ url = apiServerInUse + urlString;
+ }
+ } else {
+ logTraceWithPattern(httpStatus, content);
+ return httpStatus;
+ }
+ } else {
+ logger.debug("Failed to send POST, Http status code: {}", response.getStatus());
+ }
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+ return HttpStatus.SERVICE_UNAVAILABLE_503;
+ }
+
+ private int setSessionCookieAuthLogin() throws ExecutionException, InterruptedException, TimeoutException {
+ // URL to set status which will give us 2 cookies with username and password used for the session
+ String url = STATUS;
+ ContentResponse response = httpClient.GET(url);
+ logTraceWithPattern(response.getStatus(), response.getContentAsString());
+
+ url = AUTH_LOGIN;
+ return postVerisureAPI(url, "empty");
+ }
+
+ private boolean getInstallations() {
+ int httpResultCode = 0;
+
+ try {
+ httpResultCode = setSessionCookieAuthLogin();
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ logger.warn("Failed to set session cookie {}", e.getMessage());
+ return false;
+ }
+
+ if (httpResultCode == HttpStatus.OK_200) {
+ String url = START_GRAPHQL;
+
+ String queryQLAccountInstallations = "[{\"operationName\":\"AccountInstallations\",\"variables\":{\"email\":\""
+ + userName
+ + "\"},\"query\":\"query AccountInstallations($email: String!) {\\n account(email: $email) {\\n owainstallations {\\n giid\\n alias\\n type\\n subsidiary\\n dealerId\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]";
+ try {
+ VerisureInstallationsDTO installations = postJSONVerisureAPI(url, queryQLAccountInstallations,
+ VerisureInstallationsDTO.class);
+ logger.debug("Installation: {}", installations.toString());
+ List owaInstList = installations.getData().getAccount().getOwainstallations();
+ boolean pinCodesMatchInstallations = true;
+ List pinCodes = null;
+ String pinCode = this.pinCode;
+ if (pinCode != null) {
+ pinCodes = Arrays.asList(pinCode.split(","));
+ if (owaInstList.size() != pinCodes.size()) {
+ logger.debug("Number of installations {} does not match number of pin codes configured {}",
+ owaInstList.size(), pinCodes.size());
+ pinCodesMatchInstallations = false;
+ }
+ } else {
+ logger.debug("No pin-code defined for user {}", userName);
+ }
+
+ for (int i = 0; i < owaInstList.size(); i++) {
+ VerisureInstallation vInst = new VerisureInstallation();
+ Owainstallation owaInstallation = owaInstList.get(i);
+ String installationId = owaInstallation.getGiid();
+ if (owaInstallation.getAlias() != null && installationId != null) {
+ vInst.setInstallationId(new BigDecimal(installationId));
+ vInst.setInstallationName(owaInstallation.getAlias());
+ if (pinCode != null && pinCodes != null) {
+ int pinCodeIndex = pinCodesMatchInstallations ? i : 0;
+ vInst.setPinCode(pinCodes.get(pinCodeIndex));
+ logger.debug("Setting configured pincode index[{}] to installation ID {}", pinCodeIndex,
+ installationId);
+ }
+ verisureInstallations.put(new BigDecimal(installationId), vInst);
+ } else {
+ logger.warn("Failed to get alias and/or giid");
+ return false;
+ }
+ }
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ } else {
+ logger.warn("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
+ return false;
+ }
+ return true;
+ }
+
+ private synchronized boolean logIn() {
+ try {
+ if (!areWeLoggedIn()) {
+ logger.debug("Attempting to log in to mypages.verisure.com");
+ String url = LOGON_SUF;
+ logger.debug("Login URL: {}", url);
+ int httpStatusCode = postVerisureAPI(url, authstring);
+ if (httpStatusCode != HttpStatus.OK_200) {
+ logger.debug("Failed to login, HTTP status code: {}", httpStatusCode);
+ return false;
+ }
+ return true;
+ } else {
+ return true;
+ }
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ logger.warn("Failed to login {}", e.getMessage());
+ }
+ return false;
+ }
+
+ private void notifyListeners(T thing) {
+ deviceStatusListeners.forEach(listener -> {
+ if (listener.getVerisureThingClass().equals(thing.getClass())) {
+ listener.onDeviceStateChanged(thing);
+ }
+ });
+ }
+
+ private void notifyListenersIfChanged(VerisureThingDTO thing, VerisureInstallation installation, String deviceId) {
+ String normalizedDeviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
+ thing.setDeviceId(normalizedDeviceId);
+ thing.setSiteId(installation.getInstallationId());
+ thing.setSiteName(installation.getInstallationName());
+ VerisureThingDTO oldObj = verisureThings.get(normalizedDeviceId);
+ if (!thing.equals(oldObj)) {
+ verisureThings.put(thing.getDeviceId(), thing);
+ notifyListeners(thing);
+ } else {
+ logger.trace("No need to notify listeners for thing: {}", thing);
+ }
+ }
+
+ private void updateStatus() {
+ logger.debug("Update status");
+ verisureInstallations.forEach((installationId, installation) -> {
+ try {
+ configureInstallationInstance(installation.getInstallationId());
+ int httpResultCode = setSessionCookieAuthLogin();
+ if (httpResultCode == HttpStatus.OK_200) {
+ updateAlarmStatus(installation);
+ updateSmartLockStatus(installation);
+ updateMiceDetectionStatus(installation);
+ updateClimateStatus(installation);
+ updateDoorWindowStatus(installation);
+ updateUserPresenceStatus(installation);
+ updateSmartPlugStatus(installation);
+ updateBroadbandConnectionStatus(installation);
+ updateEventLogStatus(installation);
+ updateGatewayStatus(installation);
+ } else {
+ logger.debug("Failed to set session cookie and auth login, HTTP result code: {}", httpResultCode);
+ }
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ logger.debug("Failed to update status {}", e.getMessage());
+ }
+ });
+ }
+
+ private String createOperationJSON(String operation, VariablesDTO variables, String query) {
+ OperationDTO operationJSON = new OperationDTO();
+ operationJSON.setOperationName(operation);
+ operationJSON.setVariables(variables);
+ operationJSON.setQuery(query);
+ return gson.toJson(Collections.singletonList(operationJSON));
+ }
+
+ private synchronized void updateAlarmStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "ArmState";
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ String query = "query " + operation
+ + "($giid: String!) {\n installation(giid: $giid) {\n armState {\n type\n statusType\n date\n name\n changedVia\n allowedForFirstLine\n allowed\n errorCodes {\n value\n message\n __typename\n}\n __typename\n}\n __typename\n}\n}\n";
+
+ String queryQLAlarmStatus = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for alarm status!");
+ try {
+ VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLAlarmStatus, VerisureAlarmsDTO.class);
+ logger.debug("REST Response ({})", thing);
+ // Set unique deviceID
+ String deviceId = "alarm" + installationId;
+ thing.setDeviceId(deviceId);
+ notifyListenersIfChanged(thing, installation, deviceId);
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateSmartLockStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "DoorLock";
+ String query = "query " + operation
+ + "($giid: String!) {\n installation(giid: $giid) {\n doorlocks {\n device {\n deviceLabel\n area\n __typename\n}\n currentLockState\n eventTime\n secureModeActive\n motorJam\n userString\n method\n __typename\n}\n __typename\n}\n}\n";
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ String queryQLSmartLock = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for smart lock status");
+
+ try {
+ VerisureSmartLocksDTO thing = postJSONVerisureAPI(url, queryQLSmartLock, VerisureSmartLocksDTO.class);
+ logger.debug("REST Response ({})", thing);
+ List doorLockList = thing.getData().getInstallation().getDoorlocks();
+ doorLockList.forEach(doorLock -> {
+ VerisureSmartLocksDTO slThing = new VerisureSmartLocksDTO();
+ VerisureSmartLocksDTO.Installation inst = new VerisureSmartLocksDTO.Installation();
+ inst.setDoorlocks(Collections.singletonList(doorLock));
+ VerisureSmartLocksDTO.Data data = new VerisureSmartLocksDTO.Data();
+ data.setInstallation(inst);
+ slThing.setData(data);
+ // Set unique deviceID
+ String deviceId = doorLock.getDevice().getDeviceLabel();
+ if (deviceId != null) {
+ // Set location
+ slThing.setLocation(doorLock.getDevice().getArea());
+ slThing.setDeviceId(deviceId);
+ // Fetch more info from old endpoint
+ try {
+ VerisureSmartLockDTO smartLockThing = getJSONVerisureAPI(SMARTLOCK_PATH + slThing.getDeviceId(),
+ VerisureSmartLockDTO.class);
+ logger.debug("REST Response ({})", smartLockThing);
+ slThing.setSmartLockJSON(smartLockThing);
+ notifyListenersIfChanged(slThing, installation, deviceId);
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException e) {
+ logger.warn("Failed to query for smartlock status: {}", e.getMessage());
+ }
+ }
+ });
+
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateSmartPlugStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "SmartPlug";
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ String query = "query " + operation
+ + "($giid: String!) {\n installation(giid: $giid) {\n smartplugs {\n device {\n deviceLabel\n area\n gui {\n support\n label\n __typename\n}\n __typename\n}\n currentState\n icon\n isHazardous\n __typename\n}\n __typename\n}\n}\n";
+ String queryQLSmartPlug = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for smart plug status");
+
+ try {
+ VerisureSmartPlugsDTO thing = postJSONVerisureAPI(url, queryQLSmartPlug, VerisureSmartPlugsDTO.class);
+ logger.debug("REST Response ({})", thing);
+ List smartPlugList = thing.getData().getInstallation().getSmartplugs();
+ smartPlugList.forEach(smartPlug -> {
+ VerisureSmartPlugsDTO spThing = new VerisureSmartPlugsDTO();
+ VerisureSmartPlugsDTO.Installation inst = new VerisureSmartPlugsDTO.Installation();
+ inst.setSmartplugs(Collections.singletonList(smartPlug));
+ VerisureSmartPlugsDTO.Data data = new VerisureSmartPlugsDTO.Data();
+ data.setInstallation(inst);
+ spThing.setData(data);
+ // Set unique deviceID
+ String deviceId = smartPlug.getDevice().getDeviceLabel();
+ if (deviceId != null) {
+ // Set location
+ spThing.setLocation(smartPlug.getDevice().getArea());
+ notifyListenersIfChanged(spThing, installation, deviceId);
+ }
+ });
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateClimateStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ String operation = "Climate";
+ String query = "query " + operation
+ + "($giid: String!) {\n installation(giid: $giid) {\n climates {\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n humidityEnabled\n humidityTimestamp\n humidityValue\n temperatureTimestamp\n temperatureValue\n __typename\n }\n __typename\n}\n}\n";
+
+ String queryQLClimates = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for climate status");
+
+ try {
+ VerisureClimatesDTO thing = postJSONVerisureAPI(url, queryQLClimates, VerisureClimatesDTO.class);
+ logger.debug("REST Response ({})", thing);
+ List climateList = thing.getData().getInstallation().getClimates();
+ climateList.forEach(climate -> {
+ // If thing is Mouse detection device, then skip it, but fetch temperature from it
+ String type = climate.getDevice().getGui().getLabel();
+ if ("MOUSE".equals(type)) {
+ logger.debug("Mouse detection device!");
+ String deviceId = climate.getDevice().getDeviceLabel();
+ if (deviceId != null) {
+ deviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
+ VerisureThingDTO mouseThing = verisureThings.get(deviceId);
+ if (mouseThing != null && mouseThing instanceof VerisureMiceDetectionDTO) {
+ VerisureMiceDetectionDTO miceDetectorThing = (VerisureMiceDetectionDTO) mouseThing;
+ miceDetectorThing.setTemperatureValue(climate.getTemperatureValue());
+ miceDetectorThing.setTemperatureTime(climate.getTemperatureTimestamp());
+ notifyListeners(miceDetectorThing);
+ logger.debug("Found climate thing for a Verisure Mouse Detector");
+ }
+ }
+ return;
+ }
+ VerisureClimatesDTO cThing = new VerisureClimatesDTO();
+ VerisureClimatesDTO.Installation inst = new VerisureClimatesDTO.Installation();
+ inst.setClimates(Collections.singletonList(climate));
+ VerisureClimatesDTO.Data data = new VerisureClimatesDTO.Data();
+ data.setInstallation(inst);
+ cThing.setData(data);
+ // Set unique deviceID
+ String deviceId = climate.getDevice().getDeviceLabel();
+ if (deviceId != null) {
+ // Set location
+ cThing.setLocation(climate.getDevice().getArea());
+ notifyListenersIfChanged(cThing, installation, deviceId);
+ }
+ });
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateDoorWindowStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "DoorWindow";
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ String query = "query " + operation
+ + "($giid: String!) {\n installation(giid: $giid) {\n doorWindows {\n device {\n deviceLabel\n area\n __typename\n }\n type\n state\n wired\n reportTime\n __typename\n }\n __typename\n}\n}\n";
+
+ String queryQLDoorWindow = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for door&window status");
+
+ try {
+ VerisureDoorWindowsDTO thing = postJSONVerisureAPI(url, queryQLDoorWindow, VerisureDoorWindowsDTO.class);
+ logger.debug("REST Response ({})", thing);
+ List doorWindowList = thing.getData().getInstallation().getDoorWindows();
+ doorWindowList.forEach(doorWindow -> {
+ VerisureDoorWindowsDTO dThing = new VerisureDoorWindowsDTO();
+ VerisureDoorWindowsDTO.Installation inst = new VerisureDoorWindowsDTO.Installation();
+ inst.setDoorWindows(Collections.singletonList(doorWindow));
+ VerisureDoorWindowsDTO.Data data = new VerisureDoorWindowsDTO.Data();
+ data.setInstallation(inst);
+ dThing.setData(data);
+ // Set unique deviceID
+ String deviceId = doorWindow.getDevice().getDeviceLabel();
+ if (deviceId != null) {
+ // Set location
+ dThing.setLocation(doorWindow.getDevice().getArea());
+ notifyListenersIfChanged(dThing, installation, deviceId);
+ }
+ });
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateBroadbandConnectionStatus(VerisureInstallation inst) {
+ BigDecimal installationId = inst.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "Broadband";
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ String query = "query " + operation
+ + "($giid: String!) {\n installation(giid: $giid) {\n broadband {\n testDate\n isBroadbandConnected\n __typename\n }\n __typename\n}\n}\n";
+
+ String queryQLBroadbandConnection = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for broadband connection status");
+
+ try {
+ VerisureThingDTO thing = postJSONVerisureAPI(url, queryQLBroadbandConnection,
+ VerisureBroadbandConnectionsDTO.class);
+ logger.debug("REST Response ({})", thing);
+ // Set unique deviceID
+ String deviceId = "bc" + installationId;
+ notifyListenersIfChanged(thing, inst, deviceId);
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateUserPresenceStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "userTrackings";
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ String query = "query " + operation
+ + "($giid: String!) {\ninstallation(giid: $giid) {\n userTrackings {\n isCallingUser\n webAccount\n status\n xbnContactId\n currentLocationName\n deviceId\n name\n currentLocationTimestamp\n deviceName\n currentLocationId\n __typename\n}\n __typename\n}\n}\n";
+
+ String queryQLUserPresence = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for user presence status");
+
+ try {
+ VerisureUserPresencesDTO thing = postJSONVerisureAPI(url, queryQLUserPresence,
+ VerisureUserPresencesDTO.class);
+ logger.debug("REST Response ({})", thing);
+ List userTrackingList = thing.getData().getInstallation()
+ .getUserTrackings();
+ userTrackingList.forEach(userTracking -> {
+ String localUserTrackingStatus = userTracking.getStatus();
+ if (localUserTrackingStatus != null && localUserTrackingStatus.equals("ACTIVE")) {
+ VerisureUserPresencesDTO upThing = new VerisureUserPresencesDTO();
+ VerisureUserPresencesDTO.Installation inst = new VerisureUserPresencesDTO.Installation();
+ inst.setUserTrackings(Collections.singletonList(userTracking));
+ VerisureUserPresencesDTO.Data data = new VerisureUserPresencesDTO.Data();
+ data.setInstallation(inst);
+ upThing.setData(data);
+ // Set unique deviceID
+ String deviceId = "up" + userTracking.getWebAccount() + installationId;
+ notifyListenersIfChanged(upThing, installation, deviceId);
+ }
+ });
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateMiceDetectionStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "Mouse";
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ String query = "query " + operation
+ + "($giid: String!) {\n installation(giid: $giid) {\n mice {\n device {\n deviceLabel\n area\n gui {\n support\n __typename\n}\n __typename\n}\n type\n detections {\n count\n gatewayTime\n nodeTime\n duration\n __typename\n}\n __typename\n}\n __typename\n}\n}\n";
+
+ String queryQLMiceDetection = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for mice detection status");
+
+ try {
+ VerisureMiceDetectionDTO thing = postJSONVerisureAPI(url, queryQLMiceDetection,
+ VerisureMiceDetectionDTO.class);
+ logger.debug("REST Response ({})", thing);
+ List miceList = thing.getData().getInstallation().getMice();
+ miceList.forEach(mouse -> {
+ VerisureMiceDetectionDTO miceThing = new VerisureMiceDetectionDTO();
+ VerisureMiceDetectionDTO.Installation inst = new VerisureMiceDetectionDTO.Installation();
+ inst.setMice(Collections.singletonList(mouse));
+ VerisureMiceDetectionDTO.Data data = new VerisureMiceDetectionDTO.Data();
+ data.setInstallation(inst);
+ miceThing.setData(data);
+ // Set unique deviceID
+ String deviceId = mouse.getDevice().getDeviceLabel();
+ logger.debug("Mouse id: {} for thing: {}", deviceId, mouse);
+ if (deviceId != null) {
+ // Set location
+ miceThing.setLocation(mouse.getDevice().getArea());
+ notifyListenersIfChanged(miceThing, installation, deviceId);
+ }
+ });
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateEventLogStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "EventLog";
+ int offset = 0;
+ int numberOfEvents = this.numberOfEvents;
+ List eventCategories = new ArrayList<>(Arrays.asList("INTRUSION", "FIRE", "SOS", "WATER", "ANIMAL",
+ "TECHNICAL", "WARNING", "ARM", "DISARM", "LOCK", "UNLOCK", "PICTURE", "CLIMATE", "CAMERA_SETTINGS",
+ "DOORWINDOW_STATE_OPENED", "DOORWINDOW_STATE_CLOSED", "USERTRACKING"));
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+ variables.setHideNotifications(true);
+ variables.setOffset(offset);
+ variables.setPagesize(numberOfEvents);
+ variables.setEventCategories(eventCategories);
+ String query = "query " + operation
+ + "($giid: String!, $offset: Int!, $pagesize: Int!, $eventCategories: [String], $fromDate: String, $toDate: String, $eventContactIds: [String]) {\n installation(giid: $giid) {\n eventLog(offset: $offset, pagesize: $pagesize, eventCategories: $eventCategories, eventContactIds: $eventContactIds, fromDate: $fromDate, toDate: $toDate) {\n moreDataAvailable\n pagedList {\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n gatewayArea\n eventType\n eventCategory\n eventSource\n eventId\n eventTime\n userName\n armState\n userType\n climateValue\n sensorType\n eventCount\n __typename\n }\n __typename\n }\n __typename\n }\n}\n";
+
+ String queryQLEventLog = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for event log status");
+
+ try {
+ VerisureEventLogDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureEventLogDTO.class);
+ logger.debug("REST Response ({})", thing);
+ // Set unique deviceID
+ String deviceId = "el" + installationId;
+ notifyListenersIfChanged(thing, installation, deviceId);
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private synchronized void updateGatewayStatus(VerisureInstallation installation) {
+ BigDecimal installationId = installation.getInstallationId();
+ String url = START_GRAPHQL;
+ String operation = "communicationState";
+ VariablesDTO variables = new VariablesDTO();
+ variables.setGiid(installationId.toString());
+
+ String query = "query " + operation
+ + "($giid: String!) {\n installation(giid: $giid) {\n communicationState {\n hardwareCarrierType\n result\n mediaType\n device {\n deviceLabel\n area\n gui {\n label\n __typename\n }\n __typename\n }\n testDate\n __typename\n }\n __typename\n }\n}";
+
+ String queryQLEventLog = createOperationJSON(operation, variables, query);
+ logger.debug("Quering API for gateway status");
+
+ try {
+ VerisureGatewayDTO thing = postJSONVerisureAPI(url, queryQLEventLog, VerisureGatewayDTO.class);
+ logger.debug("REST Response ({})", thing);
+ // Set unique deviceID
+ List communicationStateList = thing.getData().getInstallation().getCommunicationState();
+ if (!communicationStateList.isEmpty()) {
+ String deviceId = communicationStateList.get(0).getDevice().getDeviceLabel();
+ if (deviceId != null) {
+ notifyListenersIfChanged(thing, installation, deviceId);
+ }
+ }
+ } catch (ExecutionException | InterruptedException | TimeoutException | JsonSyntaxException
+ | PostToAPIException e) {
+ logger.warn("Failed to send a POST to the API {}", e.getMessage());
+ }
+ }
+
+ private final class VerisureInstallation {
+ private @Nullable String installationName;
+ private BigDecimal installationId = BigDecimal.ZERO;
+ private @Nullable String pinCode;
+
+ public @Nullable String getPinCode() {
+ return pinCode;
+ }
+
+ public void setPinCode(@Nullable String pinCode) {
+ this.pinCode = pinCode;
+ }
+
+ public VerisureInstallation() {
+ }
+
+ public BigDecimal getInstallationId() {
+ return installationId;
+ }
+
+ public @Nullable String getInstallationName() {
+ return installationName;
+ }
+
+ public void setInstallationId(BigDecimal installationId) {
+ this.installationId = installationId;
+ }
+
+ public void setInstallationName(@Nullable String installationName) {
+ this.installationName = installationName;
+ }
+ }
+
+ private static class OperationDTO {
+
+ @SuppressWarnings("unused")
+ private @Nullable String operationName;
+ @SuppressWarnings("unused")
+ private VariablesDTO variables = new VariablesDTO();
+ @SuppressWarnings("unused")
+ private @Nullable String query;
+
+ public void setOperationName(String operationName) {
+ this.operationName = operationName;
+ }
+
+ public void setVariables(VariablesDTO variables) {
+ this.variables = variables;
+ }
+
+ public void setQuery(String query) {
+ this.query = query;
+ }
+ }
+
+ public static class VariablesDTO {
+
+ @SuppressWarnings("unused")
+ private boolean hideNotifications;
+ @SuppressWarnings("unused")
+ private int offset;
+ @SuppressWarnings("unused")
+ private int pagesize;
+ @SuppressWarnings("unused")
+ private @Nullable List eventCategories = null;
+ @SuppressWarnings("unused")
+ private @Nullable String giid;
+
+ public void setHideNotifications(boolean hideNotifications) {
+ this.hideNotifications = hideNotifications;
+ }
+
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ public void setPagesize(int pagesize) {
+ this.pagesize = pagesize;
+ }
+
+ public void setEventCategories(List eventCategories) {
+ this.eventCategories = eventCategories;
+ }
+
+ public void setGiid(String giid) {
+ this.giid = giid;
+ }
+ }
+
+ private class PostToAPIException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public PostToAPIException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureThingConfiguration.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureThingConfiguration.java
new file mode 100644
index 0000000000000..64c537cecb009
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/VerisureThingConfiguration.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2010-2020 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.verisure.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Configuration class for VerisureThingHandler.
+ *
+ *
+ * @author Jan Gustafsson - Initial contribution
+ */
+@NonNullByDefault
+public class VerisureThingConfiguration {
+ public static final String DEVICE_ID_LABEL = "deviceId";
+
+ private String deviceId = "";
+ private int numberOfEvents;
+ private int eventTriggerDelay;
+
+ public String getDeviceId() {
+ // Make sure device id is normalized, i.e. replace all non character/digits with empty string
+ return normalizeDeviceId(deviceId);
+ }
+
+ public static String normalizeDeviceId(String unnormalizedDeviceId) {
+ return unnormalizedDeviceId.replaceAll("[^a-zA-Z0-9]+", "");
+ }
+
+ public int getNumberOfEvents() {
+ return numberOfEvents;
+ }
+
+ public int getEventTriggerDelay() {
+ return eventTriggerDelay;
+ }
+}
diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/discovery/VerisureThingDiscoveryService.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/discovery/VerisureThingDiscoveryService.java
new file mode 100644
index 0000000000000..4a02e79cd4400
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/discovery/VerisureThingDiscoveryService.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2010-2020 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.verisure.internal.discovery;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService;
+import org.eclipse.smarthome.config.discovery.DiscoveryResult;
+import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder;
+import org.eclipse.smarthome.config.discovery.DiscoveryService;
+import org.eclipse.smarthome.core.thing.ThingUID;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.eclipse.smarthome.core.thing.binding.ThingHandlerService;
+import org.openhab.binding.verisure.internal.VerisureHandlerFactory;
+import org.openhab.binding.verisure.internal.VerisureSession;
+import org.openhab.binding.verisure.internal.VerisureThingConfiguration;
+import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
+import org.openhab.binding.verisure.internal.handler.VerisureBridgeHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The discovery service, notified by a listener on the VerisureSession.
+ *
+ * @author Jarle Hjortland - Initial contribution
+ * @author Jan Gustafsson - Further development
+ *
+ */
+@NonNullByDefault
+public class VerisureThingDiscoveryService extends AbstractDiscoveryService
+ implements DiscoveryService, ThingHandlerService {
+
+ private static final int SEARCH_TIME_SECONDS = 60;
+ private final Logger logger = LoggerFactory.getLogger(VerisureThingDiscoveryService.class);
+
+ private @NonNullByDefault({}) VerisureBridgeHandler verisureBridgeHandler;
+ private @NonNullByDefault({}) ThingUID bridgeUID;
+
+ public VerisureThingDiscoveryService() {
+ super(VerisureHandlerFactory.SUPPORTED_THING_TYPES, SEARCH_TIME_SECONDS);
+ }
+
+ @Override
+ public void startScan() {
+ logger.debug("VerisureThingDiscoveryService:startScan");
+ removeOlderResults(getTimestampOfLastScan());
+ if (verisureBridgeHandler != null) {
+ VerisureSession session = verisureBridgeHandler.getSession();
+ if (session != null) {
+ Collection verisureThings = session.getVerisureThings();
+ verisureThings.stream().forEach(thing -> {
+ logger.debug("Discovered thing: {}", thing);
+ onThingAddedInternal(thing);
+ });
+ }
+ }
+ }
+
+ private void onThingAddedInternal(VerisureThingDTO thing) {
+ logger.debug("VerisureThingDiscoveryService:OnThingAddedInternal");
+ ThingUID thingUID = getThingUID(thing);
+ String deviceId = thing.getDeviceId();
+ if (thingUID != null) {
+ if (verisureBridgeHandler != null) {
+ String label = "Device Id: " + deviceId;
+ if (thing.getLocation() != null) {
+ label += ", Location: " + thing.getLocation();
+ }
+ if (thing.getSiteName() != null) {
+ label += ", Site name: " + thing.getSiteName();
+ }
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
+ .withLabel(label).withProperty(VerisureThingConfiguration.DEVICE_ID_LABEL, deviceId)
+ .withRepresentationProperty(deviceId).build();
+ logger.debug("thinguid: {}, bridge {}, label {}", thingUID, bridgeUID, deviceId);
+ thingDiscovered(discoveryResult);
+ }
+ } else {
+ logger.debug("Discovered unsupported thing of type '{}' with deviceId {}", thing.getClass(), deviceId);
+ }
+ }
+
+ private @Nullable ThingUID getThingUID(VerisureThingDTO thing) {
+ ThingUID thingUID = null;
+ if (verisureBridgeHandler != null) {
+ String deviceId = thing.getDeviceId();
+ // Make sure device id is normalized, i.e. replace all non character/digits with empty string
+ deviceId = VerisureThingConfiguration.normalizeDeviceId(deviceId);
+ thingUID = new ThingUID(thing.getThingTypeUID(), bridgeUID, deviceId);
+ }
+ return thingUID;
+ }
+
+ @Override
+ public void activate() {
+ super.activate(Collections.singletonMap(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, true));
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof VerisureBridgeHandler) {
+ verisureBridgeHandler = (VerisureBridgeHandler) handler;
+ bridgeUID = verisureBridgeHandler.getUID();
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return verisureBridgeHandler;
+ }
+}
diff --git a/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/dto/VerisureAlarmsDTO.java b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/dto/VerisureAlarmsDTO.java
new file mode 100644
index 0000000000000..7d6ab0a659920
--- /dev/null
+++ b/bundles/org.openhab.binding.verisure/src/main/java/org/openhab/binding/verisure/internal/dto/VerisureAlarmsDTO.java
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2010-2020 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.verisure.internal.dto;
+
+import static org.openhab.binding.verisure.internal.VerisureBindingConstants.THING_TYPE_ALARM;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+
+/**
+ * The alarms of the Verisure System.
+ *
+ * @author Jan Gustafsson - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class VerisureAlarmsDTO extends VerisureBaseThingDTO {
+
+ @Override
+ public ThingTypeUID getThingTypeUID() {
+ return THING_TYPE_ALARM;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+ public static class ArmState {
+
+ private @Nullable String type;
+ private @Nullable String statusType;
+ private @Nullable String date;
+ private @Nullable String name;
+ private @Nullable String changedVia;
+ private boolean allowedForFirstLine;
+ private boolean allowed;
+ private List