[surepetcare] Sure Petcare Binding (#9713)

Signed-off-by: Rene Scherer <rene@scherer-online.com>
This commit is contained in:
Rene Scherer 2021-02-28 17:39:13 +00:00 committed by GitHub
parent f6521f6fd9
commit f7ab69689f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 5567 additions and 0 deletions

View File

@ -258,6 +258,7 @@
/bundles/org.openhab.binding.sonyprojector/ @lolodomo
/bundles/org.openhab.binding.spotify/ @Hilbrand
/bundles/org.openhab.binding.squeezebox/ @digitaldan @mhilbush
/bundles/org.openhab.binding.surepetcare/ @renescherer @HerzScheisse
/bundles/org.openhab.binding.synopanalyzer/ @clinique
/bundles/org.openhab.binding.systeminfo/ @svilenvul
/bundles/org.openhab.binding.tacmi/ @twendt @Wolfgang1966 @marvkis

View File

@ -1271,6 +1271,11 @@
<artifactId>org.openhab.binding.squeezebox</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.surepetcare</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.synopanalyzer</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,383 @@
# Sure Petcare Binding
This binding offers integration to the Sure Petcare API, supporting cloud-connected cat flaps and feeders.
### Features
1. Read access to all attributes for households, devices (hubs, flaps) and pets through individual things/channels.
2. Manual setting of pet location.
3. Setting of LED Mode (hub), Locking Mode (flaps) and Curfews.
### Restrictions / TODO
1. The Sure Petcare API is not publicly available and this binding has been based on observed interactions between their mobile phone app and the cloud API.
If the Sure Petcare API changes, this binding might stop working.
2. The current version of the binding supports only cat/pet flaps. Feeders are not yet supported as I don't own one yet.
### Credits
The binding code is based on a lot of work done by other developers:
- Holger Eisold (https://github.com/HerzScheisse) - Python use in openHAB and various PRs (https://github.com/HerzScheisse/SurePetcare-openHAB-JSR223-Rules)
- Alex Toft (https://github.com/alextoft) - PHP implementation (https://github.com/alextoft/sureflap)
- rcastberg (https://github.com/rcastberg) - Python implementation (https://github.com/rcastberg/sure_petcare)
## Supported Things
This binding supports the following thing types
| Thing | Thing Type | Discovery | Description |
|-----------------|------------|-----------|--------------------------------------------------------------------------|
| Bridge | Bridge | Manual | A single connection to the Sure Petcare API |
| Household | Thing | Automatic | The Sure Petcare Household |
| Hub Device | Thing | Automatic | The hub device which connects the cat flaps and feeders to the internet |
| Flap Device | Thing | Automatic | A cat or pet flap |
| Feeder Device | Thing | Automatic | A pet feeder |
| Pet | Thing | Automatic | A pet (dog or cat) |
## Getting started / Discovery
The binding consists of a Bridge (the API connection), and a number of Things, which relates to the individual hardware devices and pets.
Sure Petcare things can be configured either through the online configuration utility via discovery, or manually through a 'surepetcare.things' configuration file.
The Bridge will not be autodiscovered and must be added manually. This can be done via bridge thing configuration file or via PaperUI. That is because the Sure Petcare API requires authentication credentials to communicate with the service.
After adding the Bridge, it will go ONLINE, and after a short while, the discovery process for household, devices and pets will start. When new hardware is discovered it will appear in the Inbox.
## Things and their channels
Channel names in **bold** are read/write, everything else is read-only
### Bridge Thing
| Channel | Type | Description |
|-------------|--------|-----------------------------------------------------------------------------------|
| refresh | Switch | Trigger switch to force a full cache update |
### Household Thing
| Channel | Type | Description |
|------------|----------|----------------------------------------------|
| id | Number | A unique id assigned by the Sure Petcare API |
| name | Text | The name of the household |
| timezoneId | Number | The id of the household's timezone |
### Hub Device Thing
| Channel | Type | Description |
|-----------------|----------|-----------------------------------------------------------------------|
| id | Number | A unique id assigned by the Sure Petcare API |
| name | Text | The name of the hub |
| product | Text | The type of product (1=hub) |
| ledMode | Text | The mode of the hub's LED ears |
| pairingMode | Text | The state of pairing |
| online | Switch | Indicator if the hub is connected to the internet |
### Flap Device Thing (Cat or Pet Flap)
| Channel | Type | Description |
|-----------------------|----------|-----------------------------------------------------------------------|
| id | Number | A unique id assigned by the Sure Petcare API |
| name | Text | The name of the flap |
| product | Text | The type of product (3=pet flap, 6=cat flap) |
| curfewEnabled1 | Switch | Indicator if curfew #1 configuration is enabled |
| curfewLockTime1 | Text | The curfew #1 locking time (HH:MM) |
| curfewUnlockTime1 | Text | The curfew #1 unlocking time (HH:MM) |
| curfewEnabled2 | Switch | Indicator if curfew #2 configuration is enabled |
| curfewLockTime2 | Text | The curfew #2 locking time (HH:MM) |
| curfewUnlockTime2 | Text | The curfew #2 unlocking time (HH:MM) |
| curfewEnabled3 | Switch | Indicator if curfew #3 configuration is enabled |
| curfewLockTime3 | Text | The curfew #3 locking time (HH:MM) |
| curfewUnlockTime3 | Text | The curfew #3 unlocking time (HH:MM) |
| curfewEnabled4 | Switch | Indicator if curfew #4 configuration is enabled |
| curfewLockTime4 | Text | The curfew #4 locking time (HH:MM) |
| curfewUnlockTime4 | Text | The curfew #4 unlocking time (HH:MM) |
| lockingMode | Text | The locking mode (e.g. in/out, in-only, out-only etc.) |
| online | Switch | Indicator if the flap is connected to the hub |
| lowBattery | Switch | Indicator if the battery voltage is low |
| batteryLevel | Number | The battery voltage percentage |
| batteryVoltage | Number | The absolute battery voltage measurement |
| deviceRSSI | Number | The received device signal strength in dB |
| hubRSSI | Number | The received hub signal strength in dB |
### Feeder Device Thing
| Channel | Type | Description |
|-------------------|-------------|-------------------------------------------------------------------------------------------------|
| id | Number | A unique id assigned by the Sure Petcare API |
| name | Text | The name of the feeder |
| product | Text | The type of product |
| online | Switch | Indicator if the feeder is connected to the hub |
| lowBattery | Switch | Indicator if the battery voltage is low |
| batteryLevel | Number | The battery voltage percentage |
| batteryVoltage | Number | The absolute battery voltage measurement |
| deviceRSSI | Number | The received device signal strength in dB |
| hubRSSI | Number | The received hub signal strength in dB |
| bowls | Text | The feeder bowls type (1 big bowl or 2 half bowls) |
| bowlsFood | Text | The feeder big bowl food type (wet food, dry food or both) |
| bowlsTarget | Number:Mass | The feeder big bowl target weight in gram (even if user setting is oz, API stores this in gram) |
| bowlsFoodLeft | Text | The feeder left half bowl food type (wet food, dry food or both) |
| bowlsTargetLeft | Number:Mass | The feeder left half bowl target weight |
| bowlsFoodRight | Text | The feeder right half bowl food type (wet food, dry food or both) |
| bowlsTargetRight | Number:Mass | The feeder right half bowl target weight |
| bowlsCloseDelay | Text | The feeder lid close delay (fast, normal, slow) |
| bowlsTrainingMode | Text | The feeder training mode (off, full open, almost full open, half closed, almost closed) |
### Pet Thing
| Channel | Type | Description |
|------------------------|-------------|-------------------------------------------------------------------------------------|
| id | Number | A unique id assigned by the Sure Petcare API |
| name | Text | The name of the pet |
| comment | Text | A user provided comment/description |
| gender | Text | The pet's gender |
| breed | Text | The pet's breed |
| species | Text | The pet's species |
| photo | Image | The image of the pet |
| tagIdentifier | Text | The unique identifier of the pet's micro chip or collar tag |
| location | Text | The current location of the pet (0=unknown, 1=inside, 2=outside) |
| locationChanged | DateTime | The time when the location was last changed |
| locationTimeoffset | String | Time-Command to set the pet location with a time offset. (10, 30 or 60 minutes ago) |
| locationChangedThrough | Text | The device name or username where the pet left/entered the house |
| weight | Number:Mass | The pet's weight (in kilogram) |
| dateOfBirth | DateTime | The pet's date of birth |
| feederDevice | Text | The device from which the pet last ate |
| feederLastChange | Number:Mass | The last eaten change in gram (big bowl) |
| feederLastChangeLeft | Number:Mass | The last eaten change in gram (half bowl left) |
| feederLastChangeRight | Number:Mass | The last eaten change in gram (half bowl right) |
| feederLastFeeding | DateTime | The pet's last eaten date |
## Manual configuration
### Things configuration
If you use the label parameter (after the @ sign) you then have these things grouped in single tabs on PaperUI Control.
```
Bridge surepetcare:bridge:bridge1 "Demo API Bridge" @ "SurePetcare" [ username="<USERNAME>", password="<PASSWORD>", refreshIntervalTopology=36000, refreshIntervalStatus=300 ]
{
Thing household 12345 "My Household" @ "SurePetcare"
Thing hubDevice 123456 "My SurePetcare Hub" @ "SurePetcare Devices"
Thing flapDevice 123456 "My Backdoor Cat Flap" @ "SurePetcare Devices"
Thing feederDevice 123456 "My Pet Feeder" @ "SurePetcare Devices"
Thing pet 12345 "My Cat" @ "SurePetcare Pets"
}
```
### Items configuration
```
/* *****************************************
* Bridge
* *****************************************/
Group dgPet
Switch UR_1a_Online "Bridge Online [%s]" (dgPet) {channel="surepetcare:bridge:bridge1:online"}
Switch UR_1a_Refresh "Bridge Data Refresh [%s]" (dgPet) {channel="surepetcare:bridge:bridge1:refresh"}
/* *****************************************
* Household
* *****************************************/
Number UR_1b_Id "Household Id [%d]" (dgPet) {channel="surepetcare:household:bridge1:12345:id"}
String UR_1b_Name "Household Name [%s]" (dgPet) {channel="surepetcare:household:bridge1:12345:name"}
Number UR_1b_TimezoneId "Household Timezone Id [%d]" (dgPet) {channel="surepetcare:household:bridge1:12345:timezoneId"}
/* *****************************************
* Hub
* *****************************************/
Number UR_1c_Id "Hub Id [%d]" (dgPet) {channel="surepetcare:hubDevice:bridge1:123456:id"}
String UR_1c_Name "Hub Name [%s]" (dgPet) {channel="surepetcare:hubDevice:bridge1:123456:name"}
String UR_1c_Product "Hub Product [%s]" (dgPet) {channel="surepetcare:hubDevice:bridge1:123456:product"}
String UR_1c_LEDMode "Hub LED Mode [%s]" (dgPet) {channel="surepetcare:hubDevice:bridge1:123456:ledMode"}
String UR_1c_PairingMode "Hub Pairing Mode [%s]" (dgPet) {channel="surepetcare:hubDevice:bridge1:123456:pairingMode"}
Switch UR_1c_Online "Hub Online [%s]" (dgPet) {channel="surepetcare:hubDevice:bridge1:123456:online"}
/* *****************************************
* Cat/Pet Flap
* *****************************************/
Number UR_1d_Id "Flap Id [%d]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:id"}
String UR_1d_Name "Flap Name [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:name"}
String UR_1d_Product "Flap Product [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:product"}
Switch UR_1d_CurfewEnabled1 "Flap Curfew 1 Enabled [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewEnabled1"}
String UR_1d_CurfewLockTime1 "Flap Curfew 1 Lock Time [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewLockTime1"}
String UR_1d_CurfewUnlockTime1 "Flap Curfew 1 Unlock Time [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewUnlockTime1"}
Switch UR_1d_CurfewEnabled2 "Flap Curfew 2 Enabled [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewEnabled2"}
String UR_1d_CurfewLockTime2 "Flap Curfew 2 Lock Time [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewLockTime2"}
String UR_1d_CurfewUnlockTime2 "Flap Curfew 2 Unlock Time [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewUnlockTime2"}
Switch UR_1d_CurfewEnabled3 "Flap Curfew 3 Enabled [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewEnabled3"}
String UR_1d_CurfewLockTime3 "Flap Curfew 3 Lock Time [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewLockTime3"}
String UR_1d_CurfewUnlockTime3 "Flap Curfew 3 Unlock Time [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewUnlockTime3"}
Switch UR_1d_CurfewEnabled4 "Flap Curfew 4 Enabled [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewEnabled4"}
String UR_1d_CurfewLockTime4 "Flap Curfew 4 Lock Time [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewLockTime4"}
String UR_1d_CurfewUnlockTime5 "Flap Curfew 4 Unlock Time [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:curfewUnlockTime4"}
String UR_1d_LockingMode "Flap Locking Mode [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:lockingMode"}
Switch UR_1d_LowBattery "Flap Low Battery [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:lowBattery"}
Number UR_1d_BatteryLevel "Flap Battery Level [%.0f %%]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:batteryLevel"}
Number UR_1d_BatteryVoltage "Flap Battery Voltage [%.1f V]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:batteryVoltage"}
Switch UR_1d_Online "Flap Online [%s]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:online"}
Number UR_1d_DeviceRSSI "Flap Device RSSI [%.2f dB]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:deviceRSSI"}
Number UR_1d_HubRSSI "Flap Hub RSSI [%.2f dB]" (dgPet) {channel="surepetcare:flapDevice:bridge1:123456:hubRSSI"}
/* *****************************************
* Pet
* *****************************************/
Number UR_1e_Id "Pet Id [%d]" (dgPet) {channel="surepetcare:pet:bridge1:12345:id"}
String UR_1e_Name "Pet Name [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:name"}
String UR_1e_Comment "Pet Comment [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:comment"}
String UR_1e_Gender "Pet Gender [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:gender"}
String UR_1e_Breed "Pet Breed [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:breed"}
String UR_1e_Species "Pet Species [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:species"}
Image UR_1e_Photo "Pet Photo" (dgPet) {channel="surepetcare:pet:bridge1:12345:photo"}
String UR_1e_TagIdentifier "Pet Tag Identifier [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:tagIdentifier"}
String UR_1e_Location "Pet Location [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:location"}
String UR_1e_LocationTimeoffset"Pet Switch Location [%s]" (gCats) {channel="surepetcare:pet:bridge1:20584:locationTimeoffset"}
DateTime UR_1e_LocationChanged "Pet Loc. Updated [%1$ta. %1$tH:%1$tM]" (dgPet) {channel="surepetcare:pet:bridge1:12345:locationChanged"}
String UR_1e_LocationThrough "Pet Entered / Left through [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:locationChangedThrough"}
Number:Mass UR_1e_Weight "Pet Weight [%.1f %unit%]" (dgPet) {channel="surepetcare:pet:bridge1:12345:weight"}
DateTime UR_1e_DateOfBirth "Pet Date of Birth [%1$td.%1$tm.%1$tY]" (dgPet) {channel="surepetcare:pet:bridge1:12345:dateOfBirth"}
// Pet Feeder Data
String UR_1e_Device "Device Name [%s]" (dgPet) {channel="surepetcare:pet:bridge1:12345:feederDevice"}
Number:Mass UR_1e_Change "Change: [%.2f %unit%]" (dgPet) {channel="surepetcare:pet:bridge1:12345:feederLastChange"}
Number:Mass UR_1e_ChangeLeft "Change: L [%.2f %unit%]" (dgPet) {channel="surepetcare:pet:bridge1:12345:feederLastChangeLeft"}
Number:Mass UR_1e_ChangeRight "Change: R [%.2f %unit%]" (dgPet) {channel="surepetcare:pet:bridge1:12345:feederLastChangeRight"}
DateTime UR_1e_FeedAt "Last Feeding [%1$ta. %1$tH:%1$tM]" (dgPet) {channel="surepetcare:pet:bridge1:12345:feederLastFeeding"}
/* *****************************************
* Pet Feeder
* *****************************************/
Number UR_1f_Id "Feeder ID [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:id"}
String UR_1f_Name "Feeder Name [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:name"}
String UR_1f_Product "Feeder Product [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:product"}
Switch UR_1f_LowBattery "Feeder Low Battery [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:lowBattery"}
Number UR_1f_BatteryLevel "Feeder Battery Level [%.0f %%]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:batteryLevel"}
Number UR_1f_BatteryVoltage "Feeder Battery Voltage [%.2f V]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:batteryVoltage"}
String UR_1f_BowlsType "Feeder Bowls Type [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowls"}
String UR_1f_BowlsFoodtype "Feeder Food Type [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowlsFood"}
Number:Mass UR_1f_BowlsTarget "Feeder Target [%.0f %unit%]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowlsTarget"}
String UR_1f_BowlsFoodtypeLeft "Feeder Food Type L [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowlsFoodLeft"}
Number:Mass UR_1f_BowlsTargetLeft "Feeder Target L [%.0f %unit%]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowlsTargetLeft"}
String UR_1f_BowlsFoodtypeRight "Feeder Food Type R [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowlsFoodRight"}
Number:Mass UR_1f_BowlsTargetRight "Feeder Target R [%.0f %unit%]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowlsTargetRight"}
String UR_1f_BowlsLidCloseDelay "Feeder Close Delay [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowlsCloseDelay"}
String UR_1f_BowlsTrainingMode "Feeder Training Mode [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:bowlsTrainingMode"}
Switch UR_1f_Online "Feeder Status [%s]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:online"}
Number UR_1f_DeviceRSSI "Feeder Device Signal [%.2f dB]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:deviceRSSI"}
Number UR_1f_HubRSSI "Feeder Hub Signal [%.2f dB]" (dgPet) {channel="surepetcare:feederDevice:bridge1:123456:hubRSSI"}
```
### Sitemap Configuration
```
sitemap surepetcare label="My home automation" {
Frame label="Bridge" {
Text item=UR_1a_Online valuecolor=[ON="green", OFF="red"]
Switch item=UR_1a_Refresh
}
Frame label="Single Pet/Cats items" {
Text item=UR_1e_Location valuecolor=[1="green", 2="red"]
// to see also the item state, just remove the brackets from the label
Switch item=UR_1e_Location label="Set Pet Location []" mappings=[1="Inside", 2="Outside"]
// Selection item=UR_1e_Location label="Set Pet Location []" mappings=[1="Im Haus", 2="Draußen"]
Text item=UR_1e_LocationChanged
Switch item=UR_1e_LocationTimeoffset label="Set Loc with time offset []" mappings=[10="-10min", 30="-30min", 60="-1h"]
Text item=UR_1e_LocationThrough
Text item=UR_1e_Id icon="text"
Text item=UR_1e_Name
Text item=UR_1e_Comment
Text item=UR_1e_Gender
Text item=UR_1e_Breed
Text item=UR_1e_Species
Text item=UR_1e_MicroChip
Text item=UR_1e_Weight icon="text"
Text item=UR_1e_DateOfBirth
Text item=UR_1e_FeedDevice
/*Text item=UR_1e_FeedChange icon="text"*/ // if you have one big bowl in your feeder use this line and comment the following 2 out
Text item=UR_1e_FeedChangeLeft icon="text"
Text item=UR_1e_FeedChangeRight icon="text"
Text item=UR_1e_FeedAt
Image item=UR_1e_Photo
}
Frame label="Hub Device" {
Text item=UR_1c_HubOnline valuecolor=[ON="green", OFF="red"]
Text item=UR_1c_HubId icon="text"
Text item=UR_1c_HubName
Text item=UR_1c_HubProduct
Switch item=UR_1c_HubLedMode mappings=[0="Off", 1="Bright", 4="Dimmed"]
Text item=UR_1c_HubPairingMode
}
Frame label="Flap Device" {
Text item=UR_1d_FlapOnline valuecolor=[ON="green", OFF="red"]
Text item=UR_1d_FlapId icon="text"
Text item=UR_1d_FlapName
Text item=UR_1d_FlapProduct
Switch item=UR_1d_FlapCurfewEnabled1
Text item=UR_1d_FlapCurfewLocktime1
Text item=UR_1d_FlapCurfewUnlocktime1
Switch item=UR_1d_FlapCurfewEnabled2
Text item=UR_1d_FlapCurfewLocktime2
Text item=UR_1d_FlapCurfewUnlocktime2
Switch item=UR_1d_FlapCurfewEnabled3
Text item=UR_1d_FlapCurfewLocktime3
Text item=UR_1d_FlapCurfewUnlocktime3
Switch item=UR_1d_FlapCurfewEnabled4
Text item=UR_1d_FlapCurfewLocktime4
Text item=UR_1d_FlapCurfewUnlocktime4
Text item=UR_1d_FlapLockingMode
Text item=UR_1d_FlapLowBattery valuecolor=[OFF="green", ON="red"]
Text item=UR_1d_FlapBatteryLevel icon="battery"
Text item=UR_1d_FlapBatteryVoltage icon="text"
Text item=UR_1d_FlapDeviceRSSI icon="network"
Text item=UR_1d_FlapHubRSSI icon="network"
}
Frame label="Feeder Device" {
Text item=UR_1f_FeederOnline valuecolor=[ON="green", OFF="red"]
Text item=UR_1f_FeederId icon="text"
Text item=UR_1f_FeederName
Text item=UR_1f_FeederProduct
Text item=UR_1f_FeederLowBattery valuecolor=[OFF="green", ON="red"]
Text item=UR_1f_FeederBatteryLevel icon="battery"
Text item=UR_1f_FeederBatteryVoltage icon="text"
Text item=UR_1f_FeederBowlsType
/*Text item=UR_1f_FeederBowlsFoodtype
Text item=UR_1f_FeederBowlsTarget icon="text"*/
Text item=UR_1f_FeederBowlsFoodtypeLeft
Text item=UR_1f_FeederBowlsTargetLeft icon="text"
Text item=UR_1f_FeederBowlsFoodtypeRight
Text item=UR_1f_FeederBowlsTargetRight icon="text"
Text item=UR_1f_FeederBowlsLidCloseDelay
Text item=UR_1f_FeederBowlsTrainingMode
Text item=UR_1f_FeederDeviceRSSI icon="network"
Text item=UR_1f_FeederHubRSSI icon="network"
}
}
```
### Using Group Items
You can also set pet locations with a group item. Please Note: the location for each pet gets updated only if the current location is not already the location you want to set. This can be very useful if you have alot of pets that often enter the home by any window/door.
Your .items file should contain this:
```
Group:String:OR(1,2) gLocation "Cats inside [%d]"
String UR_1e_Location "Pet Location [%s]" (dgPet, gLocation) {channel="surepetcare:pet:bridge1:12345:location"}
```
And your .sitemap file could look like this:
```
Frame label="Group Pet/Cats items" {
Selection item=gLocation label="Set ALL cats to:" mappings=[1="Inside", 2="Outside"] icon="text"
Switch item=gLocation label="Set ALL cats to: []" mappings=[1="Inside", 2="Outside"]
Group item=gLocation
}
```
## Troubleshooting
| Problem | Solution |
|---------------------------------------------|-------------------------------------------------------------------------------------|
| Bridge cannot connect to Sure Petcare API | Check if you can logon to the Sure Petcare app with the given username/password. |

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.surepetcare</artifactId>
<name>openHAB Add-ons :: Bundles :: SurePetcare Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.surepetcare-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-surepetcare" description="SurePetcare Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.surepetcare/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link AuthenticationException} is thrown if the authentication/login process is unsuccessful.
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class AuthenticationException extends Exception {
private static final long serialVersionUID = -7851429815600130535L;
public AuthenticationException() {
super();
}
public AuthenticationException(String message) {
super(message);
}
public AuthenticationException(Throwable cause) {
super(cause);
}
public AuthenticationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,473 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ProtocolException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Enumeration;
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.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceControl;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfewList;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceStatus;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareHousehold;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareLoginCredentials;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareLoginResponse;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePet;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePetStatus;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareTag;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareTopology;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
/**
* The {@link SurePetcareAPIHelper} is a helper class to abstract the Sure Petcare API. It handles authentication and
* all JSON API calls. If an API call fails it automatically refreshes the authentication token and retries.
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareAPIHelper {
private final Logger logger = LoggerFactory.getLogger(SurePetcareAPIHelper.class);
private static final String API_USER_AGENT = "Mozilla/5.0 (Linux; Android 7.0; SM-G930F Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36";
private static final String API_URL = "https://app.api.surehub.io/api";
private static final String TOPOLOGY_URL = API_URL + "/me/start";
private static final String PET_BASE_URL = API_URL + "/pet";
private static final String PET_STATUS_URL = API_URL + "/pet/?with[]=status&with[]=photo";
private static final String DEVICE_BASE_URL = API_URL + "/device";
private static final String LOGIN_URL = API_URL + "/auth/login";
public static final int DEFAULT_DEVICE_ID = 12344711;
private String authenticationToken = "";
private String username = "";
private String password = "";
private @NonNullByDefault({}) HttpClient httpClient;
private SurePetcareTopology topologyCache = new SurePetcareTopology();
/**
* Sets the httpClient object to be used for API calls to Sure Petcare.
*
* @param httpClient the client to be used.
*/
public void setHttpClient(@Nullable HttpClient httpClient) {
this.httpClient = httpClient;
}
/**
* This method uses the provided username and password to obtain an authentication token used for subsequent API
* calls.
*
* @param username The Sure Petcare username (email address) to be used
* @param password The password
* @throws AuthenticationException
*/
public synchronized void login(String username, String password) throws AuthenticationException {
try {
Request request = httpClient.POST(LOGIN_URL);
setConnectionHeaders(request);
request.content(new StringContentProvider(SurePetcareConstants.GSON
.toJson(new SurePetcareLoginCredentials(username, password, getDeviceId().toString()))));
ContentResponse response = request.send();
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
SurePetcareLoginResponse loginResponse = SurePetcareConstants.GSON
.fromJson(response.getContentAsString(), SurePetcareLoginResponse.class);
if (loginResponse != null) {
authenticationToken = loginResponse.getToken();
this.username = username;
this.password = password;
logger.debug("Login successful");
} else {
throw new AuthenticationException("Invalid JSON response from login");
}
} else {
logger.debug("HTTP Response Code: {}", response.getStatus());
logger.debug("HTTP Response Msg: {}", response.getReason());
throw new AuthenticationException(
"HTTP response " + response.getStatus() + " - " + response.getReason());
}
} catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
throw new AuthenticationException(e);
}
}
/**
* Refreshes the whole topology, i.e. all devices, pets etc. through a call to the Sure Petcare API. The APi call is
* quite resource intensive and should be used very infrequently.
*/
public synchronized void updateTopologyCache() {
try {
SurePetcareTopology tc = SurePetcareConstants.GSON.fromJson(getDataFromApi(TOPOLOGY_URL),
SurePetcareTopology.class);
if (tc != null) {
topologyCache = tc;
}
} catch (JsonSyntaxException | SurePetcareApiException e) {
logger.warn("Exception caught during topology cache update", e);
}
}
/**
* Refreshes the pet information. This API call can be used more frequently.
* Unlike for the "position" API endpoint, there is none for the "status" (activity/feeding).
* We also dont need to specify a "petId" in the call, so we just need to call the API once.
*/
public synchronized void updatePetStatus() {
try {
String url = PET_STATUS_URL;
topologyCache.pets = Arrays
.asList(SurePetcareConstants.GSON.fromJson(getDataFromApi(url), SurePetcarePet[].class));
} catch (JsonSyntaxException | SurePetcareApiException e) {
logger.warn("Exception caught during pet status update", e);
}
}
/**
* Returns the whole topology.
*
* @return the topology
*/
public final SurePetcareTopology getTopology() {
return topologyCache;
}
/**
* Returns a household object if one exists with the given id, otherwise null.
*
* @param id the household id
* @return the household with the given id
*/
public final @Nullable SurePetcareHousehold getHousehold(String id) {
return topologyCache.getById(topologyCache.households, id);
}
/**
* Returns a device object if one exists with the given id, otherwise null.
*
* @param id the device id
* @return the device with the given id
*/
public final @Nullable SurePetcareDevice getDevice(String id) {
return topologyCache.getById(topologyCache.devices, id);
}
/**
* Returns a pet object if one exists with the given id, otherwise null.
*
* @param id the pet id
* @return the pet with the given id
*/
public final @Nullable SurePetcarePet getPet(String id) {
return topologyCache.getById(topologyCache.pets, id);
}
/**
* Returns a tag object if one exists with the given id, otherwise null.
*
* @param id the tag id
* @return the tag with the given id
*/
public final @Nullable SurePetcareTag getTag(String id) {
return topologyCache.getById(topologyCache.tags, id);
}
/**
* Returns the status object if a pet exists with the given id, otherwise null.
*
* @param id the pet id
* @return the status of the pet with the given id
*/
public final @Nullable SurePetcarePetStatus getPetStatus(String id) {
SurePetcarePet pet = topologyCache.getById(topologyCache.pets, id);
return pet == null ? null : pet.status;
}
/**
* Updates the pet location through an API call to the Sure Petcare API.
*
* @param pet the pet
* @param newLocationId the id of the new location
* @throws SurePetcareApiException
*/
public synchronized void setPetLocation(SurePetcarePet pet, Integer newLocationId, ZonedDateTime newSince)
throws SurePetcareApiException {
pet.status.activity.where = newLocationId;
pet.status.activity.since = newSince;
String url = PET_BASE_URL + "/" + pet.id.toString() + "/position";
setDataThroughApi(url, HttpMethod.POST, pet.status.activity);
}
/**
* Updates the device locking mode through an API call to the Sure Petcare API.
*
* @param device the device
* @param newLockingModeId the id of the new locking mode
* @throws SurePetcareApiException
*/
public synchronized void setDeviceLockingMode(SurePetcareDevice device, Integer newLockingModeId)
throws SurePetcareApiException {
// post new JSON control structure to API
SurePetcareDeviceControl control = new SurePetcareDeviceControl();
control.lockingModeId = newLockingModeId;
String ctrlurl = DEVICE_BASE_URL + "/" + device.id.toString() + "/control";
setDataThroughApi(ctrlurl, HttpMethod.PUT, control);
// now we're fetching the new state back for the cache
String devurl = DEVICE_BASE_URL + "/" + device.id.toString() + "/status";
SurePetcareDeviceStatus newStatus = SurePetcareConstants.GSON.fromJson(getDataFromApi(devurl),
SurePetcareDeviceStatus.class);
device.status.assign(newStatus);
}
/**
* Updates the device led mode through an API call to the Sure Petcare API.
*
* @param device the device
* @param newLedModeId the id of the new led mode
* @throws SurePetcareApiException
*/
public synchronized void setDeviceLedMode(SurePetcareDevice device, Integer newLedModeId)
throws SurePetcareApiException {
// post new JSON control structure to API
SurePetcareDeviceControl control = new SurePetcareDeviceControl();
control.ledModeId = newLedModeId;
String ctrlurl = DEVICE_BASE_URL + "/" + device.id.toString() + "/control";
setDataThroughApi(ctrlurl, HttpMethod.PUT, control);
// now we're fetching the new state back for the cache
String devurl = DEVICE_BASE_URL + "/" + device.id.toString() + "/status";
SurePetcareDeviceStatus newStatus = SurePetcareConstants.GSON.fromJson(getDataFromApi(devurl),
SurePetcareDeviceStatus.class);
device.status.assign(newStatus);
}
/**
* Updates all curfews through an API call to the Sure Petcare API.
*
* @param device the device
* @param curfewList the list of curfews
* @throws SurePetcareApiException
*/
public synchronized void setCurfews(SurePetcareDevice device, SurePetcareDeviceCurfewList curfewList)
throws SurePetcareApiException {
// post new JSON control structure to API
SurePetcareDeviceControl control = new SurePetcareDeviceControl();
control.curfewList = curfewList.compact();
String ctrlurl = DEVICE_BASE_URL + "/" + device.id.toString() + "/control";
setDataThroughApi(ctrlurl, HttpMethod.PUT, control);
// now we're fetching the new state back for the cache
String devurl = DEVICE_BASE_URL + "/" + device.id.toString() + "/control";
SurePetcareDeviceControl newControl = SurePetcareConstants.GSON.fromJson(getDataFromApi(devurl),
SurePetcareDeviceControl.class);
if (newControl != null) {
newControl.curfewList = newControl.curfewList.order();
}
device.control = newControl;
}
/**
* Returns a unique device id used during the authentication process with the Sure Petcare API. The id is derived
* from the local MAC address or hostname.
*
* @return a unique device id
*/
public final Integer getDeviceId() {
try {
return getDeviceId(NetworkInterface.getNetworkInterfaces(), InetAddress.getLocalHost());
} catch (UnknownHostException | SocketException e) {
logger.warn("unable to discover mac or hostname, assigning default device id {}", DEFAULT_DEVICE_ID);
return DEFAULT_DEVICE_ID;
}
}
/**
* Returns a unique device id used during the authentication process with the Sure Petcare API. The id is derived
* from the local MAC address or hostname provided as arguments
*
* @param interfaces a list of interface of this host
* @param localHostAddress the ip address of the localhost
* @return a unique device id
*/
public final int getDeviceId(Enumeration<NetworkInterface> interfaces, InetAddress localHostAddress) {
int decimal = DEFAULT_DEVICE_ID;
try {
if (interfaces.hasMoreElements()) {
NetworkInterface netif = interfaces.nextElement();
byte[] mac = netif.getHardwareAddress();
if (mac != null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mac.length; i++) {
sb.append(String.format("%02x", mac[i]));
}
String hex = sb.toString();
decimal = Math.abs((int) (Long.parseUnsignedLong(hex, 16) % Integer.MAX_VALUE));
logger.debug("current MAC address: {}, device id: {}", hex, decimal);
} else {
String hostname = localHostAddress.getHostName();
decimal = hostname.hashCode();
logger.debug("current hostname: {}, device id: {}", hostname, decimal);
}
} else {
String hostname = localHostAddress.getHostName();
decimal = hostname.hashCode();
logger.debug("current hostname: {}, device id: {}", hostname, decimal);
}
} catch (SocketException e) {
logger.debug("Socket Exception", e);
}
return decimal;
}
/**
* Sets a set of required HTTP headers for the JSON API calls.
*
* @param request the HTTP connection
* @throws ProtocolException
*/
private void setConnectionHeaders(Request request) throws ProtocolException {
// headers
request.header(HttpHeader.ACCEPT, "application/json, text/plain, */*");
request.header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate");
request.header(HttpHeader.AUTHORIZATION, "Bearer " + authenticationToken);
request.header(HttpHeader.CONNECTION, "keep-alive");
request.header(HttpHeader.CONTENT_TYPE, "application/json; utf-8");
request.header(HttpHeader.USER_AGENT, API_USER_AGENT);
request.header(HttpHeader.REFERER, "https://surepetcare.io/");
request.header("Origin", "https://surepetcare.io");
request.header("Referer", "https://surepetcare.io");
request.header("X-Requested-With", "com.sureflap.surepetcare");
}
/**
* Return the "data" element of the API result as a JsonElement.
*
* @param url The URL of the API call.
* @return The "data" element of the API result.
* @throws SurePetcareApiException
*/
private JsonElement getDataFromApi(String url) throws SurePetcareApiException {
String apiResult = getResultFromApi(url);
JsonParser parser = new JsonParser();
JsonObject object = (JsonObject) parser.parse(apiResult);
return object.get("data");
}
/**
* Sends a given object as a JSON payload to the API.
*
* @param url the URL
* @param requestMethod the request method (POST, PUT etc.)
* @param payload an object used for the payload
* @throws SurePetcareApiException
*/
private void setDataThroughApi(String url, HttpMethod method, Object payload) throws SurePetcareApiException {
String jsonPayload = SurePetcareConstants.GSON.toJson(payload);
postDataThroughAPI(url, method, jsonPayload);
}
/**
* Returns the result of a GET API call as a string.
*
* @param url the URL
* @return a JSON string with the API result
* @throws SurePetcareApiException
*/
private String getResultFromApi(String url) throws SurePetcareApiException {
Request request = httpClient.newRequest(url).method(HttpMethod.GET);
ContentResponse response = executeAPICall(request);
String responseData = response.getContentAsString();
logger.debug("API execution successful, response: {}", responseData);
return responseData;
}
/**
* Uses the given request method to send a JSON string to an API.
*
* @param url the URL
* @param method the required request method (POST, PUT etc.)
* @param jsonPayload the JSON string
* @throws SurePetcareApiException
*/
private void postDataThroughAPI(String url, HttpMethod method, String jsonPayload) throws SurePetcareApiException {
logger.debug("postDataThroughAPI URL: {}", url);
logger.debug("postDataThroughAPI Payload: {}", jsonPayload);
Request request = httpClient.newRequest(url).method(method);
request.content(new StringContentProvider(jsonPayload));
executeAPICall(request);
}
/**
* Uses the given request execute the API call. If it receives an HTTP_UNAUTHORIZED response, it will automatically
* login again and retry.
*
* @param request the Request
* @return the response from the API
* @throws SurePetcareApiException
*/
private ContentResponse executeAPICall(Request request) throws SurePetcareApiException {
int retries = 3;
while (retries > 0) {
try {
setConnectionHeaders(request);
ContentResponse response = request.send();
if ((response.getStatus() == HttpURLConnection.HTTP_OK)
|| (response.getStatus() == HttpURLConnection.HTTP_CREATED)) {
return response;
} else {
logger.debug("HTTP Response Code: {}", response.getStatus());
logger.debug("HTTP Response Msg: {}", response.getReason());
if (response.getStatus() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// authentication token has expired, login again and retry
login(username, password);
retries--;
} else {
throw new SurePetcareApiException(
"Http error: " + response.getStatus() + " - " + response.getReason());
}
}
} catch (AuthenticationException | InterruptedException | ExecutionException | TimeoutException
| ProtocolException e) {
throw new SurePetcareApiException("Exception caught during API execution.", e);
}
}
throw new SurePetcareApiException("Can't execute API after 3 retries");
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SurePetcareApiException} is thrown during API interactions.
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareApiException extends Exception {
private static final long serialVersionUID = -7851429815604230535L;
public SurePetcareApiException() {
super();
}
public SurePetcareApiException(String message) {
super(message);
}
public SurePetcareApiException(Throwable cause) {
super(cause);
}
public SurePetcareApiException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,125 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.surepetcare.internal.utils.GsonLocalDateTypeAdapter;
import org.openhab.binding.surepetcare.internal.utils.GsonLocalTimeTypeAdapter;
import org.openhab.binding.surepetcare.internal.utils.GsonZonedDateTimeTypeAdapter;
import org.openhab.core.thing.ThingTypeUID;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* The {@link SurePetcareConstants} class defines common constants, which are used across the whole binding.
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareConstants {
public static final String BINDING_ID = "surepetcare";
// List all Thing Type UIDs, related to the binding
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_HOUSEHOLD = new ThingTypeUID(BINDING_ID, "household");
public static final ThingTypeUID THING_TYPE_PET = new ThingTypeUID(BINDING_ID, "pet");
public static final ThingTypeUID THING_TYPE_HUB_DEVICE = new ThingTypeUID(BINDING_ID, "hubDevice");
public static final ThingTypeUID THING_TYPE_FLAP_DEVICE = new ThingTypeUID(BINDING_ID, "flapDevice");
public static final ThingTypeUID THING_TYPE_FEEDER_DEVICE = new ThingTypeUID(BINDING_ID, "feederDevice");
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_BRIDGE);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(Arrays.asList(THING_TYPE_HOUSEHOLD,
THING_TYPE_PET, THING_TYPE_HUB_DEVICE, THING_TYPE_FLAP_DEVICE, THING_TYPE_FEEDER_DEVICE));
public static final long DEFAULT_REFRESH_INTERVAL_TOPOLOGY = 36000; // 10 hours
public static final long DEFAULT_REFRESH_INTERVAL_STATUS = 300; // 5 mins
public static final String PROPERTY_NAME_ID = "id";
public static final int FLAP_MAX_NUMBER_OF_CURFEWS = 4;
public static final int BOWL_ID_ONE_BOWL_USED = 1;
public static final int BOWL_ID_TWO_BOWLS_USED = 4;
public static final Gson GSON = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(ZonedDateTime.class, new GsonZonedDateTimeTypeAdapter())
.registerTypeAdapter(LocalDate.class, new GsonLocalDateTypeAdapter())
.registerTypeAdapter(LocalTime.class, new GsonLocalTimeTypeAdapter()).create();
// Bridge Channel Names
public static final String BRIDGE_CHANNEL_REFRESH = "refresh";
// Household Channel Names
public static final String HOUSEHOLD_CHANNEL_ID = "id";
public static final String HOUSEHOLD_CHANNEL_NAME = "name";
public static final String HOUSEHOLD_CHANNEL_TIMEZONE_ID = "timezoneId";
// Device Channel Names
public static final String DEVICE_CHANNEL_ID = "id";
public static final String DEVICE_CHANNEL_NAME = "name";
public static final String DEVICE_CHANNEL_PRODUCT = "product";
public static final String DEVICE_CHANNEL_LED_MODE = "ledMode";
public static final String DEVICE_CHANNEL_PAIRING_MODE = "pairingMode";
public static final String DEVICE_CHANNEL_ONLINE = "online";
public static final String DEVICE_CHANNEL_CURFEW_BASE = "curfew";
public static final String DEVICE_CHANNEL_CURFEW_ENABLED = DEVICE_CHANNEL_CURFEW_BASE + "Enabled";
public static final String DEVICE_CHANNEL_CURFEW_LOCK_TIME = DEVICE_CHANNEL_CURFEW_BASE + "LockTime";
public static final String DEVICE_CHANNEL_CURFEW_UNLOCK_TIME = DEVICE_CHANNEL_CURFEW_BASE + "UnlockTime";
public static final String DEVICE_CHANNEL_LOCKING_MODE = "lockingMode";
public static final String DEVICE_CHANNEL_BATTERY_VOLTAGE = "batteryVoltage";
public static final String DEVICE_CHANNEL_BATTERY_LEVEL = "batteryLevel";
public static final String DEVICE_CHANNEL_LOW_BATTERY = "lowBattery";
public static final String DEVICE_CHANNEL_DEVICE_RSSI = "deviceRSSI";
public static final String DEVICE_CHANNEL_HUB_RSSI = "hubRSSI";
public static final String DEVICE_CHANNEL_BOWLS_FOOD = "bowlsFood";
public static final String DEVICE_CHANNEL_BOWLS_TARGET = "bowlsTarget";
public static final String DEVICE_CHANNEL_BOWLS_FOOD_LEFT = "bowlsFoodLeft";
public static final String DEVICE_CHANNEL_BOWLS_TARGET_LEFT = "bowlsTargetLeft";
public static final String DEVICE_CHANNEL_BOWLS_FOOD_RIGHT = "bowlsFoodRight";
public static final String DEVICE_CHANNEL_BOWLS_TARGET_RIGHT = "bowlsTargetRight";
public static final String DEVICE_CHANNEL_BOWLS = "bowls";
public static final String DEVICE_CHANNEL_BOWLS_CLOSE_DELAY = "bowlsCloseDelay";
public static final String DEVICE_CHANNEL_BOWLS_TRAINING_MODE = "bowlsTrainingMode";
// Pet Channel Names
public static final String PET_CHANNEL_ID = "id";
public static final String PET_CHANNEL_NAME = "name";
public static final String PET_CHANNEL_COMMENT = "comment";
public static final String PET_CHANNEL_GENDER = "gender";
public static final String PET_CHANNEL_BREED = "breed";
public static final String PET_CHANNEL_SPECIES = "species";
public static final String PET_CHANNEL_PHOTO = "photo";
public static final String PET_CHANNEL_LOCATION = "location";
public static final String PET_CHANNEL_LOCATION_CHANGED = "locationChanged";
public static final String PET_CHANNEL_LOCATION_TIMEOFFSET = "locationTimeoffset";
public static final String PET_CHANNEL_LOCATION_CHANGED_THROUGH = "locationChangedThrough";
public static final String PET_CHANNEL_DATE_OF_BIRTH = "dateOfBirth";
public static final String PET_CHANNEL_WEIGHT = "weight";
public static final String PET_CHANNEL_TAG_IDENTIFIER = "tagIdentifier";
public static final String PET_CHANNEL_FEEDER_DEVICE = "feederDevice";
public static final String PET_CHANNEL_FEEDER_LASTFEEDING = "feederLastFeeding";
public static final String PET_CHANNEL_FEEDER_LAST_CHANGE = "feederLastChange";
public static final String PET_CHANNEL_FEEDER_LAST_CHANGE_LEFT = "feederLastChangeLeft";
public static final String PET_CHANNEL_FEEDER_LAST_CHANGE_RIGHT = "feederLastChangeRight";
}

View File

@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal;
import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.surepetcare.internal.handler.SurePetcareBridgeHandler;
import org.openhab.binding.surepetcare.internal.handler.SurePetcareDeviceHandler;
import org.openhab.binding.surepetcare.internal.handler.SurePetcareHouseholdHandler;
import org.openhab.binding.surepetcare.internal.handler.SurePetcarePetHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SurePetcareHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Rene Scherer - Initial contribution
*
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.surepetcare")
@NonNullByDefault
public class SurePetcareHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(SurePetcareHandlerFactory.class);
private SurePetcareAPIHelper petcareAPI = new SurePetcareAPIHelper();
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(BRIDGE_THING_TYPES_UIDS, SurePetcareConstants.SUPPORTED_THING_TYPES_UIDS).flatMap(Collection::stream)
.collect(Collectors.toSet());
/**
* Returns true if the factory supports the given thing type.
*/
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
/**
* Returns a newly created thing handler for the given thing.
*/
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
logger.debug("createHandler - create handler for {}", thing);
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_HOUSEHOLD)) {
return new SurePetcareHouseholdHandler(thing, petcareAPI);
} else if (thingTypeUID.equals(THING_TYPE_HUB_DEVICE)) {
return new SurePetcareDeviceHandler(thing, petcareAPI);
} else if (thingTypeUID.equals(THING_TYPE_FLAP_DEVICE)) {
return new SurePetcareDeviceHandler(thing, petcareAPI);
} else if (thingTypeUID.equals(THING_TYPE_FEEDER_DEVICE)) {
return new SurePetcareDeviceHandler(thing, petcareAPI);
} else if (thingTypeUID.equals(THING_TYPE_PET)) {
return new SurePetcarePetHandler(thing, petcareAPI);
} else if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
return new SurePetcareBridgeHandler((Bridge) thing, petcareAPI);
}
return null;
}
@Reference
protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
petcareAPI.setHttpClient(httpClientFactory.getCommonHttpClient());
}
protected void unsetHttpClientFactory(HttpClientFactory httpClientFactory) {
petcareAPI.setHttpClient(null);
}
}

View File

@ -0,0 +1,176 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.discovery;
import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice.ProductType;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareHousehold;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePet;
import org.openhab.binding.surepetcare.internal.handler.SurePetcareBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SurePetcareDiscoveryService} is an implementation of a discovery service for Sure Petcare pets and
* devices.
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(SurePetcareDiscoveryService.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
private static final int DISCOVER_TIMEOUT_SECONDS = 5;
private static final int DISCOVERY_SCAN_DELAY_MINUTES = 1;
private static final int DISCOVERY_REFRESH_INTERVAL_HOURS = 12;
private @Nullable ScheduledFuture<?> discoveryJob;
private @NonNullByDefault({}) SurePetcareBridgeHandler bridgeHandler;
private @NonNullByDefault({}) ThingUID bridgeUID;
/**
* Creates a SurePetcareDiscoveryService with enabled autostart.
*/
public SurePetcareDiscoveryService() {
super(SUPPORTED_THING_TYPES, DISCOVER_TIMEOUT_SECONDS);
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return SUPPORTED_THING_TYPES;
}
@Override
public void activate() {
Map<String, Object> properties = new HashMap<>();
properties.put(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY, Boolean.TRUE);
super.activate(properties);
}
/* We override this method to allow a call from the thing handler factory */
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof SurePetcareBridgeHandler) {
bridgeHandler = (SurePetcareBridgeHandler) handler;
bridgeUID = bridgeHandler.getUID();
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Starting Sure Petcare household discovery");
stopBackgroundDiscovery();
discoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, DISCOVERY_SCAN_DELAY_MINUTES,
DISCOVERY_REFRESH_INTERVAL_HOURS * 60, TimeUnit.MINUTES);
logger.debug("Scheduled topology-changed job every {} hours", DISCOVERY_REFRESH_INTERVAL_HOURS);
}
@Override
protected void stopBackgroundDiscovery() {
ScheduledFuture<?> job = discoveryJob;
if (job != null) {
job.cancel(true);
discoveryJob = null;
logger.debug("Stopped Sure Petcare device background discovery");
}
}
@Override
protected void startScan() {
logger.debug("Starting Sure Petcare discovery scan");
// If the bridge is not online no other thing devices can be found, so no reason to scan at this moment.
removeOlderResults(getTimestampOfLastScan());
if (bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
logger.debug("Starting device discovery for bridge {}", bridgeUID);
bridgeHandler.listHouseholds().forEach(this::householdDiscovered);
bridgeHandler.listPets().forEach(this::petDiscovered);
bridgeHandler.listDevices().forEach(this::deviceDiscovered);
}
}
private void householdDiscovered(SurePetcareHousehold household) {
logger.debug("Discovered household: {}", household.name);
ThingUID thingsUID = new ThingUID(THING_TYPE_HOUSEHOLD, bridgeUID, household.id.toString());
Map<String, Object> properties = new HashMap<>(household.getThingProperties());
thingDiscovered(DiscoveryResultBuilder.create(thingsUID).withLabel(household.name).withProperties(properties)
.withRepresentationProperty(PROPERTY_NAME_ID).withBridge(bridgeUID).build());
}
private void petDiscovered(SurePetcarePet pet) {
logger.debug("Discovered pet: {}", pet.name);
ThingUID thingsUID = new ThingUID(THING_TYPE_PET, bridgeUID, pet.id.toString());
Map<String, Object> properties = new HashMap<>(pet.getThingProperties());
thingDiscovered(DiscoveryResultBuilder.create(thingsUID).withLabel(pet.name).withProperties(properties)
.withRepresentationProperty(PROPERTY_NAME_ID).withBridge(bridgeUID).build());
}
private void deviceDiscovered(SurePetcareDevice device) {
logger.debug("Discovered device: {}", device.name);
ThingTypeUID typeUID = null;
switch (ProductType.findByTypeId(device.productId)) {
case HUB:
typeUID = THING_TYPE_HUB_DEVICE;
break;
case CAT_FLAP:
typeUID = THING_TYPE_FLAP_DEVICE;
break;
case PET_FLAP:
typeUID = THING_TYPE_FLAP_DEVICE;
break;
case PET_FEEDER:
typeUID = THING_TYPE_FEEDER_DEVICE;
break;
case UNKNOWN:
default:
return;
}
ThingUID thingsUID = new ThingUID(typeUID, bridgeUID, device.id.toString());
Map<String, Object> properties = new HashMap<>(device.getThingProperties());
thingDiscovered(DiscoveryResultBuilder.create(thingsUID).withLabel(device.name).withProperties(properties)
.withRepresentationProperty(PROPERTY_NAME_ID).withBridge(bridgeUID).build());
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SurePetcareBaseObject} is the Java class used as a base DTO for other primary JSON objects.
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareBaseObject {
public Long id = 0L;
public String version = "";
public ZonedDateTime createdAt = ZonedDateTime.now();
public ZonedDateTime updatedAt = ZonedDateTime.now();
public Map<String, String> getThingProperties() {
Map<String, String> properties = new HashMap<String, String>();
properties.put("id", id.toString());
properties.put("version", version);
properties.put("createdAt", createdAt.toString());
properties.put("updatedAt", updatedAt.toString());
return properties;
}
public SurePetcareBaseObject assign(SurePetcareBaseObject newdev) {
this.id = newdev.id;
this.version = newdev.version;
this.createdAt = newdev.createdAt;
this.updatedAt = newdev.updatedAt;
return this;
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
/**
* The {@link SurePetcareBridgeConfiguration} is a container for all the bridge configuration.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareBridgeConfiguration {
public String username;
public String password;
public long refreshIntervalTopology = DEFAULT_REFRESH_INTERVAL_TOPOLOGY;
public long refreshIntervalStatus = DEFAULT_REFRESH_INTERVAL_STATUS;
}

View File

@ -0,0 +1,110 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.core.thing.Thing;
/**
* The {@link SurePetcareDevice} is the Java class used
* as a DTO to represent a Sure Petcare device, such as a hub, a cat flap, a feeder etc.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareDevice extends SurePetcareBaseObject {
public enum ProductType {
UNKNOWN(0, "Unknown"),
HUB(1, "Hub"),
PET_FLAP(3, "Pet Flap"),
PET_FEEDER(4, "Pet Feeder"),
CAT_FLAP(6, "Cat Flap");
public final Integer id;
public final String name;
private ProductType(int id, String name) {
this.id = id;
this.name = name;
}
public static @NonNull ProductType findByTypeId(final int id) {
return Arrays.stream(values()).filter(value -> value.id.equals(id)).findFirst().orElse(UNKNOWN);
}
}
public Long parentDeviceId;
public Integer productId;
public Long householdId;
public String name;
public String serialNumber;
public String macAddress;
public Integer index;
public ZonedDateTime pairingAt;
public SurePetcareDeviceControl control = new SurePetcareDeviceControl();
public SurePetcareDevice parent;
public SurePetcareDeviceStatus status = new SurePetcareDeviceStatus();
@Override
public @NonNull Map<@NonNull String, @NonNull String> getThingProperties() {
@NonNull
Map<@NonNull String, @NonNull String> properties = super.getThingProperties();
properties.put("householdId", householdId.toString());
properties.put("productType", productId.toString());
properties.put("productName", ProductType.findByTypeId(productId).name);
properties.put(Thing.PROPERTY_MAC_ADDRESS, macAddress);
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
if (status.version.device != null) {
properties.put(Thing.PROPERTY_HARDWARE_VERSION, status.version.device.hardware);
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, status.version.device.firmware);
}
if (status.version.lcd != null) {
properties.put(Thing.PROPERTY_HARDWARE_VERSION, status.version.lcd.hardware);
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, status.version.lcd.firmware);
}
if (status.version.rf != null) {
properties.put("rfHardwareVersion", status.version.rf.hardware);
properties.put("rfFirmwareVersion", status.version.rf.firmware);
}
if (pairingAt != null) {
properties.put("pairingAt", pairingAt.toString());
}
return properties;
}
@Override
public String toString() {
return "Device [id=" + id + ", name=" + name + ", product=" + ProductType.findByTypeId(productId).name + "]";
}
public SurePetcareDevice assign(SurePetcareDevice newdev) {
super.assign(newdev);
this.parentDeviceId = newdev.parentDeviceId;
this.productId = newdev.productId;
this.householdId = newdev.householdId;
this.name = newdev.name;
this.serialNumber = newdev.serialNumber;
this.macAddress = newdev.macAddress;
this.index = newdev.index;
this.pairingAt = newdev.pairingAt;
this.control = newdev.control;
this.parent = newdev.parent;
this.status = newdev.status;
return this;
}
}

View File

@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.util.List;
import org.openhab.binding.surepetcare.internal.utils.SurePetcareDeviceCurfewListTypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SurePetcareDeviceControl} class is used to serialize a JSON object to control certain parameters of a
* device (e.g. locking mode, curfew etc.).
*
* @author Rene Scherer - Initial contribution
* @author Holger Eisold - Added pet feeder status
*/
public class SurePetcareDeviceControl {
@SerializedName("locking")
public Integer lockingModeId;
public Boolean fastPolling;
@SerializedName("led_mode")
public Integer ledModeId;
@SerializedName("pairing_mode")
public Integer pairingModeId;
public Bowls bowls;
public Lid lid;
@SerializedName("training_mode")
public Integer trainingModeId;
@SerializedName("curfew")
@JsonAdapter(SurePetcareDeviceCurfewListTypeAdapterFactory.class)
public SurePetcareDeviceCurfewList curfewList;
public class Bowls {
public class BowlSettings {
@SerializedName("food_type")
public Integer foodId;
@SerializedName("target")
public Integer targetId;
public BowlSettings(Integer foodId, Integer targetId) {
this.foodId = foodId;
this.targetId = targetId;
}
}
@SerializedName("settings")
public List<BowlSettings> bowlSettings;
@SerializedName("type")
public Integer bowlId;
public Bowls(List<BowlSettings> bowlSettings, Integer bowlId) {
this.bowlSettings = bowlSettings;
this.bowlId = bowlId;
}
}
public class Lid {
@SerializedName("close_delay")
public Integer closeDelayId;
public Lid(Integer closeDelayId) {
this.closeDelayId = closeDelayId;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("SurePetcareDeviceControl [");
builder.append("lockingModeId=").append(lockingModeId);
builder.append(", fastPolling=").append(fastPolling);
builder.append(", ledModeId=").append(ledModeId);
builder.append(", pairingModeId=").append(pairingModeId);
builder.append(", trainingModeId=").append(trainingModeId);
builder.append(", curfew=").append(curfewList);
return builder.toString();
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.time.LocalTime;
/**
* The {@link SurePetcareDeviceCurfew} class is used to serialize a curfew.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareDeviceCurfew {
public boolean enabled;
public LocalTime lockTime;
public LocalTime unlockTime;
public SurePetcareDeviceCurfew() {
this.enabled = false;
this.lockTime = LocalTime.MIDNIGHT;
this.unlockTime = LocalTime.MIDNIGHT;
}
public SurePetcareDeviceCurfew(boolean enabled, LocalTime lockTime, LocalTime unlockTime) {
this.enabled = enabled;
this.lockTime = lockTime;
this.unlockTime = unlockTime;
}
@Override
public String toString() {
return enabled + "," + lockTime + "," + unlockTime;
}
}

View File

@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.util.ArrayList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
/**
* The {@link SurePetcareDeviceCurfewList} class is used to serialize a list of curfew parameters.
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareDeviceCurfewList extends ArrayList<SurePetcareDeviceCurfew> {
private static final long serialVersionUID = -6947992959305282143L;
/**
* Return the list element with the given index. If the list if too short, it will grow automatically to the given
* index.
*
* @return element with given index
*/
@Override
public SurePetcareDeviceCurfew get(int index) {
while (size() <= index) {
// grow list to required size
add(new SurePetcareDeviceCurfew());
}
return super.get(index);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("[");
for (SurePetcareDeviceCurfew c : this) {
builder.append("(").append(c).append(")");
}
builder.append("]]");
return builder.toString();
}
/**
* Creates a list of curfews with enabled ones at the front of the list and disabled ones at the back.
*
* @return new ordered list.
*/
public SurePetcareDeviceCurfewList order() {
SurePetcareDeviceCurfewList orderedList = new SurePetcareDeviceCurfewList();
// remove any disabled curfews from the list
for (SurePetcareDeviceCurfew curfew : this) {
if (curfew.enabled) {
orderedList.add(curfew);
}
}
// now fill up the list with empty disabled slots.
for (int i = orderedList.size(); i < SurePetcareConstants.FLAP_MAX_NUMBER_OF_CURFEWS; i++) {
orderedList.add(new SurePetcareDeviceCurfew());
}
return orderedList;
}
/**
* Trims the list of curfews and removes any disabled ones.
*
* @return the new compact list of curfews.
*/
public SurePetcareDeviceCurfewList compact() {
SurePetcareDeviceCurfewList compactList = new SurePetcareDeviceCurfewList();
// remove any disabled curfews from the list
for (SurePetcareDeviceCurfew curfew : this) {
if (curfew.enabled) {
compactList.add(curfew);
}
}
return compactList;
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SurePetcareDeviceStatus} class is used to serialize a JSON object to report the status of a device (e.g.
* locking mode, LED mode etc.).
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareDeviceStatus {
@SerializedName("led_mode")
public Integer ledModeId;
@SerializedName("pairing_mode")
public Integer pairingModeId;
public Locking locking;
public Version version;
public Float battery;
// learn_mode - unknown type
public Boolean online;
public Signal signal = new Signal();
public SurePetcareDeviceStatus assign(SurePetcareDeviceStatus source) {
this.ledModeId = source.ledModeId;
this.pairingModeId = source.pairingModeId;
this.locking = source.locking;
this.version = source.version;
this.battery = source.battery;
this.online = source.online;
this.signal = source.signal;
return this;
}
public class Locking {
@SerializedName("mode")
public Integer modeId;
}
public class Version {
public class Device {
public String hardware;
public String firmware;
}
// for Cat flaps only
public Device device = new Device();
// for Pet flaps only
public Device lcd = new Device();
public Device rf = new Device();
}
public class Signal {
public Float deviceRssi;
public Float hubRssi;
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SurePetcareHousehold} is the Java class used as a DTO to represent a Sure Petcare Household.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareHousehold extends SurePetcareBaseObject {
public String name;
public String shareCode;
public Integer timezoneId;
@SerializedName("users")
public List<HouseholdUsers> users = null;
@Override
public String toString() {
return "SurePetcareHousehold [id=" + id + ", name=" + name + "]";
}
public static class HouseholdUsers {
public class User {
@SerializedName("id")
public Long userId;
@SerializedName("name")
public String userName;
}
public User user;
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
/**
* The {@link SurePetcareLoginCredentials} is the Java class as a DTO to hold login credentials for the Sure Petcare
* API.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareLoginCredentials {
public String emailAddress;
public String password;
public String deviceId;
public SurePetcareLoginCredentials() {
}
public SurePetcareLoginCredentials(String emailAddress, String password, String deviceId) {
this.emailAddress = emailAddress;
this.password = password;
this.deviceId = deviceId;
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
/**
* The {@link SurePetcareLoginResponse} is a Java class used as a DTO to hold the Sure Petcare API's login response.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareLoginResponse {
public Data data;
public String getToken() {
return data.token;
}
public class Data {
public SurePetcareUser user;
/**
* The Sure Petcare API authentication token returned from the login call
*/
public String token;
@Override
public String toString() {
return "Data [user=" + user + ", token=" + token + "]";
}
}
@Override
public String toString() {
return "SurePetcareJsonLoginResponse [data=" + data + "]";
}
}

View File

@ -0,0 +1,97 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SurePetcarePet} is a DTO class used to represent a pet. It's used to deserialize JSON API results.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcarePet extends SurePetcareBaseObject {
public enum PetGender {
UNKNONWN(-1, "Unknown"),
FEMALE(0, "Female"),
MALE(1, "Male");
public final Integer id;
public final String name;
private PetGender(int id, String name) {
this.id = id;
this.name = name;
}
public static PetGender findByTypeId(final int id) {
return Arrays.stream(values()).filter(value -> value.id.equals(id)).findFirst().orElse(UNKNONWN);
}
}
public enum PetSpecies {
UNKNONWN(0, "Unknown"),
CAT(1, "Cat"),
DOG(2, "Dog");
public final Integer id;
public final String name;
private PetSpecies(int id, String name) {
this.id = id;
this.name = name;
}
public static PetSpecies findByTypeId(final int id) {
return Arrays.stream(values()).filter(value -> value.id.equals(id)).findFirst().orElse(UNKNONWN);
}
}
public String name = "";
@SerializedName("gender")
public Integer genderId;
public LocalDate dateOfBirth;
public BigDecimal weight;
public String comments;
public Long householdId;
public Integer breedId;
public Long photoId;
public Integer speciesId;
public Long tagId;
public SurePetcarePhoto photo;
public SurePetcarePetStatus status = new SurePetcarePetStatus();
@Override
public String toString() {
return "Pet [id=" + id + ", name=" + name + ", tagId=" + tagId + "]";
}
@Override
public @NonNull Map<@NonNull String, @NonNull String> getThingProperties() {
@NonNull
Map<@NonNull String, @NonNull String> properties = super.getThingProperties();
properties.put("householdId", householdId.toString());
return properties;
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.time.ZonedDateTime;
/**
* The {@link SurePetcarePetActivity} is the Java class used to represent the
* status of a pet. It's used to deserialize JSON API results.
*
* @author Rene Scherer - Initial contribution
* @author Holger Eisold - Added pet feeder status
*/
public class SurePetcarePetActivity {
public Long tagId;
public Long deviceId;
public Long userId;
public Integer where;
public ZonedDateTime since;
public SurePetcarePetActivity() {
}
public SurePetcarePetActivity(Integer location, ZonedDateTime since) {
this.where = location;
this.since = since;
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SurePetcarePetFeeding} is the Java class used to represent the
* status of a pet. It's used to deserialize JSON API results.
*
* @author Rene Scherer - Initial contribution
* @author Holger Eisold - Added pet feeder status
*/
public class SurePetcarePetFeeding {
public Long tagId;
public Long deviceId;
@SerializedName("change")
public List<Float> feedChange = new ArrayList<>();
@SerializedName("at")
public ZonedDateTime feedChangeAt;
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
/**
* The {@link SurePetcarePetStatus} is the Java class used to represent the
* status of a pet. It's used to deserialize JSON API results.
*
* @author Rene Scherer - Initial contribution
* @author Holger Eisold - Added pet feeder status
*/
public class SurePetcarePetStatus {
public SurePetcarePetActivity activity;
public SurePetcarePetFeeding feeding;
}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
/**
* The {@link SurePetcarePhoto} is the Java class used to represent the photo of a pet or user. It's used to deserialize
* JSON API results.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcarePhoto extends SurePetcareBaseObject {
// {
// "id":78634,
// "location":"https:\/\/surehub.s3.amazonaws.com\/user-photos\/thm\/23421\/z70LUtqaHVlAIkgYRDIooi5666GvQwdAZptCgeZU.jpg",
// "uploading_user_id":34542,
// "version":"MA==",
// "created_at":"2019-09-02T09:31:07+00:00",
// "updated_at":"2019-09-02T09:31:07+00:00"
// }
public String location;
public Long uploadingUserId;
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.util.ArrayList;
import java.util.List;
/**
* The {@link SurePetcareTag} is the Java class used to represent the micro chip or collar tag of a pet. It's used to
* deserialize JSON API results.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareTag extends SurePetcareBaseObject {
// {
// "id":34552,
// "tag":"981.000007623719",
// "version":"MA==",
// "created_at":"2019-09-02T09:27:17+00:00",
// "updated_at":"2019-09-02T09:27:17+00:00",
// "supported_product_ids":[
// 3,
// 4,
// 6
// ]
// }
public String tag;
public List<Integer> supportedProductIds = new ArrayList<>();
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link SurePetcareTopology} is the Java class used to represent a whole Sure Petcare topology. It's used to
* deserialize JSON API results.
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareTopology {
public List<SurePetcareDevice> devices = new ArrayList<>();
public List<SurePetcareHousehold> households = new ArrayList<>();
public List<SurePetcarePet> pets = new ArrayList<>();
public List<SurePetcarePhoto> photos = new ArrayList<>();
public List<SurePetcareTag> tags = new ArrayList<>();
@Nullable
public SurePetcareUser user;
public @Nullable <T extends SurePetcareBaseObject> T getById(List<T> elements, String id) {
for (T e : elements) {
if (id.equals(e.id.toString())) {
return e;
}
}
return null;
}
@Override
public String toString() {
return "SurePetcareTopology [# of Devices=" + devices.size() + ", # of Households=" + households.size()
+ ", # of pets=" + pets.size() + "]";
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.dto;
/**
* The {@link SurePetcareUser} is the Java class used to represent a Sure Petcare API user. It's used to deserialize
* JSON API results.
*
* @author Rene Scherer - Initial contribution
*/
public class SurePetcareUser extends SurePetcareBaseObject {
// "user":{
// "id":23465,
// "email_address":"rs@gugus.com",
// "first_name":"Admin",
// "last_name":"User",
// "country_id":77,
// "language_id":37,
// "marketing_opt_in":false,
// "terms_accepted":true,
// "weight_units":0,
// "time_format":0,
// "version":"MA==",
// "created_at":"2019-09-02T08:20:03+00:00",
// "updated_at":"2019-09-02T08:20:03+00:00",
// "notifications":{
// "device_status":true,
// "animal_movement":true,
// "intruder_movements":true,
// "new_device_pet":true,
// "household_management":true,
// "photos":true,
// "low_battery":true,
// "curfew":true,
// "feeding_activity":true
// }
// }
public String emailAddress;
public String firstName;
public String lastName;
public Integer countryId;
public Integer languageId;
// and various others not yet mapped
@Override
public String toString() {
return "User [id=" + id + ", email_address=" + emailAddress + ", first_name=" + firstName + ", last_name="
+ lastName + "]";
}
}

View File

@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.handler;
import static org.openhab.core.thing.ThingStatus.ONLINE;
import java.time.Duration;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.surepetcare.internal.SurePetcareAPIHelper;
import org.openhab.core.cache.ExpiringCache;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SurePetcareBaseObjectHandler} is responsible for handling the things created to represent Sure Petcare
* objects.
*
* @author Rene Scherer - Initial Contribution
*/
@NonNullByDefault
public abstract class SurePetcareBaseObjectHandler extends BaseThingHandler {
private static final int CACHE_TIMEOUT_SECOND = 3;
private final Logger logger = LoggerFactory.getLogger(SurePetcareBaseObjectHandler.class);
protected final SurePetcareAPIHelper petcareAPI;
protected ExpiringCache<Integer> updateThingCache = new ExpiringCache<>(Duration.ofSeconds(CACHE_TIMEOUT_SECOND),
this::refreshCache);
public SurePetcareBaseObjectHandler(Thing thing, final SurePetcareAPIHelper petcareAPI) {
super(thing);
this.petcareAPI = petcareAPI;
}
@Override
public void initialize() {
updateStatus(ONLINE);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
updateThingCache.getValue();
}
}
@Override
public void updateProperties(Map<String, String> properties) {
super.updateProperties(properties);
}
private @Nullable Integer refreshCache() {
logger.debug("cache has timed out, we refresh the values in the thing");
updateThing();
// we don't care about the cache content, we just return an empty object
return Integer.MIN_VALUE;
}
/**
* Updates all channels of a thing.
*/
protected abstract void updateThing();
}

View File

@ -0,0 +1,212 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.handler;
import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.surepetcare.internal.AuthenticationException;
import org.openhab.binding.surepetcare.internal.SurePetcareAPIHelper;
import org.openhab.binding.surepetcare.internal.discovery.SurePetcareDiscoveryService;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareBridgeConfiguration;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareHousehold;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePet;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SurePetcareBridgeHandler} is responsible for handling the bridge things created to use the Sure Petcare
* API. This way, the user credentials may be entered only once.
*
* It also spawns 2 background polling threads to update the more static data (topology) and the pet locations at
* different time intervals.
*
* @author Rene Scherer - Initial Contribution
*/
@NonNullByDefault
public class SurePetcareBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(SurePetcareBridgeHandler.class);
private final SurePetcareAPIHelper petcareAPI;
private @Nullable ScheduledFuture<?> topologyPollingJob;
private @Nullable ScheduledFuture<?> petStatusPollingJob;
public SurePetcareBridgeHandler(Bridge bridge, SurePetcareAPIHelper petcareAPI) {
super(bridge);
this.petcareAPI = petcareAPI;
}
@Override
public void initialize() {
logger.debug("Initializing Sure Petcare bridge handler.");
SurePetcareBridgeConfiguration config = getConfigAs(SurePetcareBridgeConfiguration.class);
if (config.username != null && config.password != null) {
updateStatus(ThingStatus.UNKNOWN);
try {
logger.debug("Login to SurePetcare API with username: {}", config.username);
petcareAPI.login(config.username, config.password);
logger.debug("Login successful, updating topology cache");
petcareAPI.updateTopologyCache();
logger.debug("Cache update successful, setting bridge status to ONLINE");
updateStatus(ThingStatus.ONLINE);
updateThings();
} catch (AuthenticationException e) {
logger.debug("Authentication exception during initializing", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-authentication");
return;
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-missing-username-or-password");
return;
}
ScheduledFuture<?> job = topologyPollingJob;
if (job == null || job.isCancelled()) {
topologyPollingJob = scheduler.scheduleWithFixedDelay(() -> {
petcareAPI.updateTopologyCache();
updateThings();
}, config.refreshIntervalTopology, config.refreshIntervalTopology, TimeUnit.SECONDS);
logger.debug("Bridge topology polling job every {} seconds", config.refreshIntervalTopology);
}
job = petStatusPollingJob;
if (job == null || job.isCancelled()) {
petStatusPollingJob = scheduler.scheduleWithFixedDelay(this::pollAndUpdatePetStatus,
config.refreshIntervalStatus, config.refreshIntervalStatus, TimeUnit.SECONDS);
logger.debug("Pet status polling job every {} seconds", config.refreshIntervalStatus);
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(SurePetcareDiscoveryService.class);
}
@Override
public void dispose() {
ScheduledFuture<?> job = topologyPollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
topologyPollingJob = null;
logger.debug("Stopped topology background polling process");
}
job = petStatusPollingJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
petStatusPollingJob = null;
logger.debug("Stopped pet status background polling process");
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
updateState(BRIDGE_CHANNEL_REFRESH, OnOffType.OFF);
} else {
switch (channelUID.getId()) {
case BRIDGE_CHANNEL_REFRESH:
if (OnOffType.ON.equals(command)) {
petcareAPI.updateTopologyCache();
updateThings();
updateState(BRIDGE_CHANNEL_REFRESH, OnOffType.OFF);
}
break;
}
}
}
public ThingUID getUID() {
return thing.getUID();
}
public Iterable<SurePetcareHousehold> listHouseholds() {
return petcareAPI.getTopology().households;
}
public Iterable<SurePetcarePet> listPets() {
return petcareAPI.getTopology().pets;
}
public Iterable<SurePetcareDevice> listDevices() {
return petcareAPI.getTopology().devices;
}
protected synchronized void updateThings() {
logger.debug("Updating {} connected things", getThing().getThings().size());
// update existing things
for (Thing th : getThing().getThings()) {
String tid = th.getUID().getId();
Map<String, String> properties = null;
ThingHandler handler = th.getHandler();
if (handler instanceof SurePetcarePetHandler) {
((SurePetcarePetHandler) handler).updateThing();
SurePetcarePet pet = petcareAPI.getTopology().getById(petcareAPI.getTopology().pets, tid);
if (pet != null) {
properties = pet.getThingProperties();
}
} else if (handler instanceof SurePetcareHouseholdHandler) {
((SurePetcareHouseholdHandler) handler).updateThing();
SurePetcareHousehold household = petcareAPI.getTopology().getById(petcareAPI.getTopology().households,
tid);
if (household != null) {
properties = household.getThingProperties();
}
} else if (handler instanceof SurePetcareDeviceHandler) {
((SurePetcareDeviceHandler) handler).updateThing();
SurePetcareDevice device = petcareAPI.getTopology().getById(petcareAPI.getTopology().devices, tid);
if (device != null) {
properties = device.getThingProperties();
}
}
if ((properties != null) && (handler instanceof SurePetcareBaseObjectHandler)) {
((SurePetcareBaseObjectHandler) handler).updateProperties(properties);
}
}
}
private synchronized void pollAndUpdatePetStatus() {
petcareAPI.updatePetStatus();
for (Thing th : getThing().getThings()) {
if (th.getThingTypeUID().equals(THING_TYPE_PET)) {
ThingHandler handler = th.getHandler();
if (handler != null) {
((SurePetcarePetHandler) handler).updateThing();
}
}
}
}
}

View File

@ -0,0 +1,257 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.handler;
import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import javax.measure.quantity.Mass;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.surepetcare.internal.SurePetcareAPIHelper;
import org.openhab.binding.surepetcare.internal.SurePetcareApiException;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceControl;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceControl.Bowls.BowlSettings;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfew;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SurePetcareDeviceHandler} is responsible for handling hubs and pet flaps created to represent Sure Petcare
* devices.
*
* @author Rene Scherer - Initial Contribution
* @author Holger Eisold - Added pet feeder status
*/
@NonNullByDefault
public class SurePetcareDeviceHandler extends SurePetcareBaseObjectHandler {
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final float BATTERY_FULL_VOLTAGE = 4 * 1.5f; // 4x AA batteries of 1.5V each
private static final float BATTERY_EMPTY_VOLTAGE = 4.2f; // 4x AA batteries of 1.05V each
private static final float LOW_BATTERY_THRESHOLD = 4 * 1.1f;
private final Logger logger = LoggerFactory.getLogger(SurePetcareDeviceHandler.class);
public SurePetcareDeviceHandler(Thing thing, SurePetcareAPIHelper petcareAPI) {
super(thing, petcareAPI);
logger.debug("Created device handler for type {}", thing.getThingTypeUID().getAsString());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
updateThingCache.getValue();
} else if (channelUID.getId().startsWith(DEVICE_CHANNEL_CURFEW_BASE)) {
handleCurfewCommand(channelUID, command);
} else {
switch (channelUID.getId()) {
case DEVICE_CHANNEL_LOCKING_MODE:
if (command instanceof StringType) {
synchronized (petcareAPI) {
SurePetcareDevice device = petcareAPI.getDevice(thing.getUID().getId());
if (device != null) {
String newLockingModeIdStr = ((StringType) command).toString();
try {
Integer newLockingModeId = Integer.valueOf(newLockingModeIdStr);
petcareAPI.setDeviceLockingMode(device, newLockingModeId);
updateState(DEVICE_CHANNEL_LOCKING_MODE,
new StringType(device.status.locking.modeId.toString()));
} catch (NumberFormatException e) {
logger.warn("Invalid locking mode: {}, ignoring command", newLockingModeIdStr);
} catch (SurePetcareApiException e) {
logger.warn(
"Error from SurePetcare API. Can't update locking mode {} for device {}",
newLockingModeIdStr, device, e);
}
}
}
}
break;
case DEVICE_CHANNEL_LED_MODE:
if (command instanceof StringType) {
synchronized (petcareAPI) {
SurePetcareDevice device = petcareAPI.getDevice(thing.getUID().getId());
if (device != null) {
String newLedModeIdStr = command.toString();
try {
Integer newLedModeId = Integer.valueOf(newLedModeIdStr);
petcareAPI.setDeviceLedMode(device, newLedModeId);
updateState(DEVICE_CHANNEL_LOCKING_MODE,
new StringType(device.status.ledModeId.toString()));
} catch (NumberFormatException e) {
logger.warn("Invalid locking mode: {}, ignoring command", newLedModeIdStr);
} catch (SurePetcareApiException e) {
logger.warn("Error from SurePetcare API. Can't update LED mode {} for device {}",
newLedModeIdStr, device, e);
}
}
}
}
break;
default:
logger.warn("Update on unsupported channel {}, ignoring command", channelUID.getId());
}
}
}
@Override
protected void updateThing() {
SurePetcareDevice device = petcareAPI.getDevice(thing.getUID().getId());
if (device != null) {
logger.debug("Updating all thing channels for device : {}", device);
updateState(DEVICE_CHANNEL_ID, new DecimalType(device.id));
updateState(DEVICE_CHANNEL_NAME, new StringType(device.name));
updateState(DEVICE_CHANNEL_PRODUCT, new StringType(device.productId.toString()));
updateState(DEVICE_CHANNEL_ONLINE, OnOffType.from(device.status.online));
if (thing.getThingTypeUID().equals(THING_TYPE_HUB_DEVICE)) {
updateState(DEVICE_CHANNEL_LED_MODE, new StringType(device.status.ledModeId.toString()));
updateState(DEVICE_CHANNEL_PAIRING_MODE, new StringType(device.status.pairingModeId.toString()));
} else {
float batVol = device.status.battery;
updateState(DEVICE_CHANNEL_BATTERY_VOLTAGE, new DecimalType(batVol));
updateState(DEVICE_CHANNEL_BATTERY_LEVEL, new DecimalType(Math.min(
(batVol - BATTERY_EMPTY_VOLTAGE) / (BATTERY_FULL_VOLTAGE - BATTERY_EMPTY_VOLTAGE) * 100.0f,
100.0f)));
updateState(DEVICE_CHANNEL_LOW_BATTERY, OnOffType.from(batVol < LOW_BATTERY_THRESHOLD));
updateState(DEVICE_CHANNEL_DEVICE_RSSI, new DecimalType(device.status.signal.deviceRssi));
updateState(DEVICE_CHANNEL_HUB_RSSI, new DecimalType(device.status.signal.hubRssi));
if (thing.getThingTypeUID().equals(THING_TYPE_FLAP_DEVICE)) {
updateThingCurfews(device);
updateState(DEVICE_CHANNEL_LOCKING_MODE, new StringType(device.status.locking.modeId.toString()));
} else if (thing.getThingTypeUID().equals(THING_TYPE_FEEDER_DEVICE)) {
int bowlId = device.control.bowls.bowlId;
List<BowlSettings> bowlSettings = device.control.bowls.bowlSettings;
int numBowls = bowlSettings.size();
if (numBowls > 0) {
if (bowlId == BOWL_ID_ONE_BOWL_USED) {
updateState(DEVICE_CHANNEL_BOWLS_FOOD,
new StringType(bowlSettings.get(0).foodId.toString()));
updateState(DEVICE_CHANNEL_BOWLS_TARGET, new QuantityType<Mass>(
device.control.bowls.bowlSettings.get(0).targetId, SIUnits.GRAM));
} else if (bowlId == BOWL_ID_TWO_BOWLS_USED) {
updateState(DEVICE_CHANNEL_BOWLS_FOOD_LEFT,
new StringType(bowlSettings.get(0).foodId.toString()));
updateState(DEVICE_CHANNEL_BOWLS_TARGET_LEFT,
new QuantityType<Mass>(bowlSettings.get(0).targetId, SIUnits.GRAM));
if (numBowls > 1) {
updateState(DEVICE_CHANNEL_BOWLS_FOOD_RIGHT,
new StringType(bowlSettings.get(1).foodId.toString()));
updateState(DEVICE_CHANNEL_BOWLS_TARGET_RIGHT,
new QuantityType<Mass>(bowlSettings.get(1).targetId, SIUnits.GRAM));
}
}
}
updateState(DEVICE_CHANNEL_BOWLS, new StringType(device.control.bowls.bowlId.toString()));
updateState(DEVICE_CHANNEL_BOWLS_CLOSE_DELAY,
new StringType(device.control.lid.closeDelayId.toString()));
updateState(DEVICE_CHANNEL_BOWLS_TRAINING_MODE,
new StringType(device.control.trainingModeId.toString()));
} else {
logger.warn("Unknown product type for device {}", thing.getUID().getAsString());
}
}
}
}
private void updateThingCurfews(SurePetcareDevice device) {
for (int i = 0; i < FLAP_MAX_NUMBER_OF_CURFEWS; i++) {
SurePetcareDeviceCurfew curfew = device.control.curfewList.get(i);
logger.debug("updateThingCurfews - Updating device curfew: {}", curfew.toString());
updateState(DEVICE_CHANNEL_CURFEW_ENABLED + (i + 1), OnOffType.from(curfew.enabled));
updateState(DEVICE_CHANNEL_CURFEW_LOCK_TIME + (i + 1),
new StringType(TIME_FORMATTER.format(curfew.lockTime)));
updateState(DEVICE_CHANNEL_CURFEW_UNLOCK_TIME + (i + 1),
new StringType(TIME_FORMATTER.format(curfew.unlockTime)));
}
}
private void handleCurfewCommand(ChannelUID channelUID, Command command) {
String channelUIDBase = channelUID.getIdWithoutGroup().substring(0,
channelUID.getIdWithoutGroup().length() - 1);
int slot = Integer.parseInt(channelUID.getAsString().substring(channelUID.getAsString().length() - 1));
synchronized (petcareAPI) {
SurePetcareDevice device = petcareAPI.getDevice(thing.getUID().getId());
if (device != null) {
try {
SurePetcareDeviceControl existingControl = device.control;
SurePetcareDeviceCurfew curfew = existingControl.curfewList.get(slot - 1);
boolean requiresUpdate = false;
switch (channelUIDBase) {
case DEVICE_CHANNEL_CURFEW_ENABLED:
if (command instanceof OnOffType) {
if (curfew.enabled != command.equals(OnOffType.ON)) {
logger.debug("Enabling curfew slot: {}", slot);
requiresUpdate = true;
}
curfew.enabled = (command.equals(OnOffType.ON));
}
break;
case DEVICE_CHANNEL_CURFEW_LOCK_TIME:
LocalTime newLockTime = LocalTime.parse(command.toString(), TIME_FORMATTER);
if (!(curfew.lockTime.equals(newLockTime)) && curfew.enabled) {
logger.debug("Changing curfew slot {} lock time to: {}", slot, newLockTime);
requiresUpdate = true;
}
curfew.lockTime = newLockTime;
break;
case DEVICE_CHANNEL_CURFEW_UNLOCK_TIME:
LocalTime newUnlockTime = LocalTime.parse(command.toString(), TIME_FORMATTER);
if (!(curfew.unlockTime.equals(newUnlockTime)) && curfew.enabled) {
logger.debug("Changing curfew slot {} unlock time to: {}", slot, newUnlockTime);
requiresUpdate = true;
}
curfew.unlockTime = newUnlockTime;
break;
default:
break;
}
if (requiresUpdate) {
try {
logger.debug("Updating curfews: {}", existingControl.curfewList.toString());
petcareAPI.setCurfews(device, existingControl.curfewList);
updateThingCurfews(device);
} catch (SurePetcareApiException e) {
logger.warn("Error from SurePetcare API. Can't update curfews for device {}: {}", device,
e.getMessage());
}
}
} catch (DateTimeParseException e) {
logger.warn("Incorrect curfew time format HH:mm: {}", e.getMessage());
}
}
}
}
}

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.handler;
import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.surepetcare.internal.SurePetcareAPIHelper;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareHousehold;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Thing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SurePetcareHouseholdHandler} is responsible for handling things created to represent Sure Petcare
* households.
*
* @author Rene Scherer - Initial Contribution
*/
@NonNullByDefault
public class SurePetcareHouseholdHandler extends SurePetcareBaseObjectHandler {
private final Logger logger = LoggerFactory.getLogger(SurePetcareHouseholdHandler.class);
public SurePetcareHouseholdHandler(Thing thing, SurePetcareAPIHelper petcareAPI) {
super(thing, petcareAPI);
}
@Override
protected void updateThing() {
SurePetcareHousehold household = petcareAPI.getHousehold(thing.getUID().getId());
if (household != null) {
logger.debug("Updating all thing channels for household : {}", household);
updateState(HOUSEHOLD_CHANNEL_ID, new DecimalType(household.id));
updateState(HOUSEHOLD_CHANNEL_NAME, new StringType(household.name));
updateState(HOUSEHOLD_CHANNEL_TIMEZONE_ID, new DecimalType(household.timezoneId));
} else {
logger.debug("Trying to update unknown household: {}", thing.getUID().getId());
}
}
}

View File

@ -0,0 +1,250 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.handler;
import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
import java.io.IOException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import javax.measure.quantity.Mass;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.surepetcare.internal.SurePetcareAPIHelper;
import org.openhab.binding.surepetcare.internal.SurePetcareApiException;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareHousehold;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePet;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePetActivity;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePetFeeding;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareTag;
import org.openhab.core.cache.ByteArrayFileCache;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SurePetcarePetHandler} is responsible for handling the things created to represent Sure Petcare pets.
*
* @author Rene Scherer - Initial Contribution
* @author Holger Eisold - Added pet feeder status, location time offset
*/
@NonNullByDefault
public class SurePetcarePetHandler extends SurePetcareBaseObjectHandler {
private final Logger logger = LoggerFactory.getLogger(SurePetcarePetHandler.class);
private static final ByteArrayFileCache IMAGE_CACHE = new ByteArrayFileCache("org.openhab.binding.surepetcare");
public SurePetcarePetHandler(Thing thing, SurePetcareAPIHelper petcareAPI) {
super(thing, petcareAPI);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
updateThingCache.getValue();
} else {
switch (channelUID.getId()) {
case PET_CHANNEL_LOCATION:
logger.debug("Received location update command: {}", command.toString());
if (command instanceof StringType) {
synchronized (petcareAPI) {
SurePetcarePet pet = petcareAPI.getPet(thing.getUID().getId());
if (pet != null) {
String newLocationIdStr = ((StringType) command).toString();
try {
Integer newLocationId = Integer.valueOf(newLocationIdStr);
// Only update if location has changed. (Needed for Group:Switch item)
if ((pet.status.activity.where.equals(newLocationId)) || newLocationId.equals(0)) {
logger.debug("Location has not changed, skip pet id: {} with loc id: {}",
pet.id, newLocationId);
} else {
logger.debug("Received new location: {}", newLocationId);
petcareAPI.setPetLocation(pet, newLocationId, ZonedDateTime.now());
updateState(PET_CHANNEL_LOCATION,
new StringType(pet.status.activity.where.toString()));
updateState(PET_CHANNEL_LOCATION_CHANGED,
new DateTimeType(pet.status.activity.since));
}
} catch (NumberFormatException e) {
logger.warn("Invalid location id: {}, ignoring command", newLocationIdStr, e);
} catch (SurePetcareApiException e) {
logger.warn("Error from SurePetcare API. Can't update location {} for pet {}",
newLocationIdStr, pet, e);
}
}
}
}
break;
case PET_CHANNEL_LOCATION_TIMEOFFSET:
logger.debug("Received location time offset update command: {}", command.toString());
if (command instanceof StringType) {
synchronized (petcareAPI) {
SurePetcarePet pet = petcareAPI.getPet(thing.getUID().getId());
if (pet != null) {
String commandIdStr = ((StringType) command).toString();
try {
Integer commandId = Integer.valueOf(commandIdStr);
Integer currentLocation = pet.status.activity.where;
logger.debug("Received new location: {}", currentLocation == 1 ? 2 : 1);
// We set the location to the opposite state.
// We also set location to INSIDE (1) if currentLocation is Unknown (0)
if (commandId == 10 || commandId == 30 || commandId == 60) {
ZonedDateTime time = ZonedDateTime.now().minusMinutes(commandId);
petcareAPI.setPetLocation(pet, currentLocation == 1 ? 2 : 1, time);
}
updateState(PET_CHANNEL_LOCATION,
new StringType(pet.status.activity.where.toString()));
updateState(PET_CHANNEL_LOCATION_CHANGED,
new DateTimeType(pet.status.activity.since));
updateState(PET_CHANNEL_LOCATION_TIMEOFFSET, UnDefType.UNDEF);
} catch (NumberFormatException e) {
logger.warn("Invalid location id: {}, ignoring command", commandIdStr, e);
} catch (SurePetcareApiException e) {
logger.warn("Error from SurePetcare API. Can't update location {} for pet {}",
commandIdStr, pet, e);
}
}
}
}
break;
default:
logger.warn("Update on unsupported channel {}", channelUID.getId());
}
}
}
@Override
protected void updateThing() {
synchronized (petcareAPI) {
SurePetcarePet pet = petcareAPI.getPet(thing.getUID().getId());
if (pet != null) {
logger.debug("Updating all thing channels for pet : {}", pet);
updateState(PET_CHANNEL_ID, new DecimalType(pet.id));
updateState(PET_CHANNEL_NAME, pet.name == null ? UnDefType.UNDEF : new StringType(pet.name));
updateState(PET_CHANNEL_COMMENT, pet.comments == null ? UnDefType.UNDEF : new StringType(pet.comments));
updateState(PET_CHANNEL_GENDER,
pet.genderId == null ? UnDefType.UNDEF : new StringType(pet.genderId.toString()));
updateState(PET_CHANNEL_BREED,
pet.breedId == null ? UnDefType.UNDEF : new StringType(pet.breedId.toString()));
updateState(PET_CHANNEL_SPECIES,
pet.speciesId == null ? UnDefType.UNDEF : new StringType(pet.speciesId.toString()));
updateState(PET_CHANNEL_PHOTO,
pet.photo == null ? UnDefType.UNDEF : getPetPhotoFromCache(pet.photo.location));
SurePetcarePetActivity loc = pet.status.activity;
if (loc != null) {
updateState(PET_CHANNEL_LOCATION, new StringType(loc.where.toString()));
if (loc.since != null) {
updateState(PET_CHANNEL_LOCATION_CHANGED, new DateTimeType(loc.since));
}
}
updateState(PET_CHANNEL_DATE_OF_BIRTH, pet.dateOfBirth == null ? UnDefType.UNDEF
: new DateTimeType(pet.dateOfBirth.atStartOfDay(ZoneId.systemDefault())));
updateState(PET_CHANNEL_WEIGHT,
pet.weight == null ? UnDefType.UNDEF : new QuantityType<Mass>(pet.weight, SIUnits.KILOGRAM));
if (pet.tagId != null) {
SurePetcareTag tag = petcareAPI.getTag(pet.tagId.toString());
if (tag != null) {
updateState(PET_CHANNEL_TAG_IDENTIFIER, new StringType(tag.tag));
}
}
if (pet.status.activity.deviceId != null) {
SurePetcareDevice device = petcareAPI.getDevice(pet.status.activity.deviceId.toString());
if (device != null) {
updateState(PET_CHANNEL_LOCATION_CHANGED_THROUGH, new StringType(device.name));
}
} else if (pet.status.activity.userId != null) {
SurePetcareHousehold household = petcareAPI.getHousehold(pet.householdId.toString());
if (household != null) {
Long userId = pet.status.activity.userId;
household.users.stream().map(user -> user.user).filter(user -> userId.equals(user.userId))
.forEach(user -> updateState(PET_CHANNEL_LOCATION_CHANGED_THROUGH,
new StringType(user.userName)));
}
}
SurePetcarePetFeeding feeding = pet.status.feeding;
if (feeding != null) {
SurePetcareDevice device = petcareAPI.getDevice(feeding.deviceId.toString());
if (device != null) {
updateState(PET_CHANNEL_FEEDER_DEVICE, new StringType(device.name));
int bowlId = device.control.bowls.bowlId;
int numBowls = feeding.feedChange.size();
if (numBowls > 0) {
if (bowlId == BOWL_ID_ONE_BOWL_USED) {
updateState(PET_CHANNEL_FEEDER_LAST_CHANGE,
new QuantityType<Mass>(feeding.feedChange.get(0), SIUnits.GRAM));
} else if (bowlId == BOWL_ID_TWO_BOWLS_USED) {
updateState(PET_CHANNEL_FEEDER_LAST_CHANGE_LEFT,
new QuantityType<Mass>(feeding.feedChange.get(0), SIUnits.GRAM));
if (numBowls > 1) {
updateState(PET_CHANNEL_FEEDER_LAST_CHANGE_RIGHT,
new QuantityType<Mass>(feeding.feedChange.get(1), SIUnits.GRAM));
}
}
}
updateState(PET_CHANNEL_FEEDER_LASTFEEDING, new DateTimeType(feeding.feedChangeAt));
}
}
} else {
logger.debug("Trying to update unknown pet: {}", thing.getUID().getId());
}
}
}
/**
* Tries to lookup image in cache. If not found, it tries to download the image from its URL.
*
* @param url the url of the pet photo
* @return the pet image as {@link RawType} or UNDEF
*/
private State getPetPhotoFromCache(@Nullable String url) {
if (url == null) {
return UnDefType.UNDEF;
}
if (IMAGE_CACHE.containsKey(url)) {
try {
byte[] bytes = IMAGE_CACHE.get(url);
String contentType = HttpUtil.guessContentTypeFromData(bytes);
return new RawType(bytes,
contentType == null || contentType.isEmpty() ? RawType.DEFAULT_MIME_TYPE : contentType);
} catch (IOException e) {
logger.trace("Failed to download the content of URL '{}'", url, e);
}
} else {
// photo is not yet in cache, download and add
RawType image = HttpUtil.downloadImage(url);
if (image != null) {
IMAGE_CACHE.put(url, image.getBytes());
return image;
}
}
return UnDefType.UNDEF;
}
}

View File

@ -0,0 +1,88 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.utils;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* GSON serialiser/deserialiser for converting {@link LocalDate} objects.
*
* @author Rene Scherer - Initial Contribution
*/
@NonNullByDefault
public class GsonLocalDateTypeAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
private static final DateTimeFormatter ZONED_DATETIME_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
/**
* Gson invokes this call-back method during serialization when it encounters a field of the
* specified type.
* <p>
*
* In the implementation of this call-back method, you should consider invoking
* {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
* non-trivial field of the {@code src} object. However, you should never invoke it on the
* {@code src} object itself since that will cause an infinite loop (Gson will call your
* call-back method again).
*
* @param src the object that needs to be converted to Json.
* @param typeOfSrc the actual type (fully genericized version) of the source object.
* @return a JsonElement corresponding to the specified object.
*/
@Override
public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(LOCAL_DATE_FORMATTER.format(src));
}
/**
* Gson invokes this call-back method during deserialization when it encounters a field of the
* specified type.
* <p>
*
* In the implementation of this call-back method, you should consider invoking
* {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
* for any non-trivial field of the returned object. However, you should never invoke it on the
* the same type passing {@code json} since that will cause an infinite loop (Gson will call your
* call-back method again).
*
* @param json The Json data being deserialized
* @param typeOfT The type of the Object to deserialize to
* @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
* @throws JsonParseException if json is not in the expected format of {@code typeOfT}
*/
@Override
public @Nullable LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
String el = json.getAsString();
// we allow dates to be represented as dates only or with time and timezone offset
if (el.length() > 10) {
return ZONED_DATETIME_FORMATTER.parse(el, LocalDate::from);
} else {
return LOCAL_DATE_FORMATTER.parse(el, LocalDate::from);
}
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.utils;
import java.lang.reflect.Type;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* GSON serialiser/deserialiser for converting {@link LocalTime} objects.
*
* @author Rene Scherer - Initial Contribution
*/
@NonNullByDefault
public class GsonLocalTimeTypeAdapter implements JsonSerializer<LocalTime>, JsonDeserializer<LocalTime> {
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
/**
* Gson invokes this call-back method during serialization when it encounters a field of the
* specified type.
* <p>
*
* In the implementation of this call-back method, you should consider invoking
* {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
* non-trivial field of the {@code src} object. However, you should never invoke it on the
* {@code src} object itself since that will cause an infinite loop (Gson will call your
* call-back method again).
*
* @param src the object that needs to be converted to Json.
* @param typeOfSrc the actual type (fully genericized version) of the source object.
* @return a JsonElement corresponding to the specified object.
*/
@Override
public JsonElement serialize(LocalTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(TIME_FORMATTER.format(src));
}
/**
* Gson invokes this call-back method during deserialization when it encounters a field of the
* specified type.
* <p>
*
* In the implementation of this call-back method, you should consider invoking
* {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
* for any non-trivial field of the returned object. However, you should never invoke it on the
* the same type passing {@code json} since that will cause an infinite loop (Gson will call your
* call-back method again).
*
* @param json The Json data being deserialized
* @param typeOfT The type of the Object to deserialize to
* @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
* @throws JsonParseException if json is not in the expected format of {@code typeOfT}
*/
@Override
public @Nullable LocalTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return TIME_FORMATTER.parse(json.getAsString(), LocalTime::from);
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.utils;
import java.lang.reflect.Type;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* GSON serialiser/deserialiser for converting {@link ZonedDateTime} objects.
*
* @author Rene Scherer - Initial Contribution
*/
@NonNullByDefault
public class GsonZonedDateTimeTypeAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> {
private static final DateTimeFormatter ZONED_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx");
/**
* Gson invokes this call-back method during serialization when it encounters a field of the
* specified type.
* <p>
*
* In the implementation of this call-back method, you should consider invoking
* {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
* non-trivial field of the {@code src} object. However, you should never invoke it on the
* {@code src} object itself since that will cause an infinite loop (Gson will call your
* call-back method again).
*
* @param src the object that needs to be converted to Json.
* @param typeOfSrc the actual type (fully genericized version) of the source object.
* @return a JsonElement corresponding to the specified object.
*/
@Override
public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(ZONED_FORMATTER.format(src));
}
/**
* Gson invokes this call-back method during deserialization when it encounters a field of the
* specified type.
* <p>
*
* In the implementation of this call-back method, you should consider invoking
* {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
* for any non-trivial field of the returned object. However, you should never invoke it on the
* the same type passing {@code json} since that will cause an infinite loop (Gson will call your
* call-back method again).
*
* @param json The Json data being deserialized
* @param typeOfT The type of the Object to deserialize to
* @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
* @throws JsonParseException if json is not in the expected format of {@code typeOfT}
*/
@Override
public @Nullable ZonedDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return ZONED_FORMATTER.parse(json.getAsString(), ZonedDateTime::from);
}
}

View File

@ -0,0 +1,123 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.utils;
import java.io.IOException;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfew;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfewList;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.gson.stream.MalformedJsonException;
/**
* The {@link GsonColonDateTypeAdapter} class is a custom TypeAdapter factory to ensure deserialization always returns a
* list even if the Json document contains only a single curfew object and not an array.
*
* See https://stackoverflow.com/questions/43412261/make-gson-accept-single-objects-where-it-expects-arrays
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public final class SurePetcareDeviceCurfewListTypeAdapterFactory<E> implements TypeAdapterFactory {
// Gson can instantiate it itself
private SurePetcareDeviceCurfewListTypeAdapterFactory() {
}
@Override
public @Nullable <T> TypeAdapter<T> create(final @Nullable Gson gson, final @Nullable TypeToken<T> typeToken) {
if (gson == null || typeToken == null) {
return null;
}
final TypeAdapter<SurePetcareDeviceCurfew> elementTypeAdapter = gson
.getAdapter(TypeToken.get(SurePetcareDeviceCurfew.class));
@SuppressWarnings("unchecked")
final TypeAdapter<T> alwaysListTypeAdapter = (TypeAdapter<T>) new SurePetcareDeviceCurfewListTypeAdapter(
elementTypeAdapter).nullSafe();
return alwaysListTypeAdapter;
}
private static final class SurePetcareDeviceCurfewListTypeAdapter
extends TypeAdapter<List<SurePetcareDeviceCurfew>> {
private final TypeAdapter<SurePetcareDeviceCurfew> elementTypeAdapter;
private SurePetcareDeviceCurfewListTypeAdapter(final TypeAdapter<SurePetcareDeviceCurfew> elementTypeAdapter) {
this.elementTypeAdapter = elementTypeAdapter;
}
@Override
public void write(final @Nullable JsonWriter out, @Nullable final List<SurePetcareDeviceCurfew> list)
throws IOException {
if (out != null && list != null) {
out.beginArray();
for (SurePetcareDeviceCurfew curfew : list) {
elementTypeAdapter.write(out, curfew);
}
out.endArray();
}
}
@Override
public @Nullable List<SurePetcareDeviceCurfew> read(final @Nullable JsonReader in) throws IOException {
// This is where we detect the list "type"
final SurePetcareDeviceCurfewList list = new SurePetcareDeviceCurfewList();
if (in != null) {
final JsonToken token = in.peek();
switch (token) {
case BEGIN_ARRAY:
// If it's a regular list, just consume [, <all elements>, and ]
in.beginArray();
while (in.hasNext()) {
SurePetcareDeviceCurfew cf = elementTypeAdapter.read(in);
if (cf != null) {
list.add(cf);
}
}
in.endArray();
break;
case BEGIN_OBJECT:
SurePetcareDeviceCurfew cf = elementTypeAdapter.read(in);
if (cf != null) {
list.add(cf);
}
break;
case NULL:
throw new AssertionError(
"Must never happen: check if the type adapter configured with .nullSafe()");
case STRING:
case NUMBER:
case BOOLEAN:
case NAME:
case END_ARRAY:
case END_OBJECT:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
default:
throw new AssertionError("Must never happen: " + token);
}
}
return list;
}
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<binding:binding id="surepetcare" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Sure Petcare Binding</name>
<description>This binding interacts with the Sure Petcare Connect range of cat flaps and feeders</description>
<author>Rene Scherer, Holger Eisold</author>
</binding:binding>

View File

@ -0,0 +1,266 @@
binding.surepetcare.name = Sure Petcare Binding
binding.surepetcare.description = This binding interacts with the Sure Petcare Connect range of cat/pet flaps and feeders
# bridge-types
thing-type.surepetcare.bridge.label = Sure Petcare API Bridge
thing-type.surepetcare.bridge.description = The Sure Petcare Cloud API Bridge.
# bridge types config
thing-type.config.surepetcare.bridge.username.label = Sure Petcare Username (Email)
thing-type.config.surepetcare.bridge.username.description = The username to access the Sure Petcare API.
thing-type.config.surepetcare.bridge.password.label = Sure Petcare Password
thing-type.config.surepetcare.bridge.password.description = The password to access the Sure Petcare API.
thing-type.config.surepetcare.bridge.refreshIntervalStatus.label = Refresh Interval Pet Status
thing-type.config.surepetcare.bridge.refreshIntervalStatus.description = Query interval (in secs) for pet status. (min. 300 / default 300).
thing-type.config.surepetcare.bridge.refreshIntervalTopology.label = Refresh Interval Topology
thing-type.config.surepetcare.bridge.refreshIntervalTopology.description = Query interval (in secs) for device topology. (min 300 / default 36000).
# thing-types
thing-type.surepetcare.household.label = Sure Petcare Household
thing-type.surepetcare.household.description = A Sure Petcare household.
thing-type.surepetcare.hubDevice.label = Sure Petcare Hub Device
thing-type.surepetcare.hubDevice.description = A Sure Petcare hub device (connects cat/pet flaps, feeders etc. to a network).
thing-type.surepetcare.flapDevice.label = Sure Petcare Flap Device
thing-type.surepetcare.flapDevice.description = A Sure Petcare cat or pet flap device.
thing-type.surepetcare.feederDevice.label = Sure Petcare Pet Feeder
thing-type.surepetcare.feederDevice.description = A Sure Petcare pet feeder device.
thing-type.surepetcare.pet.label = Sure Petcare Pet
thing-type.surepetcare.pet.description = A Sure Petcare pet.
# channel-types
channel-type.surepetcare.refreshType.label = Refresh
channel-type.surepetcare.refreshType.description = Triggers a cache refresh of everything (devices, pets etc.).
channel-type.surepetcare.idType.label = ID
channel-type.surepetcare.idType.description = A unique id of the thing.
channel-type.surepetcare.nameType.label = Name
channel-type.surepetcare.nameType.description = The name of the thing.
channel-type.surepetcare.commentType.label = Comments
channel-type.surepetcare.commentType.description = Comments about the thing.
channel-type.surepetcare.timezoneIdType.label = Timezone ID
channel-type.surepetcare.timezoneIdType.description = The identifier of the timezone.
channel-type.surepetcare.productType.label = Product Type
channel-type.surepetcare.productType.description = The type of product this device represents. (0=Unknown, 1=Hub, 3=Pet Flap, 4=Feeder, 6=Cat Flap)
channel-type.surepetcare.productType.state.option.0 = Unknown
channel-type.surepetcare.productType.state.option.1 = Hub
channel-type.surepetcare.productType.state.option.3 = Pet Flap
channel-type.surepetcare.productType.state.option.4 = Pet Feeder
channel-type.surepetcare.productType.state.option.6 = Cat Flap
channel-type.surepetcare.shareCodeType.label = Share Code
channel-type.surepetcare.shareCodeType.description = A unique code provided by Sure Petcare to access photos of pets.
channel-type.surepetcare.genderType.label = Gender
channel-type.surepetcare.genderType.description = The gender of the pet.
channel-type.surepetcare.genderType.state.option.0 = Female
channel-type.surepetcare.genderType.state.option.1 = Male
channel-type.surepetcare.breedType.label = Breed
channel-type.surepetcare.breedType.description = The breed of the pet.
channel-type.surepetcare.breedType.state.option.333 = Abyssinian
channel-type.surepetcare.breedType.state.option.334 = American Bobtail
channel-type.surepetcare.breedType.state.option.335 = American Curl
channel-type.surepetcare.breedType.state.option.336 = American Shorthair
channel-type.surepetcare.breedType.state.option.337 = American Wirehair
channel-type.surepetcare.breedType.state.option.338 = Balinese
channel-type.surepetcare.breedType.state.option.339 = Bengal
channel-type.surepetcare.breedType.state.option.340 = Birman
channel-type.surepetcare.breedType.state.option.341 = Bombay
channel-type.surepetcare.breedType.state.option.342 = British Shorthair
channel-type.surepetcare.breedType.state.option.343 = Burmese
channel-type.surepetcare.breedType.state.option.344 = Burmilla
channel-type.surepetcare.breedType.state.option.345 = Chartreux
channel-type.surepetcare.breedType.state.option.346 = Chinese Li Hua
channel-type.surepetcare.breedType.state.option.347 = Colorpoint Shorthair
channel-type.surepetcare.breedType.state.option.348 = Cornish Rex
channel-type.surepetcare.breedType.state.option.349 = Cymric
channel-type.surepetcare.breedType.state.option.350 = Devon Rex
channel-type.surepetcare.breedType.state.option.351 = Egyptian Mau
channel-type.surepetcare.breedType.state.option.352 = European Burmese
channel-type.surepetcare.breedType.state.option.353 = Exotic
channel-type.surepetcare.breedType.state.option.354 = Havana Brown
channel-type.surepetcare.breedType.state.option.355 = Himalayan
channel-type.surepetcare.breedType.state.option.356 = Japanese Bobtail
channel-type.surepetcare.breedType.state.option.357 = Javanese
channel-type.surepetcare.breedType.state.option.358 = Korat
channel-type.surepetcare.breedType.state.option.359 = LaPerm
channel-type.surepetcare.breedType.state.option.360 = Maine Coon
channel-type.surepetcare.breedType.state.option.361 = Manx
channel-type.surepetcare.breedType.state.option.362 = Nebelung
channel-type.surepetcare.breedType.state.option.363 = Norwegian Forest
channel-type.surepetcare.breedType.state.option.364 = Ocicat
channel-type.surepetcare.breedType.state.option.365 = Oriental
channel-type.surepetcare.breedType.state.option.366 = Persian
channel-type.surepetcare.breedType.state.option.367 = Ragamuffin
channel-type.surepetcare.breedType.state.option.368 = Ragdoll Cats
channel-type.surepetcare.breedType.state.option.369 = Russisch Blue
channel-type.surepetcare.breedType.state.option.370 = Savannah
channel-type.surepetcare.breedType.state.option.371 = Scottish Fold
channel-type.surepetcare.breedType.state.option.372 = Selkirk Rex
channel-type.surepetcare.breedType.state.option.373 = Siamese Cat
channel-type.surepetcare.breedType.state.option.374 = Siberian
channel-type.surepetcare.breedType.state.option.375 = Singapura
channel-type.surepetcare.breedType.state.option.376 = Snowshoe
channel-type.surepetcare.breedType.state.option.377 = Somali
channel-type.surepetcare.breedType.state.option.378 = Sphynx
channel-type.surepetcare.breedType.state.option.379 = Tonkinese
channel-type.surepetcare.breedType.state.option.380 = Turkish Angora
channel-type.surepetcare.breedType.state.option.381 = Turkish Van
channel-type.surepetcare.breedType.state.option.382 = Domestic Shorthair
channel-type.surepetcare.breedType.state.option.383 = Domestic Longhair
channel-type.surepetcare.breedType.state.option.384 = Moggy
channel-type.surepetcare.locationType.label = Location
channel-type.surepetcare.locationType.description = The current location of the pet (inside or outside).
channel-type.surepetcare.locationType.state.option.0 = Unknown
channel-type.surepetcare.locationType.state.option.1 = Inside
channel-type.surepetcare.locationType.state.option.2 = Outside
channel-type.surepetcare.speciesType.label = Species
channel-type.surepetcare.speciesType.description = The species of the pet.
channel-type.surepetcare.speciesType.state.option.0 = Undefined
channel-type.surepetcare.speciesType.state.option.1 = Cat
channel-type.surepetcare.speciesType.state.option.2 = Dog
channel-type.surepetcare.photoURLType.label = Photo URL
channel-type.surepetcare.photoURLType.description = The URL of the pet photo.
channel-type.surepetcare.tagIdentifierType.label = Micro Chip Tag Identifier
channel-type.surepetcare.tagIdentifierType.description = The unique identifier of the pet micro chip tag.
channel-type.surepetcare.locationChangedType.label = Last Location Change Time
channel-type.surepetcare.locationChangedType.description = The time when the pet location has last changed.
channel-type.surepetcare.locationTimeoffsetType.label = Location Change Time Offset
channel-type.surepetcare.locationTimeoffsetType.description = Time-Command to set the pet location with a time offset. (10, 30 or 60 minutes ago).
channel-type.surepetcare.locationTimeoffsetType.state.option.10 = 10 mins ago
channel-type.surepetcare.locationTimeoffsetType.state.option.30 = 30 mins ago
channel-type.surepetcare.locationTimeoffsetType.state.option.60 = 60 mins ago
channel-type.surepetcare.locationChangedThroughType.label = Last Location Changed By
channel-type.surepetcare.locationChangedThroughType.description = Indicates by which user or device the last location change was made.
channel-type.surepetcare.ledModeType.label = LED Mode
channel-type.surepetcare.ledModeType.description = The current LED mode of the device.
channel-type.surepetcare.ledModeType.state.option.0 = Off
channel-type.surepetcare.ledModeType.state.option.1 = Bright
channel-type.surepetcare.ledModeType.state.option.4 = Dimmed
channel-type.surepetcare.pairingModeType.label = Pairing Mode
channel-type.surepetcare.pairingModeType.description = The current pairing mode of the device.
channel-type.surepetcare.pairingModeType.state.option.0 = Normal Mode
channel-type.surepetcare.pairingModeType.state.option.1 = Pairing Mode
channel-type.surepetcare.onlineType.label = Online State
channel-type.surepetcare.onlineType.description = Indicator if device is online or offline.
channel-type.surepetcare.curfewEnabledType.label = Curfew Enabled
channel-type.surepetcare.curfewEnabledType.description = Indicator if the curfew is enabled.
channel-type.surepetcare.curfewLockTimeType.label = Curfew Lock Time
channel-type.surepetcare.curfewLockTimeType.description = The time when the curfew starts.
channel-type.surepetcare.curfewUnlockTimeType.label = Curfew Unlock Time
channel-type.surepetcare.curfewUnlockTimeType.description = The time when the curfew finishes.
channel-type.surepetcare.dateOfBirthType.label = Pet Birthday
channel-type.surepetcare.dateOfBirthType.description = The pet birthday.
channel-type.surepetcare.weightType.label = Pet Weight
channel-type.surepetcare.weightType.description = The pet weight
channel-type.surepetcare.feederDeviceType.label = Pet Feeding Device Name
channel-type.surepetcare.feederDeviceType.description = The pet feeding device name.
channel-type.surepetcare.feederLastFeedingType.label = Pet Last Feeding
channel-type.surepetcare.feederLastFeedingType.description = The last pet feeding.
channel-type.surepetcare.feederLastChangeType.label = Pet Feeding Last Change
channel-type.surepetcare.feederLastChangeType.description = The pet feeding last change.
channel-type.surepetcare.feederLastChangeLeftType.label = Pet Feeding Last Change Left
channel-type.surepetcare.feederLastChangeLeftType.description = The pet feeding last change left.
channel-type.surepetcare.feederLastChangeRightType.label = Pet Feeding Last Change Right
channel-type.surepetcare.feederLastChangeRightType.description = The pet feeding last change right.
channel-type.surepetcare.lockingModeType.label = Locking Mode
channel-type.surepetcare.lockingModeType.description = The id of the locking mode the flap is currently set to (e.g. in/out, in only, out only, locked)
channel-type.surepetcare.lockingModeType.state.option.0 = Unlocked
channel-type.surepetcare.lockingModeType.state.option.1 = Locked In-only
channel-type.surepetcare.lockingModeType.state.option.2 = Locked Out-only
channel-type.surepetcare.lockingModeType.state.option.3 = Locked
channel-type.surepetcare.lockingModeType.state.option.4 = Curfew
channel-type.surepetcare.batteryVoltageType.label = Battery Voltage
channel-type.surepetcare.batteryVoltageType.description = The current battery voltage.
channel-type.surepetcare.rssiDeviceType.label = Signal Strength Device (RSSI)
channel-type.surepetcare.rssiDeviceType.description = The received device signal strength (RSSI).
channel-type.surepetcare.rssiHubType.label = Signal Strength Hub (RSSI)
channel-type.surepetcare.rssiHubType.description = The received hub signal strength (RSSI).
channel-type.surepetcare.bowlsFoodType.label = Bowls Food Type
channel-type.surepetcare.bowlsFoodType.description = The food type of the bowl if the big bowl is used.
channel-type.surepetcare.bowlsFoodType.state.option.1 = Wet Food
channel-type.surepetcare.bowlsFoodType.state.option.2 = Dry Food
channel-type.surepetcare.bowlsFoodType.state.option.3 = Wet & Dry Food
channel-type.surepetcare.bowlsTargetType.label = Bowls Food Type (Big Bowl)
channel-type.surepetcare.bowlsTargetType.description = The weight target of the bowl if the big bowl is used.
channel-type.surepetcare.bowlsFoodLeftType.label = Bowls Food Type Half Bowl (Left)
channel-type.surepetcare.bowlsFoodLeftType.description = The food type of the left bowl if the half bowls are used.
channel-type.surepetcare.bowlsFoodLeftType.state.option.1 = Wet Food
channel-type.surepetcare.bowlsFoodLeftType.state.option.2 = Dry Food
channel-type.surepetcare.bowlsFoodLeftType.state.option.3 = Wet & Dry Food
channel-type.surepetcare.bowlsTargetLeftType.label = Bowls Target Half Bowl (Left)
channel-type.surepetcare.bowlsTargetLeftType.description = The weight target of the left bowl if the half bowls are used.
channel-type.surepetcare.bowlsFoodRightType.label = Bowls Food Type Half Bowl (Right)
channel-type.surepetcare.bowlsFoodRightType.description = The food type of the right bowl if the half bowls are used.
channel-type.surepetcare.bowlsFoodRightType.state.option.1 = Wet Food
channel-type.surepetcare.bowlsFoodRightType.state.option.2 = Dry Food
channel-type.surepetcare.bowlsFoodRightType.state.option.3 = Wet & Dry Food
channel-type.surepetcare.bowlsTargetRightType.label = Bowls Target Half Bowl (Right)
channel-type.surepetcare.bowlsTargetRightType.description = The weight target of the right bowl if the half bowls are used.
channel-type.surepetcare.bowlsType.label = Bowls Type
channel-type.surepetcare.bowlsType.description = The type of used bowls.
channel-type.surepetcare.bowlsType.state.option.0 = Undefined
channel-type.surepetcare.bowlsType.state.option.1 = Big Bowl
channel-type.surepetcare.bowlsType.state.option.4 = Half Bowls
channel-type.surepetcare.bowlsCloseDelayType.label = Feeder Lid Close Delay
channel-type.surepetcare.bowlsCloseDelayType.description = The feeder lid close delay
channel-type.surepetcare.bowlsCloseDelayType.state.option.0 = Fast
channel-type.surepetcare.bowlsCloseDelayType.state.option.4 = Normal
channel-type.surepetcare.bowlsCloseDelayType.state.option.20 = Slow
channel-type.surepetcare.bowlsTrainingModeType.label = Feeder Training Mode
channel-type.surepetcare.bowlsTrainingModeType.description = The feeder training mode.
channel-type.surepetcare.bowlsTrainingModeType.state.option.0 = Off
channel-type.surepetcare.bowlsTrainingModeType.state.option.1 = Full open
channel-type.surepetcare.bowlsTrainingModeType.state.option.2 = Almost full open
channel-type.surepetcare.bowlsTrainingModeType.state.option.3 = Half closed
channel-type.surepetcare.bowlsTrainingModeType.state.option.4 = Almost closed
# Thing status description
offline.conf-error-invalid-refresh-intervals = Invalid refresh interval.
offline.conf-error-missing-username-or-password = Missing username or password.
offline.conf-error-authentication = Authentication Error.

View File

@ -0,0 +1,266 @@
binding.surepetcare.name = Sure Petcare Binding
binding.surepetcare.description = Dieses Binding stellt eine Verbindung mit der Sure Petcare API her.
# bridge-types
thing-type.surepetcare.bridge.label = Sure Petcare API Konto
thing-type.surepetcare.bridge.description = Ermöglicht den Zugriff auf die Sure Petcare API.
# bridge types config
thing-type.config.surepetcare.bridge.username.label = Sure Petcare Benutzername
thing-type.config.surepetcare.bridge.username.description = Sure Petcare Benutzername oder Email-Adresse.
thing-type.config.surepetcare.bridge.password.label = Sure Petcare Passwort
thing-type.config.surepetcare.bridge.password.description = Sure Petcare Passwort.
thing-type.config.surepetcare.bridge.refreshIntervalStatus.label = Abfrageintervall Haustier Status
thing-type.config.surepetcare.bridge.refreshIntervalStatus.description = Intervall zur Abfrage des Haustier Status (in Sekunden) (mind. 300 / standard 36000).
thing-type.config.surepetcare.bridge.refreshIntervalTopology.label = Abfrageintervall Topology
thing-type.config.surepetcare.bridge.refreshIntervalTopology.description = Intervall zur Abfrage der Geräte und Haustier Daten (in Sekunden) (mind. 300 / standard 300).
# thing-types
thing-type.surepetcare.household.label = Sure Petcare Haushalt
thing-type.surepetcare.household.description = Ein Sure Petcare Haushalt.
thing-type.surepetcare.hubDevice.label = Sure Petcare Gerät (Hub)
thing-type.surepetcare.hubDevice.description = Ein Sure Petcare Gerät (Hub).
thing-type.surepetcare.flapDevice.label = Sure Petcare Gerät (Haustier-/Katzenklappe)
thing-type.surepetcare.flapDevice.description = Ein Sure Petcare Gerät (Haustier-/Katzenklappe).
thing-type.surepetcare.feederDevice.label = Sure Petcare Gerät (Futterautomat)
thing-type.surepetcare.feederDevice.description = Sure Petcare Gerät (Futterautomat).
thing-type.surepetcare.pet.label = Sure Petcare Haustier
thing-type.surepetcare.pet.description = Ein Sure Petcare Haustier.
# channel-types
channel-type.surepetcare.refreshType.label = Aktualisieren
channel-type.surepetcare.refreshType.description = Löst die Aktualisierung der Geräte- und Haustierdaten aus.
channel-type.surepetcare.idType.label = ID
channel-type.surepetcare.idType.description = Zeigt die ID an.
channel-type.surepetcare.nameType.label = Name
channel-type.surepetcare.nameType.description = Zeigt den Namen an.
channel-type.surepetcare.commentType.label = Kommentar
channel-type.surepetcare.commentType.description = Zeigt den Kommentar an.
channel-type.surepetcare.timezoneIdType.label = Zeitzone ID
channel-type.surepetcare.timezoneIdType.description = Zeigt die ID der Zeitzone an.
channel-type.surepetcare.productType.label = Produkt Typ
channel-type.surepetcare.productType.description = Zeigt den Produkt Namen an. #(0=unbekannt, 1=Hub, 3=Haustierklappe, 4=Futterautomat, 6=Katzenklappe)
channel-type.surepetcare.productType.state.option.0 = unbekannt
channel-type.surepetcare.productType.state.option.1 = Hub
channel-type.surepetcare.productType.state.option.3 = Haustierklappe
channel-type.surepetcare.productType.state.option.4 = Futterautomat
channel-type.surepetcare.productType.state.option.6 = Katzenklappe
channel-type.surepetcare.shareCodeType.label = Share Code
channel-type.surepetcare.shareCodeType.description = A unique code provided by Sure Petcare to access photos of pets
channel-type.surepetcare.genderType.label = Geschlecht
channel-type.surepetcare.genderType.description = Zeigt das Geschlecht des Haustieres an.
channel-type.surepetcare.genderType.state.option.0 = Weiblich
channel-type.surepetcare.genderType.state.option.1 = Männlich
channel-type.surepetcare.breedType.label = Rasse
channel-type.surepetcare.breedType.description = Zeigt die Rasse des Haustieres an.
channel-type.surepetcare.breedType.state.option.333 = Abessinierkatze
channel-type.surepetcare.breedType.state.option.334 = American Bobtail
channel-type.surepetcare.breedType.state.option.335 = American Curl
channel-type.surepetcare.breedType.state.option.336 = American Shorthair
channel-type.surepetcare.breedType.state.option.337 = American Wirehair
channel-type.surepetcare.breedType.state.option.338 = Balinesenkatze
channel-type.surepetcare.breedType.state.option.339 = Bengal
channel-type.surepetcare.breedType.state.option.340 = Birma-Katze
channel-type.surepetcare.breedType.state.option.341 = Bombay-Katze
channel-type.surepetcare.breedType.state.option.342 = British Kurzhaar
channel-type.surepetcare.breedType.state.option.343 = Burma-Katze
channel-type.surepetcare.breedType.state.option.344 = Burmilla
channel-type.surepetcare.breedType.state.option.345 = Chartreux
channel-type.surepetcare.breedType.state.option.346 = Chinese Li Hua
channel-type.surepetcare.breedType.state.option.347 = Colorpoint Shorthair
channel-type.surepetcare.breedType.state.option.348 = Cornish Rex
channel-type.surepetcare.breedType.state.option.349 = Cymric
channel-type.surepetcare.breedType.state.option.350 = Devon Rex
channel-type.surepetcare.breedType.state.option.351 = Ägyptische Mau
channel-type.surepetcare.breedType.state.option.352 = Burma-Katze
channel-type.surepetcare.breedType.state.option.353 = Exotische Kurzhaarkatze
channel-type.surepetcare.breedType.state.option.354 = Havana-Katze
channel-type.surepetcare.breedType.state.option.355 = Colourpoint
channel-type.surepetcare.breedType.state.option.356 = Japanese Bobtail
channel-type.surepetcare.breedType.state.option.357 = Orientalisch Langhaar
channel-type.surepetcare.breedType.state.option.358 = Korat-Katze
channel-type.surepetcare.breedType.state.option.359 = LaPerm
channel-type.surepetcare.breedType.state.option.360 = Maine Coon
channel-type.surepetcare.breedType.state.option.361 = Manx
channel-type.surepetcare.breedType.state.option.362 = Nebelung-Katze
channel-type.surepetcare.breedType.state.option.363 = Norwegische Waldkatze
channel-type.surepetcare.breedType.state.option.364 = Ocicat
channel-type.surepetcare.breedType.state.option.365 = Orientalisch Kurzhaar
channel-type.surepetcare.breedType.state.option.366 = Persisch
channel-type.surepetcare.breedType.state.option.367 = Perserkatze
channel-type.surepetcare.breedType.state.option.368 = Ragdoll-Katze
channel-type.surepetcare.breedType.state.option.369 = Russisch Blau
channel-type.surepetcare.breedType.state.option.370 = Savannah-Katze
channel-type.surepetcare.breedType.state.option.371 = Schottische Faltohrkatze
channel-type.surepetcare.breedType.state.option.372 = Selkirk Rex
channel-type.surepetcare.breedType.state.option.373 = Siamkatze
channel-type.surepetcare.breedType.state.option.374 = Sibirische Katze
channel-type.surepetcare.breedType.state.option.375 = Singapura
channel-type.surepetcare.breedType.state.option.376 = Snowshoe
channel-type.surepetcare.breedType.state.option.377 = Somali
channel-type.surepetcare.breedType.state.option.378 = Sphynx-Katze
channel-type.surepetcare.breedType.state.option.379 = Tonkanese
channel-type.surepetcare.breedType.state.option.380 = Türkisch Angora
channel-type.surepetcare.breedType.state.option.381 = Türkisch Van
channel-type.surepetcare.breedType.state.option.382 = Kurzhaarige Hauskatze
channel-type.surepetcare.breedType.state.option.383 = Langhaarige Hauskatze
channel-type.surepetcare.breedType.state.option.384 = Wildkatze
channel-type.surepetcare.locationType.label = Standort
channel-type.surepetcare.locationType.description = Zeigt den Standort des Haustieres an.
channel-type.surepetcare.locationType.state.option.0 = Unbekannt
channel-type.surepetcare.locationType.state.option.1 = Im Haus
channel-type.surepetcare.locationType.state.option.2 = Draußen
channel-type.surepetcare.speciesType.label = Tierart
channel-type.surepetcare.speciesType.description = Zeigt die Tierart des Haustieres an.
channel-type.surepetcare.speciesType.state.option.0 = Undefiniert
channel-type.surepetcare.speciesType.state.option.1 = Katze
channel-type.surepetcare.speciesType.state.option.2 = Hund
channel-type.surepetcare.photoURLType.label = Foto URL
channel-type.surepetcare.photoURLType.description = Zeigt die URL des Haustier Fotos an.
channel-type.surepetcare.tagIdentifierType.label = Micro Chip Tag
channel-type.surepetcare.tagIdentifierType.description = Zeigt den Tag des Haustier Microchips an.
channel-type.surepetcare.locationChangedType.label = Standort Zeit
channel-type.surepetcare.locationChangedType.description = Zeigt die Zeit des letzten Standortwechsels an.
channel-type.surepetcare.locationTimeoffsetType.label = Standort Wechsel Zeitversatz
channel-type.surepetcare.locationTimeoffsetType.description = Änderung des Haustier Standortes mit einem Zeitversatz. (vor 10, 30 oder 60 Minuten).
channel-type.surepetcare.locationTimeoffsetType.state.option.10 = vor 10 min
channel-type.surepetcare.locationTimeoffsetType.state.option.30 = vor 30 min
channel-type.surepetcare.locationTimeoffsetType.state.option.60 = vor 60 min
channel-type.surepetcare.locationChangedThroughType.label = Standort geändert durch
channel-type.surepetcare.locationChangedThroughType.description = Zeigt an durch welchen Benutzer oder welches Gerät die letzte Standort-Änderung durchgeführt wurde.
channel-type.surepetcare.ledModeType.label = LED Modus
channel-type.surepetcare.ledModeType.description = Zeigt den LED Modus des Hubs an.
channel-type.surepetcare.ledModeType.state.option.0 = Aus
channel-type.surepetcare.ledModeType.state.option.1 = Hell
channel-type.surepetcare.ledModeType.state.option.4 = Dunkel
channel-type.surepetcare.pairingModeType.label = Paarungs Modus
channel-type.surepetcare.pairingModeType.description = Zeigt den Paarungs Modus des Hubs an.
channel-type.surepetcare.pairingModeType.state.option.0 = Normaler Modus
channel-type.surepetcare.pairingModeType.state.option.1 = Paarungsmodus
channel-type.surepetcare.onlineType.label = Online Status
channel-type.surepetcare.onlineType.description = Zeigt den Online Status des Gerätes an.
channel-type.surepetcare.curfewEnabledType.label = Ausgangssperre aktiv
channel-type.surepetcare.curfewEnabledType.description = Zeigt an ob die Ausgangssperre aktiviert ist.
channel-type.surepetcare.curfewLockTimeType.label = Ausgangssperre Sperrzeit
channel-type.surepetcare.curfewLockTimeType.description = Zeigt die Sperrzeit der Ausgangssperre an.
channel-type.surepetcare.curfewUnlockTimeType.label = Ausgangssperre Entsperrzeit
channel-type.surepetcare.curfewUnlockTimeType.description = Zeigt die Entsperrzeit der Ausgangssperre an.
channel-type.surepetcare.dateOfBirthType.label = Geburtstag
channel-type.surepetcare.dateOfBirthType.description = Zeigt den Geburtstag des Haus an.
channel-type.surepetcare.weightType.label = Gewicht
channel-type.surepetcare.weightType.description = Zeigt das Gewicht des Haustieres an.
channel-type.surepetcare.feederDeviceType.label = Futterautomat Name
channel-type.surepetcare.feederDeviceType.description = Der Name des Futterautomats.
channel-type.surepetcare.feederLastFeedingType.label = Letzte Futteraufnahme
channel-type.surepetcare.feederLastFeedingType.description = Die letzte Futteraufnahme.
channel-type.surepetcare.feederLastChangeType.label = Letzte Futteraufnahme Änderung
channel-type.surepetcare.feederLastChangeType.description = Die letzte Futteraufnahme Änderung.
channel-type.surepetcare.feederLastChangeLeftType.label = Letzte Futteraufnahme Änderung (Links)
channel-type.surepetcare.feederLastChangeLeftType.description = Die letzte Futteraufnahme Änderung (links).
channel-type.surepetcare.feederLastChangeRightType.label = Letzte Futteraufnahme Änderung (Rechts)
channel-type.surepetcare.feederLastChangeRightType.description = Die letzte Futteraufnahme Änderung (rechts).
channel-type.surepetcare.lockingModeType.label = Sperrmodus
channel-type.surepetcare.lockingModeType.description = Zeigt des Sperrmodus des Gerätes an.
channel-type.surepetcare.lockingModeType.state.option.0 = Entriegelt
channel-type.surepetcare.lockingModeType.state.option.1 = Verriegelt (nur hinein)
channel-type.surepetcare.lockingModeType.state.option.2 = Verriegelt (nur hinaus)
channel-type.surepetcare.lockingModeType.state.option.3 = Verriegelt
channel-type.surepetcare.lockingModeType.state.option.4 = Ausgangssperre
channel-type.surepetcare.batteryVoltageType.label = Batterie Spannung
channel-type.surepetcare.batteryVoltageType.description = Zeigt die Spannung der Batterie an. (in Volt)
channel-type.surepetcare.rssiDeviceType.label = Signalstärke (Gerät)
channel-type.surepetcare.rssiDeviceType.description = Zeigt die Signalstärke des Gerätes an.
channel-type.surepetcare.rssiHubType.label = Signalstärke (Hub)
channel-type.surepetcare.rssiHubType.description = Zeigt die Signalstärke des Hubs an.
channel-type.surepetcare.bowlsFoodType.label = Napf Futter Typ (Großer Napf)
channel-type.surepetcare.bowlsFoodType.description = Zeigt den Futter Typ des großen Napfes an.
channel-type.surepetcare.bowlsFoodType.state.option.1 = Nassfutter
channel-type.surepetcare.bowlsFoodType.state.option.2 = Trockenfutter
channel-type.surepetcare.bowlsFoodType.state.option.3 = Nass- u. Trockenfutter
channel-type.surepetcare.bowlsTargetType.label = Napf Gewicht (Großer Napf)
channel-type.surepetcare.bowlsTargetType.description = Zeigt das Zielgewicht des Futters vom großen Napf an.
channel-type.surepetcare.bowlsFoodLeftType.label = Napf Futter Typ Links (Halbe Näpfe)
channel-type.surepetcare.bowlsFoodLeftType.description = Zeigt den Futter Typ des linken kleinen Napfes an.
channel-type.surepetcare.bowlsFoodLeftType.state.option.1 = Nassfutter
channel-type.surepetcare.bowlsFoodLeftType.state.option.2 = Trockenfutter
channel-type.surepetcare.bowlsFoodLeftType.state.option.3 = Nass- u. Trockenfutter
channel-type.surepetcare.bowlsTargetLeftType.label = Napf Gewicht Links (Halbe Näpfe)
channel-type.surepetcare.bowlsTargetLeftType.description = Zeigt das Zielgewicht des Futters vom linken kleinen Napf an.
channel-type.surepetcare.bowlsFoodRightType.label = Napf Futter Typ Rechts (Halbe Näpfe)
channel-type.surepetcare.bowlsFoodRightType.description = Zeigt den Futter Typ des rechten kleinen Napfes an.
channel-type.surepetcare.bowlsFoodRightType.state.option.1 = Nassfutter
channel-type.surepetcare.bowlsFoodRightType.state.option.2 = Trockenfutter
channel-type.surepetcare.bowlsFoodRightType.state.option.3 = Nass- u. Trockenfutter
channel-type.surepetcare.bowlsTargetRightType.label = Napf Gewicht Rechts (Halbe Näpfe)
channel-type.surepetcare.bowlsTargetRightType.description = Zeigt das Zielgewicht des Futters vom rechten großen Napf an.
channel-type.surepetcare.bowlsType.label = Napf Typ
channel-type.surepetcare.bowlsType.description = Zeigt den Typ des Napfes an.
channel-type.surepetcare.bowlsType.state.option.0 = Undefiniert
channel-type.surepetcare.bowlsType.state.option.1 = Großer Napf
channel-type.surepetcare.bowlsType.state.option.4 = Halbe Näpfe
channel-type.surepetcare.bowlsCloseDelayType.label = Deckel Schließverzögerung
channel-type.surepetcare.bowlsCloseDelayType.description = Zeigt die Schließverzögerung des Deckels an.
channel-type.surepetcare.bowlsCloseDelayType.state.option.0 = Schnell
channel-type.surepetcare.bowlsCloseDelayType.state.option.4 = Normal
channel-type.surepetcare.bowlsCloseDelayType.state.option.20 = Langsam
channel-type.surepetcare.bowlsTrainingModeType.label = Futterautomat Training Modus
channel-type.surepetcare.bowlsTrainingModeType.description = Zeigt den Trainings Modus des Futterautomats an.
channel-type.surepetcare.bowlsTrainingModeType.state.option.0 = Aus
channel-type.surepetcare.bowlsTrainingModeType.state.option.1 = Ganz geöffnet
channel-type.surepetcare.bowlsTrainingModeType.state.option.2 = Fast ganz geöffnet
channel-type.surepetcare.bowlsTrainingModeType.state.option.3 = Halb geschlossen
channel-type.surepetcare.bowlsTrainingModeType.state.option.4 = Fast geschlossen
# Thing status description
offline.conf-error-invalid-refresh-intervals = Ungültiger Aktualisierungs-Intervall.
offline.conf-error-missing-username-or-password = Benutzername oder Passowrt fehlt.
offline.conf-error-authentication = Authentifizierungs-Fehler.

View File

@ -0,0 +1,665 @@
<?xml version="1.0" encoding="UTF-8" ?>
<thing:thing-descriptions bindingId="surepetcare"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="bridge">
<label>Sure Petcare API Bridge</label>
<description>The Sure Petcare Cloud API Bridge</description>
<channels>
<channel id="refresh" typeId="refreshType"/>
</channels>
<config-description>
<parameter name="username" type="text" required="true">
<label>Username (Email)</label>
<description>The username to access the Sure Petcare API</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>The password to access the Sure Petcare API</description>
<context>password</context>
</parameter>
<parameter name="refreshIntervalTopology" type="integer" required="false" min="300" unit="s">
<label>Refresh Interval Topology</label>
<description>Query interval (in secs) for device topology</description>
<default>36000</default>
</parameter>
<parameter name="refreshIntervalStatus" type="integer" required="false" min="60" unit="s">
<label>Refresh Interval Pet Status</label>
<description>Query interval (in secs) for pet status</description>
<default>300</default>
</parameter>
</config-description>
</bridge-type>
<channel-type id="refreshType">
<item-type>Switch</item-type>
<label>Refresh</label>
<description>Triggers a cache refresh of everything (devices, pets etc.).</description>
<state readOnly="false"/>
</channel-type>
<!-- Supported Sure Petcare pets and features -->
<thing-type id="household">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Sure Petcare Household</label>
<description>A Sure Petcare household</description>
<channels>
<channel id="id" typeId="idType"/>
<channel id="name" typeId="nameType"/>
<channel id="timezoneId" typeId="timezoneIdType"/>
</channels>
<properties>
<property name="id"/>
<property name="version"/>
<property name="createdAt"/>
<property name="updatedAt"/>
</properties>
<representation-property>id</representation-property>
</thing-type>
<thing-type id="hubDevice">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Sure Petcare Hub Device</label>
<description>A Sure Petcare hub device (connects cat flaps, feeders etc. to a network)</description>
<channels>
<channel id="id" typeId="idType"/>
<channel id="name" typeId="nameType"/>
<channel id="product" typeId="productType"/>
<channel id="ledMode" typeId="ledModeType"/>
<channel id="pairingMode" typeId="pairingModeType"/>
<channel id="online" typeId="onlineType"/>
</channels>
<properties>
<property name="id"/>
<property name="version"/>
<property name="createdAt"/>
<property name="updatedAt"/>
<property name="householdId"/>
<property name="productType"/>
<property name="productName"/>
</properties>
<representation-property>id</representation-property>
</thing-type>
<thing-type id="flapDevice">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Sure Petcare Flap Device</label>
<description>A Sure Petcare cat or pet flap device</description>
<channels>
<channel id="id" typeId="idType"/>
<channel id="name" typeId="nameType"/>
<channel id="product" typeId="productType"/>
<channel id="curfewEnabled1" typeId="curfewEnabledType"/>
<channel id="curfewLockTime1" typeId="curfewLockTimeType"/>
<channel id="curfewUnlockTime1" typeId="curfewUnlockTimeType"/>
<channel id="curfewEnabled2" typeId="curfewEnabledType"/>
<channel id="curfewLockTime2" typeId="curfewLockTimeType"/>
<channel id="curfewUnlockTime2" typeId="curfewUnlockTimeType"/>
<channel id="curfewEnabled3" typeId="curfewEnabledType"/>
<channel id="curfewLockTime3" typeId="curfewLockTimeType"/>
<channel id="curfewUnlockTime3" typeId="curfewUnlockTimeType"/>
<channel id="curfewEnabled4" typeId="curfewEnabledType"/>
<channel id="curfewLockTime4" typeId="curfewLockTimeType"/>
<channel id="curfewUnlockTime4" typeId="curfewUnlockTimeType"/>
<channel id="lockingMode" typeId="lockingModeType"/>
<channel id="lowBattery" typeId="system.low-battery"/>
<channel id="batteryLevel" typeId="system.battery-level"/>
<channel id="batteryVoltage" typeId="batteryVoltageType"/>
<channel id="online" typeId="onlineType"/>
<channel id="deviceRSSI" typeId="rssiDeviceType"/>
<channel id="hubRSSI" typeId="rssiHubType"/>
</channels>
<properties>
<property name="id"/>
<property name="version"/>
<property name="createdAt"/>
<property name="updatedAt"/>
<property name="householdId"/>
<property name="productType"/>
<property name="productName"/>
<property name="pairingAt"/>
</properties>
<representation-property>id</representation-property>
</thing-type>
<thing-type id="feederDevice">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Sure Petcare Feeder Device</label>
<description>A Sure Petcare pet feeder device</description>
<channels>
<channel id="id" typeId="idType"/>
<channel id="name" typeId="nameType"/>
<channel id="product" typeId="productType"/>
<channel id="lowBattery" typeId="system.low-battery"/>
<channel id="batteryLevel" typeId="system.battery-level"/>
<channel id="batteryVoltage" typeId="batteryVoltageType"/>
<channel id="online" typeId="onlineType"/>
<channel id="deviceRSSI" typeId="rssiDeviceType"/>
<channel id="hubRSSI" typeId="rssiHubType"/>
<channel id="bowlsFood" typeId="bowlsFoodType"/>
<channel id="bowlsTarget" typeId="bowlsTargetType"/>
<channel id="bowlsFoodLeft" typeId="bowlsFoodLeftType"/>
<channel id="bowlsTargetLeft" typeId="bowlsTargetLeftType"/>
<channel id="bowlsFoodRight" typeId="bowlsFoodRightType"/>
<channel id="bowlsTargetRight" typeId="bowlsTargetRightType"/>
<channel id="bowls" typeId="bowlsType"/>
<channel id="bowlsCloseDelay" typeId="bowlsCloseDelayType"/>
<channel id="bowlsTrainingMode" typeId="bowlsTrainingModeType"/>
</channels>
<properties>
<property name="id"/>
<property name="version"/>
<property name="createdAt"/>
<property name="updatedAt"/>
<property name="householdId"/>
<property name="productType"/>
<property name="productName"/>
<property name="pairingAt"/>
</properties>
<representation-property>id</representation-property>
</thing-type>
<thing-type id="pet">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Sure Petcare Pet</label>
<description>A Sure Petcare pet</description>
<channels>
<channel id="id" typeId="idType"/>
<channel id="name" typeId="nameType"/>
<channel id="comment" typeId="commentType"/>
<channel id="gender" typeId="genderType"/>
<channel id="breed" typeId="breedType"/>
<channel id="species" typeId="speciesType"/>
<channel id="photo" typeId="photoType"/>
<channel id="tagIdentifier" typeId="tagIdentifierType"/>
<channel id="location" typeId="locationType"/>
<channel id="locationChanged" typeId="locationChangedType"/>
<channel id="locationTimeoffset" typeId="locationTimeoffsetType"/>
<channel id="locationChangedThrough" typeId="locationChangedThroughType"/>
<channel id="dateOfBirth" typeId="dateOfBirthType"/>
<channel id="weight" typeId="weightType"/>
<channel id="feederDevice" typeId="feederDeviceType"/>
<channel id="feederLastFeeding" typeId="feederLastFeedingType"/>
<channel id="feederLastChange" typeId="feederLastChangeType"/>
<channel id="feederLastChangeLeft" typeId="feederLastChangeLeftType"/>
<channel id="feederLastChangeRight" typeId="feederLastChangeRightType"/>
</channels>
<properties>
<property name="id"/>
<property name="version"/>
<property name="createdAt"/>
<property name="updatedAt"/>
<property name="householdId"/>
</properties>
<representation-property>id</representation-property>
</thing-type>
<channel-type id="idType">
<item-type>Number</item-type>
<label>Id</label>
<description>A unique id of the thing</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="nameType">
<item-type>String</item-type>
<label>Name</label>
<description>The name of the thing</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="commentType">
<item-type>String</item-type>
<label>Comment</label>
<description>Comments about the thing</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="timezoneIdType">
<item-type>Number</item-type>
<label>Timezone Id</label>
<description>The identifier of the timezone</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="productType">
<item-type>String</item-type>
<label>Product Type</label>
<description>The type of product this device represents</description>
<state readOnly="true">
<options>
<option value="0">Unknown</option>
<option value="1">Hub</option>
<option value="3">Pet Flap</option>
<option value="4">Pet Feeder</option>
<option value="6">Cat Flap</option>
</options>
</state>
</channel-type>
<channel-type id="shareCodeType" advanced="true">
<item-type>String</item-type>
<label>Share Code</label>
<description>A unique code provided by Sure Petcare to access photos of pets</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="genderType">
<item-type>String</item-type>
<label>Gender</label>
<description>The gender of the pet</description>
<state readOnly="true">
<options>
<option value="0">Female</option>
<option value="1">Male</option>
</options>
</state>
</channel-type>
<channel-type id="breedType">
<item-type>String</item-type>
<label>Breed</label>
<description>The breed of the pet</description>
<state readOnly="true">
<options>
<option value="333">Abyssinian</option>
<option value="334">American Bobtail</option>
<option value="335">American Curl</option>
<option value="336">American Shorthair</option>
<option value="337">American Wirehair</option>
<option value="338">Balinese</option>
<option value="339">Bengal</option>
<option value="340">Birman</option>
<option value="341">Bombay</option>
<option value="342">British Shorthair</option>
<option value="343">Burmese</option>
<option value="344">Burmilla</option>
<option value="345">Chartreux</option>
<option value="346">Chinese Li Hua</option>
<option value="347">Colorpoint Shorthair</option>
<option value="348">Cornish Rex</option>
<option value="349">Cymric</option>
<option value="350">Devon Rex</option>
<option value="351">Egyptian Mau</option>
<option value="352">European Burmese</option>
<option value="353">Exotic</option>
<option value="354">Havana Brown</option>
<option value="355">Himalayan</option>
<option value="356">Japanese Bobtail</option>
<option value="357">Javanese</option>
<option value="358">Korat</option>
<option value="359">LaPerm</option>
<option value="360">Maine Coon</option>
<option value="361">Manx</option>
<option value="362">Nebelung</option>
<option value="363">Norwegian Forest</option>
<option value="364">Ocicat</option>
<option value="365">Oriental</option>
<option value="366">Persian</option>
<option value="367">Ragamuffin</option>
<option value="368">Ragdoll Cats</option>
<option value="369">Russisch Blue</option>
<option value="370">Savannah</option>
<option value="371">Scottish Fold</option>
<option value="372">Selkirk Rex</option>
<option value="373">Siamese Cat</option>
<option value="374">Siberian</option>
<option value="375">Singapura</option>
<option value="376">Snowshoe</option>
<option value="377">Somali</option>
<option value="378">Sphynx</option>
<option value="379">Tonkinese</option>
<option value="380">Turkish Angora</option>
<option value="381">Turkish Van</option>
<option value="382">Domestic Shorthair</option>
<option value="383">Domestic Longhair</option>
<option value="384">Moggy</option>
</options>
</state>
</channel-type>
<channel-type id="speciesType">
<item-type>String</item-type>
<label>Species</label>
<description>The species of the pet</description>
<state readOnly="true">
<options>
<option value="0">Undefined</option>
<option value="1">Cat</option>
<option value="2">Dog</option>
</options>
</state>
</channel-type>
<channel-type id="photoType">
<item-type>Image</item-type>
<label>Pet Photo</label>
<description>The image of the pet</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="tagIdentifierType">
<item-type>String</item-type>
<label>Micro Chip Tag Identifier</label>
<description>The unique identifier of the pet micro chip tag</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="locationType">
<item-type>String</item-type>
<label>Location</label>
<description>The current location of the pet</description>
<state readOnly="false">
<options>
<option value="0">Unknown</option>
<option value="1">Inside</option>
<option value="2">Outside</option>
</options>
</state>
</channel-type>
<channel-type id="locationChangedType">
<item-type>DateTime</item-type>
<label>Last Location Change Time</label>
<description>The time when the pet location has last changed</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="locationTimeoffsetType">
<item-type>String</item-type>
<label>Location Update with Time Offset</label>
<description>The location update of the pet with time offset (10, 30, 60 minutes ago.)</description>
<state readOnly="false">
<options>
<option value="10">10 mins ago</option>
<option value="30">30 mins ago</option>
<option value="60">60 mins ago</option>
</options>
</state>
</channel-type>
<channel-type id="locationChangedThroughType">
<item-type>String</item-type>
<label>Pet Location Changed Through</label>
<description>Shows the device name or username of the last location change.</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="ledModeType" advanced="true">
<item-type>String</item-type>
<label>LED Mode</label>
<description>The current LED mode of the device</description>
<state readOnly="false">
<options>
<option value="0">Off</option>
<option value="1">Bright</option>
<option value="4">Dimmed</option>
</options>
</state>
</channel-type>
<channel-type id="pairingModeType" advanced="true">
<item-type>String</item-type>
<label>Pairing Mode</label>
<description>The current pairing mode of the device</description>
<state readOnly="true">
<options>
<option value="0">Normal Mode</option>
<option value="1">Pairing Mode</option>
</options>
</state>
</channel-type>
<channel-type id="onlineType">
<item-type>Switch</item-type>
<label>Online</label>
<description>Indicator if device is online or offline</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="curfewEnabledType">
<item-type>Switch</item-type>
<label>Curfew Enabled</label>
<description>Indicator if the curfew is enabled</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="curfewLockTimeType">
<item-type>String</item-type>
<label>Curfew Lock Time</label>
<description>The time when the curfew starts</description>
<state readOnly="false" pattern="%s"/>
</channel-type>
<channel-type id="curfewUnlockTimeType">
<item-type>String</item-type>
<label>Curfew Unlock Time</label>
<description>The time when the curfew finishes</description>
<state readOnly="false" pattern="%s"/>
</channel-type>
<channel-type id="dateOfBirthType">
<item-type>DateTime</item-type>
<label>Pet Date of Birth</label>
<description>The pet's date of birth.</description>
<category>Time</category>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td"/>
</channel-type>
<channel-type id="weightType">
<item-type>Number:Mass</item-type>
<label>Pet Weight</label>
<description>The pet weight</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="feederDeviceType">
<item-type>String</item-type>
<label>Pet Feeding Device Name</label>
<description>The pet feeding device name</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="feederLastFeedingType">
<item-type>DateTime</item-type>
<label>Pet Last Feeding</label>
<description>The last pet feeding</description>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
</channel-type>
<channel-type id="feederLastChangeType">
<item-type>Number:Mass</item-type>
<label>Pet Feeding Last Change</label>
<description>The pet feeding last change</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="feederLastChangeLeftType">
<item-type>Number:Mass</item-type>
<label>Pet Feeding Last Change Left</label>
<description>The pet feeding last change left</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="feederLastChangeRightType">
<item-type>Number:Mass</item-type>
<label>Pet Feeding Last Change Right</label>
<description>The pet feeding last change right</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="lockingModeType">
<item-type>String</item-type>
<label>Locking Mode</label>
<description>The id of the locking mode the flap is currently set to</description>
<state readOnly="false">
<options>
<option value="0">Unlocked</option>
<option value="1">Locked In-only</option>
<option value="2">Locked Out-only</option>
<option value="3">Locked</option>
<option value="4">Curfew</option>
</options>
</state>
</channel-type>
<channel-type id="batteryVoltageType">
<item-type>Number:ElectricPotential</item-type>
<label>Battery Voltage</label>
<description>The current battery voltage</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="rssiDeviceType" advanced="true">
<item-type>Number</item-type>
<label>Signal Strength Device (RSSI)</label>
<description>The received device signal strength (RSSI).</description>
<state readOnly="true" pattern="%.2f dB"/>
</channel-type>
<channel-type id="rssiHubType" advanced="true">
<item-type>Number</item-type>
<label>Signal Strength Hub (RSSI)</label>
<description>The received hub signal strength (RSSI).</description>
<state readOnly="true" pattern="%.2f dB"/>
</channel-type>
<channel-type id="bowlsFoodType">
<item-type>String</item-type>
<label>Bowls Food Type (Big Bowl)</label>
<description>The food type of the bowl if the big bowl is used.</description>
<state readOnly="true">
<options>
<option value="1">Wet Food</option>
<option value="2">Dry Food</option>
<option value="3">Wet + Dry Food</option>
</options>
</state>
</channel-type>
<channel-type id="bowlsTargetType">
<item-type>Number:Mass</item-type>
<label>Bowls Target (Big Bowl)</label>
<description>The weight target of the bowl if the big bowl is used.</description>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="bowlsFoodLeftType">
<item-type>String</item-type>
<label>Bowls Food Type Small Bowl (Left)</label>
<description>The food type of the left bowl if the small bowls are used.</description>
<state readOnly="true">
<options>
<option value="1">Wet Food</option>
<option value="2">Dry Food</option>
<option value="3">Wet + Dry Food</option>
</options>
</state>
</channel-type>
<channel-type id="bowlsTargetLeftType">
<item-type>Number:Mass</item-type>
<label>Bowls Target Small Bowl (Left)</label>
<description>The weight target of the left bowl if the small bowls are used.</description>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="bowlsFoodRightType">
<item-type>String</item-type>
<label>Bowls Food Type Small Bowl (Right)</label>
<description>The food type of the right bowl if the small bowls are used.</description>
<state readOnly="true">
<options>
<option value="1">Wet Food</option>
<option value="2">Dry Food</option>
<option value="3">Wet + Dry Food</option>
</options>
</state>
</channel-type>
<channel-type id="bowlsTargetRightType">
<item-type>Number:Mass</item-type>
<label>Bowls Target Small Bowl (Right)</label>
<description>The weight target of the right bowl if the small bowls are used.</description>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="bowlsType">
<item-type>String</item-type>
<label>Bowls Type</label>
<description>The type of used bowls.</description>
<state readOnly="true">
<options>
<option value="0">Undefined</option>
<option value="1">Big Bowl</option>
<option value="4">Half Bowls</option>
</options>
</state>
</channel-type>
<channel-type id="bowlsCloseDelayType">
<item-type>String</item-type>
<label>Feeder Lid Close Delay</label>
<description>The feeder lid close delay</description>
<state readOnly="true">
<options>
<option value="0">Fast</option>
<option value="4">Normal</option>
<option value="20">Slow</option>
</options>
</state>
</channel-type>
<channel-type id="bowlsTrainingModeType">
<item-type>String</item-type>
<label>Feeder Training Mode</label>
<description>The feeder training mode.</description>
<state readOnly="true">
<options>
<option value="0">Off</option>
<option value="1">Full open</option>
<option value="2">Almost Full open</option>
<option value="3">Half closed</option>
<option value="4">Almost closed</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.data;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareBaseObject;
/**
* The {@link SurePetcareBaseObjectTest} class implements unit test case for {@link SurePetcareBaseObject}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareBaseObjectTest {
@Test
public void testNotNullFromJson() {
String testResponse = "{\"id\":2491083182,\"version\":\"MA==\",\"created_at\":\"2019-09-18T16:09:30+00:00\",\"updated_at\":\"2019-09-18T16:09:30+00:00\"}";
SurePetcareBaseObject response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareBaseObject.class);
if (response != null) {
assertEquals(Long.valueOf(2491083182L), response.id);
assertEquals("MA==", response.version);
assertNotNull(response.createdAt);
assertNotNull(response.updatedAt);
} else {
fail("GSON returned null");
}
}
@Test
public void testNullAttributesFromJson() {
String testResponse = "{\"id\":33421}";
SurePetcareBaseObject response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareBaseObject.class);
if (response != null) {
assertEquals(Long.valueOf(33421), response.id);
assertEquals("", response.version);
assertNotNull(response.createdAt);
assertNotNull(response.updatedAt);
} else {
fail("GSON returned null");
}
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.data;
import static org.junit.jupiter.api.Assertions.*;
import java.text.ParseException;
import java.time.LocalTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceControl;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfew;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfewList;
/**
* The {@link SurePetcareDeviceControlTest} class implements unit test case for {@link SurePetcareDeviceControl}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareDeviceControlTest {
@Test
public void testJsonDeserializeCurfewArray() throws ParseException {
String testResponse = "{\"curfew\":[{\"enabled\":true,\"lock_time\":\"19:30\",\"unlock_time\":\"07:00\"}],\"locking\":0,\"fast_polling\":false}";
SurePetcareDeviceControl response = SurePetcareConstants.GSON.fromJson(testResponse,
SurePetcareDeviceControl.class);
if (response != null) {
assertEquals(1, response.curfewList.size());
assertEquals(Integer.valueOf(0), response.lockingModeId);
} else {
fail("GSON returned null");
}
}
@Test
public void testJsonDeserializeSingleCurfew() throws ParseException {
String testResponse = "{\"curfew\":{\"enabled\":true,\"lock_time\":\"19:00\",\"unlock_time\":\"08:00\"},\"fast_polling\":true}";
SurePetcareDeviceControl response = SurePetcareConstants.GSON.fromJson(testResponse,
SurePetcareDeviceControl.class);
if (response != null) {
assertEquals(1, response.curfewList.size());
assertEquals(true, response.fastPolling);
} else {
fail("GSON returned null");
}
}
@Test
public void testJsonSerializeLockingMode() throws ParseException {
SurePetcareDeviceControl control = new SurePetcareDeviceControl();
control.lockingModeId = Integer.valueOf(4);
String json = SurePetcareConstants.GSON.toJson(control);
assertEquals("{\"locking\":4}", json);
}
@Test
public void testJsonSerializeCurfew() throws ParseException {
SurePetcareDeviceControl control = new SurePetcareDeviceControl();
SurePetcareDeviceCurfewList curfews = new SurePetcareDeviceCurfewList();
curfews.add(new SurePetcareDeviceCurfew(true, LocalTime.of(19, 30, 00), LocalTime.of(07, 00, 00)));
control.curfewList = curfews;
String json = SurePetcareConstants.GSON.toJson(control);
assertEquals("{\"curfew\":[{\"enabled\":true,\"lock_time\":\"19:30\",\"unlock_time\":\"07:00\"}]}", json);
}
}

View File

@ -0,0 +1,97 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.data;
import static org.junit.jupiter.api.Assertions.*;
import java.time.LocalTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfew;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfewList;
/**
* The {@link SurePetcareDeviceCurfewListTest} class implements unit test case for {@link SurePetcareDeviceCurfewList}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareDeviceCurfewListTest {
@Test
public void testGet() {
SurePetcareDeviceCurfewList orig = new SurePetcareDeviceCurfewList();
orig.add(new SurePetcareDeviceCurfew());
orig.add(new SurePetcareDeviceCurfew(true, LocalTime.of(12, 00, 00), LocalTime.of(13, 00, 00)));
assertEquals(orig.size(), 2);
assertFalse(orig.get(0).enabled);
assertTrue(orig.get(1).enabled);
assertEquals(orig.size(), 2);
assertFalse(orig.get(2).enabled);
assertEquals(orig.size(), 3);
assertFalse(orig.get(3).enabled);
assertEquals(orig.size(), 4);
assertFalse(orig.get(9).enabled);
assertEquals(orig.size(), 10);
}
@Test
public void testOrder() {
SurePetcareDeviceCurfewList orig = new SurePetcareDeviceCurfewList();
orig.add(new SurePetcareDeviceCurfew());
orig.add(new SurePetcareDeviceCurfew(true, LocalTime.of(12, 00, 00), LocalTime.of(13, 00, 00)));
orig.add(new SurePetcareDeviceCurfew(true, LocalTime.of(19, 00, 00), LocalTime.of(20, 00, 00)));
assertEquals(orig.size(), 3);
assertFalse(orig.get(0).enabled);
assertTrue(orig.get(1).enabled);
assertTrue(orig.get(2).enabled);
SurePetcareDeviceCurfewList ordered = orig.order();
assertEquals(ordered.size(), 4);
assertTrue(ordered.get(0).enabled);
assertEquals(ordered.get(0).lockTime, LocalTime.of(12, 00, 00));
assertTrue(ordered.get(1).enabled);
assertFalse(ordered.get(2).enabled);
assertFalse(ordered.get(3).enabled);
}
@Test
public void testCompact() {
SurePetcareDeviceCurfewList orig = new SurePetcareDeviceCurfewList();
orig.add(new SurePetcareDeviceCurfew());
orig.add(new SurePetcareDeviceCurfew(true, LocalTime.of(12, 00, 00), LocalTime.of(13, 00, 00)));
orig.add(new SurePetcareDeviceCurfew(true, LocalTime.of(19, 00, 00), LocalTime.of(20, 00, 00)));
orig.add(new SurePetcareDeviceCurfew());
assertEquals(orig.size(), 4);
assertFalse(orig.get(0).enabled);
assertTrue(orig.get(1).enabled);
assertTrue(orig.get(2).enabled);
assertFalse(orig.get(3).enabled);
SurePetcareDeviceCurfewList compact = orig.compact();
assertEquals(compact.size(), 2);
assertTrue(compact.get(0).enabled);
assertEquals(compact.get(0).lockTime, LocalTime.of(12, 00, 00));
assertTrue(compact.get(1).enabled);
assertEquals(compact.get(1).lockTime, LocalTime.of(19, 00, 00));
}
}

View File

@ -0,0 +1,125 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.data;
import static org.junit.jupiter.api.Assertions.*;
import java.text.ParseException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice;
/**
* The {@link SurePetcareDeviceTest} class implements unit test case for {@link SurePetcareDevice}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareDeviceTest {
@Test
public void testJsonDeserializeHub1() throws ParseException {
String testResponse = "{\"id\":296464,\"product_id\":1,\"household_id\":48712,\"name\":\"Home Hub\",\"serial_number\":\"H008-0296432\",\"mac_address\":\"00000491630A0D64\",\"version\":\"NjA=\",\"created_at\":\"2019-04-18T14:45:11+00:00\",\"updated_at\":\"2019-09-30T12:31:52+00:00\",\"control\":{\"led_mode\":4,\"pairing_mode\":0},\"status\":{\"led_mode\":4,\"pairing_mode\":0,\"version\":{\"device\":{\"hardware\":3,\"firmware\":1.772}},\"online\":true}}";
SurePetcareDevice response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareDevice.class);
if (response != null) {
assertEquals(Long.valueOf(296464L), response.id);
assertEquals(Integer.valueOf(1), response.productId);
assertEquals(Long.valueOf(48712), response.householdId);
assertEquals("Home Hub", response.name);
assertEquals("H008-0296432", response.serialNumber);
assertEquals("00000491630A0D64", response.macAddress);
assertEquals("NjA=", response.version);
assertEquals(Integer.valueOf(4), response.control.ledModeId);
assertEquals(Integer.valueOf(0), response.control.pairingModeId);
assertEquals(Integer.valueOf(4), response.status.ledModeId);
assertEquals(Integer.valueOf(0), response.status.pairingModeId);
assertEquals("3", response.status.version.device.hardware);
assertEquals("1.772", response.status.version.device.firmware);
} else {
fail("GSON returned null");
}
}
@Test
public void testJsonDeserializeHub2() throws ParseException {
String testResponse = "{\"id\":101797,\"product_id\":1,\"household_id\":21005,\"name\":\"Home Hub\",\"serial_number\":\"H005-0101321\",\"mac_address\":\"0000801F1341F1C7\",\"version\":\"NzAzNg==\",\"created_at\":\"2018-05-18T11:11:59+00:00\",\"updated_at\":\"2020-05-01T07:51:32+00:00\",\"control\":{\"led_mode\":4,\"pairing_mode\":0},\"status\":{\"led_mode\":4,\"pairing_mode\":0,\"version\":{\"device\":{\"hardware\":3,\"firmware\":2.43}},\"online\":true}}";
SurePetcareDevice response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareDevice.class);
if (response != null) {
assertEquals(Long.valueOf(101797), response.id);
assertEquals(Integer.valueOf(1), response.productId);
assertEquals(Long.valueOf(21005), response.householdId);
assertEquals("Home Hub", response.name);
assertEquals("H005-0101321", response.serialNumber);
assertEquals("0000801F1341F1C7", response.macAddress);
assertEquals("NzAzNg==", response.version);
assertEquals(Integer.valueOf(4), response.control.ledModeId);
assertEquals(Integer.valueOf(0), response.control.pairingModeId);
assertEquals(Integer.valueOf(4), response.status.ledModeId);
assertEquals(Integer.valueOf(0), response.status.pairingModeId);
assertEquals("3", response.status.version.device.hardware);
assertEquals("2.43", response.status.version.device.firmware);
} else {
fail("GSON returned null");
}
}
@Test
public void testJsonDeserializeCatFlap() throws ParseException {
String testResponse = "{\"id\":318966,\"parent_device_id\":296464,\"product_id\":6,\"household_id\":48712,\"name\":\"Back Door Cat Flap\",\"serial_number\":\"N005-0089709\",\"mac_address\":\"6D5E01CFF9D5B370\",\"index\":0,\"version\":\"MTE5\",\"created_at\":\"2019-05-13T14:09:18+00:00\",\"updated_at\":\"2019-10-01T07:37:20+00:00\",\"pairing_at\":\"2019-09-02T08:24:13+00:00\",\"control\":{\"curfew\":[{\"enabled\":true,\"lock_time\":\"19:30\",\"unlock_time\":\"07:00\"}],\"locking\":0,\"fast_polling\":false},\"parent\":{\"id\":296464,\"product_id\":1,\"household_id\":48712,\"name\":\"Home Hub\",\"serial_number\":\"H008-0296464\",\"mac_address\":\"00000491620A0F60\",\"version\":\"NjE=\",\"created_at\":\"2019-04-18T14:45:11+00:00\",\"updated_at\":\"2019-10-01T07:37:20+00:00\"},\"status\":{\"locking\":{\"mode\":0},\"version\":{\"device\":{\"hardware\":9,\"firmware\":335}},\"battery\":5.771,\"learn_mode\":null,\"online\":true,\"signal\":{\"device_rssi\":-87.25,\"hub_rssi\":-83.5}},\"tags\":[{\"id\":60456,\"index\":0,\"profile\":2,\"version\":\"MA==\",\"created_at\":\"2019-09-02T09:27:17+00:00\",\"updated_at\":\"2019-09-02T09:27:23+00:00\"}]}";
SurePetcareDevice response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareDevice.class);
if (response != null) {
response.getThingProperties();
assertEquals(Long.valueOf(318966), response.id);
assertEquals(Integer.valueOf(6), response.productId);
assertEquals(Long.valueOf(48712), response.householdId);
assertEquals("Back Door Cat Flap", response.name);
assertEquals("N005-0089709", response.serialNumber);
assertEquals("6D5E01CFF9D5B370", response.macAddress);
assertEquals("9", response.status.version.device.hardware);
assertEquals("335", response.status.version.device.firmware);
assertEquals("MTE5", response.version);
assertEquals(Integer.valueOf(0), response.status.locking.modeId);
} else {
fail("GSON returned null");
}
}
@Test
public void testJsonDeserializePetFlap() throws ParseException {
String testResponse = "{\"id\":318966,\"parent_device_id\":296464,\"product_id\":3,\"household_id\":48712,\"name\":\"Back Door Cat Flap\",\"mac_address\":\"6D5E01CFF9D5B370\",\"index\":0,\"version\":\"MjYxMw==\",\"created_at\":\"2019-03-02T14:54:30+00:00\",\"updated_at\":\"2020-05-01T07:51:32+00:00\",\"pairing_at\":\"2019-06-18T19:54:34+00:00\",\"control\":{\"curfew\":{\"enabled\":true,\"lock_time\":\"19:00\",\"unlock_time\":\"08:00\"},\"fast_polling\":true},\"parent\":{\"id\":101797,\"product_id\":1,\"household_id\":21005,\"name\":\"Salem\",\"serial_number\":\"N005-0089709\",\"mac_address\":\"0000801F1221F1C2\",\"version\":\"NzAzNg==\",\"created_at\":\"2018-05-18T11:11:59+00:00\",\"updated_at\":\"2020-05-01T07:51:32+00:00\"},\"status\":{\"battery\":5.864999999999999,\"locking\":{\"mode\":4,\"curfew\":{\"delay_time\":0,\"lock_time\":\"19:00\",\"permission\":2,\"unlock_time\":\"08:00\",\"locked\":false}},\"version\":{\"lcd\":{\"hardware\":1,\"firmware\":1},\"rf\":{\"hardware\":4,\"firmware\":0.16}},\"learn_mode\":false,\"online\":true,\"signal\":{\"device_rssi\":-88.33333333333333,\"hub_rssi\":-86}},\"tags\":[{\"id\":24725,\"index\":0,\"version\":\"MA==\",\"created_at\":\"2019-06-18T19:54:42+00:00\",\"updated_at\":\"2020-03-11T16:06:58+00:00\"}]}";
SurePetcareDevice response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareDevice.class);
if (response != null) {
response.getThingProperties();
assertEquals(Long.valueOf(318966), response.id);
assertEquals(Integer.valueOf(3), response.productId);
assertEquals(Long.valueOf(48712), response.householdId);
assertEquals("Back Door Cat Flap", response.name);
assertNull(response.serialNumber);
assertEquals("6D5E01CFF9D5B370", response.macAddress);
assertEquals("1", response.status.version.lcd.hardware);
assertEquals("1", response.status.version.lcd.firmware);
assertEquals("4", response.status.version.rf.hardware);
assertEquals("0.16", response.status.version.rf.firmware);
assertEquals("MjYxMw==", response.version);
assertEquals(Integer.valueOf(4), response.status.locking.modeId);
} else {
fail("GSON returned null");
}
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.data;
import static org.junit.jupiter.api.Assertions.*;
import java.text.ParseException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareHousehold;
/**
* The {@link SurePetcareHouseholdTest} class implements unit test case for {@link SurePetcareHousehold}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareHouseholdTest {
@Test
public void testJsonDeserialize1() throws ParseException {
String testReponse = "{\"id\":2491083182,\"name\":\"Test Home\",\"share_code\":\"BFHvjQ8DgvnP\",\"timezone_id\":340,\"version\":\"MA==\",\"created_at\":\"2019-09-02T08:20:45+00:00\",\"updated_at\":\"2019-09-02T08:20:48+00:00\",\"invites\":[{\"id\":12352,\"code\":\"QDEZHNNHFG\",\"email_address\":\"user1@gugus.com\",\"creator_user_id\":32712,\"acceptor_user_id\":87621,\"owner\":false,\"write\":true,\"status\":1,\"version\":\"Mg==\",\"created_at\":\"2019-09-09T10:33:36+00:00\",\"updated_at\":\"2019-09-09T11:59:39+00:00\",\"user\":{\"acceptor\":{\"id\":87621,\"name\":\"User1\"},\"creator\":{\"id\":32712,\"name\":\"Admin User\"}}}],\"users\":[{\"id\":32712,\"owner\":true,\"write\":true,\"version\":\"MA==\",\"created_at\":\"2019-09-02T08:20:45+00:00\",\"updated_at\":\"2019-09-02T08:20:50+00:00\",\"user\":{\"id\":32712,\"name\":\"Admin User\"}},{\"id\":87621,\"owner\":false,\"write\":true,\"version\":\"MA==\",\"created_at\":\"2019-09-09T11:59:39+00:00\",\"updated_at\":\"2019-09-09T11:59:39+00:00\",\"user\":{\"id\":87621,\"name\":\"User1\"}}]}";
SurePetcareHousehold response = SurePetcareConstants.GSON.fromJson(testReponse, SurePetcareHousehold.class);
if (response != null) {
assertEquals(Long.valueOf(2491083182L), response.id);
assertEquals("Test Home", response.name);
assertEquals("BFHvjQ8DgvnP", response.shareCode);
assertEquals(Integer.valueOf(340), response.timezoneId);
} else {
fail("GSON returned null");
}
}
@Test
public void testJsonDeserialize2() throws ParseException {
String testReponse = "{\"id\":2491083182,\"name\":\"Test Home\",\"share_code\":\"BFHvjQ8DgvnP\",\"timezone_id\":320,\"version\":\"MQ==\",\"created_at\":\"2018-12-21T17:50:07+00:00\",\"updated_at\":\"2018-12-21T17:50:07+00:00\",\"invites\":[{\"id\":10414,\"code\":\"KHDSUYRBKJF\",\"email_address\":\"test@gmx.de\",\"creator_user_id\":22360,\"owner\":false,\"write\":false,\"status\":0,\"version\":\"MA==\",\"created_at\":\"2018-12-22T09:15:10+00:00\",\"updated_at\":\"2018-12-22T09:15:10+00:00\",\"user\":{\"creator\":{\"id\":22360,\"name\":\"Owner Name\"}}}],\"users\":[{\"id\":22360,\"owner\":true,\"write\":true,\"version\":\"MQ==\",\"created_at\":\"2018-12-21T17:50:07+00:00\",\"updated_at\":\"2018-12-21T17:50:13+00:00\",\"user\":{\"id\":22360,\"name\":\"Owner Name\"}}]}";
SurePetcareHousehold response = SurePetcareConstants.GSON.fromJson(testReponse, SurePetcareHousehold.class);
if (response != null) {
assertEquals(Long.valueOf(2491083182L), response.id);
assertEquals("Test Home", response.name);
assertEquals("BFHvjQ8DgvnP", response.shareCode);
assertEquals(Integer.valueOf(320), response.timezoneId);
} else {
fail("GSON returned null");
}
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.data;
import static org.junit.jupiter.api.Assertions.*;
import java.text.ParseException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePetActivity;
/**
* The {@link SurePetcarePetLocationTest} class implements unit test case for {@link SurePetcarePetLocation}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcarePetActivityTest {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
@Test
public void testJsonDeserialize() throws ParseException {
String testReponse = "{\"tag_id\":60126,\"device_id\":376236,\"where\":2,\"since\":\"2019-09-11T13:09:07+00:00\"}";
SurePetcarePetActivity response = SurePetcareConstants.GSON.fromJson(testReponse, SurePetcarePetActivity.class);
if (response != null) {
assertEquals(Long.valueOf(60126), response.tagId);
assertEquals(Long.valueOf(376236), response.deviceId);
assertEquals(Integer.valueOf(2), response.where);
ZonedDateTime since = FORMATTER.parse("2019-09-11T13:09:07+00:00", ZonedDateTime::from);
assertEquals(since, response.since);
} else {
fail("GSON returned null");
}
}
@Test
public void testJsonFullSerialize() throws ParseException {
ZonedDateTime since = ZonedDateTime.parse("2019-09-11T13:09:07+00:00");
SurePetcarePetActivity location = new SurePetcarePetActivity(2, since);
String json = SurePetcareConstants.GSON.toJson(location, SurePetcarePetActivity.class);
assertEquals("{\"where\":2,\"since\":\"2019-09-11T13:09:07+00:00\"}", json);
}
}

View File

@ -0,0 +1,139 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.data;
import static org.junit.jupiter.api.Assertions.*;
import java.math.BigDecimal;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
import org.openhab.binding.surepetcare.internal.dto.SurePetcarePet;
/**
* The {@link SurePetcarePetTest} class implements unit test case for {@link SurePetcarePet}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcarePetTest {
// {
// "id":34675,
// "name":"Cat",
// "gender":0,
// "date_of_birth":"2017-08-01T00:00:00+00:00",
// "weight":"3.5",
// "comments":"Test Comment",
// "household_id":87435,
// "breed_id":382,
// "photo_id":23412,
// "species_id":1,
// "tag_id":60456,
// "version":"Mw==",
// "created_at":"2019-09-02T09:27:17+00:00",
// "updated_at":"2019-10-03T12:17:48+00:00",
// "conditions":[
// {
// "id":18,
// "version":"MA==",
// "created_at":"2019-10-03T12:17:48+00:00",
// "updated_at":"2019-10-03T12:17:48+00:00"
// },
// {
// "id":17,
// "version":"MA==",
// "created_at":"2019-10-03T12:17:48+00:00",
// "updated_at":"2019-10-03T12:17:48+00:00"
// }
// ],
// "photo":{
// "id":79293,
// "location":"https:\/\/surehub.s3.amazonaws.com\/user-photos\/thm\/23412\/z70LUtqaHVhlsdfuyHKJH5HDysg5AR6GvQwdAZptCgeZU.jpg",
// "uploading_user_id":52815,
// "version":"MA==",
// "created_at":"2019-09-02T09:31:07+00:00",
// "updated_at":"2019-09-02T09:31:07+00:00"
// },
// "position":{
// "tag_id":60456,
// "device_id":318986,
// "where":1,
// "since":"2019-10-03T10:23:37+00:00"
// },
// "status":{
// "activity":{
// "tag_id":60456,
// "device_id":318986,
// "where":1,
// "since":"2019-10-03T10:23:37+00:00"
// }
// }
// }
private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter ZONED_DATETIME_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
@Test
public void testJsonDeserialize1() throws ParseException {
String testReponse = "{\"id\":34675,\"name\":\"Cat\",\"gender\":0,\"date_of_birth\":\"2017-08-01T00:00:00+00:00\",\"weight\":\"3.5\",\"comments\":\"Test Comment\",\"household_id\":87435,\"breed_id\":382,\"photo_id\":23412,\"species_id\":1,\"tag_id\":60456,\"version\":\"Mw==\",\"created_at\":\"2019-09-02T09:27:17+00:00\",\"updated_at\":\"2019-10-03T12:17:48+00:00\",\"conditions\":[{\"id\":18,\"version\":\"MA==\",\"created_at\":\"2019-10-03T12:17:48+00:00\",\"updated_at\":\"2019-10-03T12:17:48+00:00\"},{\"id\":17,\"version\":\"MA==\",\"created_at\":\"2019-10-03T12:17:48+00:00\",\"updated_at\":\"2019-10-03T12:17:48+00:00\"}],\"photo\":{\"id\":79293,\"location\":\"https:\\/\\/surehub.s3.amazonaws.com\\/user-photos\\/thm\\/23412\\/z70LUtqaHVhlsdfuyHKJH5HDysg5AR6GvQwdAZptCgeZU.jpg\",\"uploading_user_id\":52815,\"version\":\"MA==\",\"created_at\":\"2019-09-02T09:31:07+00:00\",\"updated_at\":\"2019-09-02T09:31:07+00:00\"},\"position\":{\"tag_id\":60456,\"device_id\":318986,\"where\":1,\"since\":\"2019-10-03T10:23:37+00:00\"},\"status\":{\"activity\":{\"tag_id\":60456,\"device_id\":318986,\"where\":1,\"since\":\"2019-10-03T10:23:37+00:00\"}}}";
SurePetcarePet response = SurePetcareConstants.GSON.fromJson(testReponse, SurePetcarePet.class);
if (response != null) {
assertEquals(Long.valueOf(34675), response.id);
assertEquals("Cat", response.name);
assertEquals(Integer.valueOf(0), response.genderId);
assertEquals(LocalDate.parse("2017-08-01", LOCAL_DATE_FORMATTER), response.dateOfBirth);
assertEquals(BigDecimal.valueOf(3.5), response.weight);
assertEquals("Test Comment", response.comments);
assertEquals(Long.valueOf(87435), response.householdId);
assertEquals(Long.valueOf(23412), response.photoId);
assertEquals(SurePetcarePet.PetSpecies.CAT.id, response.speciesId);
assertEquals(Integer.valueOf(382), response.breedId);
assertEquals(Integer.valueOf(1), response.status.activity.where);
assertEquals(ZonedDateTime.parse("2019-10-03T10:23:37+00:00", ZONED_DATETIME_FORMATTER),
response.status.activity.since);
} else {
fail("GSON returned null");
}
}
@Test
public void testJsonDeserialize2() throws ParseException {
String testReponse = "{\"id\":30622,\"name\":\"Cat\",\"gender\":1,\"date_of_birth\":\"2016-04-01T00:00:00+00:00\",\"weight\":\"6\",\"comments\":\"\",\"household_id\":21005,\"breed_id\":382,\"food_type_id\":1,\"photo_id\":77957,\"species_id\":1,\"tag_id\":24725,\"version\":\"OA==\",\"created_at\":\"2018-12-22T08:59:13+00:00\",\"updated_at\":\"2019-08-26T18:17:38+00:00\",\"photo\":{\"id\":77957,\"location\":\"https://surehub.s3.amazonaws.com/user-photos/thm/22360/1jhp4OtwmNvWXrsT4pWLJhoYOt7Ti9UVm5SjsFoC9Y.jpg \",\"uploading_user_id\":22360,\"version\":\"MA==\",\"created_at\":\"2019-08-26T18:17:38+00:00\",\"updated_at\":\"2019-08-26T18:17:38+00:00\"},\"position\":{\"tag_id\":24725,\"device_id\":243573,\"where\":2,\"since\":\"2020-05-01T06:01:53+00:00\"},\"status\":{\"activity\":{\"tag_id\":24725,\"device_id\":243573,\"where\":2,\"since\":\"2020-05-01T06:01:53+00:00\"}}}";
SurePetcarePet response = SurePetcareConstants.GSON.fromJson(testReponse, SurePetcarePet.class);
if (response != null) {
assertEquals(Long.valueOf(30622), response.id);
assertEquals("Cat", response.name);
assertEquals(Integer.valueOf(1), response.genderId);
assertEquals(LocalDate.parse("2016-04-01", LOCAL_DATE_FORMATTER), response.dateOfBirth);
assertEquals(BigDecimal.valueOf(6), response.weight);
assertEquals("", response.comments);
assertEquals(Long.valueOf(21005), response.householdId);
assertEquals(Long.valueOf(77957), response.photoId);
assertEquals(SurePetcarePet.PetSpecies.CAT.id, response.speciesId);
assertEquals(Integer.valueOf(382), response.breedId);
assertEquals(Integer.valueOf(2), response.status.activity.where);
assertEquals(ZonedDateTime.parse("2020-05-01T06:01:53+00:00", ZONED_DATETIME_FORMATTER),
response.status.activity.since);
} else {
fail("GSON returned null");
}
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.data;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareTopology;
/**
* The {@link SurePetcareTopologyTest} class implements unit test case for {@link SurePetcareTopology}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareTopologyTest {
@Test
public void testTopologyPopulated() {
String testResponse = "{\"devices\":[{\"id\":23912},{\"id\":23481}],\"households\":[{\"id\":83271}],\"pets\":[{\"id\":12345}],\"photos\":[{\"id\":64257,\"version\":\"MA==\",\"created_at\":\"2019-10-04T16:03:20+00:00\",\"updated_at\":\"2019-10-04T16:03:20+00:00\"}],\"user\":{\"id\":33421,\"version\":\"MA==\",\"created_at\":\"2019-09-18T16:09:30+00:00\",\"updated_at\":\"2019-09-18T16:09:30+00:00\"}}";
SurePetcareTopology response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareTopology.class);
if (response != null) {
assertNotNull(response.devices);
assertEquals(2, response.devices.size());
assertNotNull(response.households);
assertEquals(1, response.households.size());
assertNotNull(response.pets);
assertEquals(1, response.pets.size());
assertNotNull(response.photos);
assertEquals(1, response.photos.size());
assertNotNull(response.user);
} else {
fail("GSON returned null");
}
}
@Test
public void testTopologyEmpty() {
String testResponse = "{}";
SurePetcareTopology response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareTopology.class);
if (response != null) {
assertNotNull(response.tags);
assertEquals(0, response.tags.size());
assertNotNull(response.households);
assertEquals(0, response.households.size());
assertNotNull(response.pets);
assertEquals(0, response.pets.size());
assertNotNull(response.devices);
assertEquals(0, response.devices.size());
assertNull(response.user);
} else {
fail("GSON returned null");
}
}
@Test
public void testGetUserNull() {
String testResponse = "{\"devices\":[{\"id\":23912},{\"id\":23481}],\"households\":[{\"id\":83271}],\"pets\":[{\"id\":12345}],\"photos\":[{\"id\":64257,\"version\":\"MA==\",\"created_at\":\"2019-10-04T16:03:20+00:00\",\"updated_at\":\"2019-10-04T16:03:20+00:00\"}]}";
SurePetcareTopology response = SurePetcareConstants.GSON.fromJson(testResponse, SurePetcareTopology.class);
if (response != null) {
assertNull(response.user);
} else {
fail("GSON returned null");
}
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2021 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.surepetcare.internal.handler;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.surepetcare.internal.SurePetcareConstants;
import org.openhab.binding.surepetcare.internal.dto.SurePetcareLoginResponse;
/**
* The {@link SurePetcareLoginResponseTest} class implements unit test case for {@link SurePetcareLoginResponse}
*
* @author Rene Scherer - Initial contribution
*/
@NonNullByDefault
public class SurePetcareLoginResponseTest {
@Test
public void testParseLoginResponse() {
String testReponse = "{\"data\":{\"user\":{\"id\":23412,\"email_address\":\"rene@gugus.com\",\"first_name\":\"Rene\",\"last_name\":\"Scherer\",\"country_id\":77,\"language_id\":37,\"marketing_opt_in\":false,\"terms_accepted\":true,\"weight_units\":0,\"time_format\":0,\"version\":\"MA==\",\"created_at\":\"2019-09-02T08:20:03+00:00\",\"updated_at\":\"2019-09-02T08:20:03+00:00\",\"notifications\":{\"device_status\":true,\"animal_movement\":true,\"intruder_movements\":true,\"new_device_pet\":true,\"household_management\":true,\"photos\":true,\"low_battery\":true,\"curfew\":true,\"feeding_activity\":true}},\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwcC5hcGkuc3VyZWh1Yi5pby9hcGkvYXV0aC9sb2dpbiIsImlhdCI6MTU2NzYxMjY2OSwiZXhwIjoxNTk5MDYyMjY5LCJuYmYiOjE1Njc2MTI2NjksImp0aSI6IlY4M1lJQlJ5dVRqMUVDcWsiLCJzdWIiOjUyODE1LCJwcnYiOiJiM2VkM2RiMzM0YzJiYzMzYjE4NDI2OTQ3NTU3NTZhM2ZmYmY1YTdkIiwiZGV2aWNlX2lkIjoiNTczODc2MzQifQ.WeRutm8I7gMb21dtrknDh6LGFkwxfrXcak-IoykwvV8\"}}";
SurePetcareLoginResponse response = SurePetcareConstants.GSON.fromJson(testReponse,
SurePetcareLoginResponse.class);
if (response != null) {
assertEquals("Rene", response.data.user.firstName);
assertEquals(363, response.data.token.length());
} else {
fail("GSON returned null");
}
}
}

View File

@ -289,6 +289,7 @@
<module>org.openhab.binding.sonyprojector</module>
<module>org.openhab.binding.spotify</module>
<module>org.openhab.binding.squeezebox</module>
<module>org.openhab.binding.surepetcare</module>
<module>org.openhab.binding.synopanalyzer</module>
<module>org.openhab.binding.systeminfo</module>
<module>org.openhab.binding.tacmi</module>