diff --git a/src/main/java/io/rudolph/netatmo/Extension.kt b/src/main/java/io/rudolph/netatmo/Extension.kt new file mode 100644 index 0000000..337194a --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/Extension.kt @@ -0,0 +1,7 @@ +package io.rudolph.netatmo + +import io.rudolph.netatmo.executable.Executable +import retrofit2.Call + +val Call.executable: Executable + get() = Executable(this) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/common/transform/JacksonTransform.kt b/src/main/java/io/rudolph/netatmo/JacksonTransform.kt similarity index 91% rename from src/main/java/io/rudolph/netatmo/api/common/transform/JacksonTransform.kt rename to src/main/java/io/rudolph/netatmo/JacksonTransform.kt index 65162ea..81ecc29 100644 --- a/src/main/java/io/rudolph/netatmo/api/common/transform/JacksonTransform.kt +++ b/src/main/java/io/rudolph/netatmo/JacksonTransform.kt @@ -1,4 +1,4 @@ -package io.rudolph.netatmo.api.common.transform +package io.rudolph.netatmo import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper @@ -7,6 +7,7 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule import io.rudolph.netatmo.api.common.model.DeviceType import io.rudolph.netatmo.api.common.model.Scale import io.rudolph.netatmo.api.common.model.ScaleType +import io.rudolph.netatmo.api.common.transform.* import io.rudolph.netatmo.api.energy.model.TemperatureType import io.rudolph.netatmo.api.energy.model.ThermMode import io.rudolph.netatmo.api.energy.model.ZoneType @@ -14,6 +15,8 @@ import io.rudolph.netatmo.api.energy.model.transform.TemperaturTypeDeserializer import io.rudolph.netatmo.api.energy.model.transform.ThermModeDeserializer import io.rudolph.netatmo.api.energy.model.transform.ZoneTypeDeserializer import io.rudolph.netatmo.api.energy.model.transform.ZoneTypeSerializer +import io.rudolph.netatmo.api.presence.model.EventType +import io.rudolph.netatmo.api.presence.transform.EventTypeDeserializer import io.rudolph.netatmo.api.weather.model.Measure import io.rudolph.netatmo.api.weather.model.transform.MeasureDeserializer import io.rudolph.netatmo.oauth2.model.Scope @@ -35,6 +38,7 @@ internal object JacksonTransform { addDeserializer(Measure::class.java, MeasureDeserializer()) addDeserializer(Scale::class.java, ScaleDeserializer()) addDeserializer(ScaleType::class.java, ScaleTypeDeserializer()) + addDeserializer(EventType::class.java, EventTypeDeserializer()) addSerializer(DeviceType::class.java, DeviceTypeSerializer()) addSerializer(ZoneType::class.java, ZoneTypeSerializer()) diff --git a/src/main/java/io/rudolph/netatmo/NetatmoApi.kt b/src/main/java/io/rudolph/netatmo/NetatmoApi.kt index c13e0a2..3d5398d 100644 --- a/src/main/java/io/rudolph/netatmo/NetatmoApi.kt +++ b/src/main/java/io/rudolph/netatmo/NetatmoApi.kt @@ -2,9 +2,10 @@ package io.rudolph.netatmo import com.jakewharton.retrofit2.adapter.kotlin.coroutines.experimental.CoroutineCallAdapterFactory import io.rudolph.netatmo.api.aircare.AirCareConnector -import io.rudolph.netatmo.api.common.transform.JacksonTransform import io.rudolph.netatmo.api.energy.EnergyConnector +import io.rudolph.netatmo.api.presence.PresenceConnector import io.rudolph.netatmo.api.weather.WeatherConnector +import io.rudolph.netatmo.api.welcome.WelcomeConnector import io.rudolph.netatmo.oauth2.TokenStorage import io.rudolph.netatmo.oauth2.model.Scope import io.rudolph.netatmo.oauth2.networkinterceptor.AuthInterceptor @@ -107,4 +108,6 @@ class NetatmoApi(userMail: String? = null, val energyApiConnector = EnergyConnector(api) val weatherApiConnector = WeatherConnector(api) val airCareConnector = AirCareConnector(api) + val presenceConnector = PresenceConnector(api) + val welcomeConnector = WelcomeConnector(api) } \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/aircare/AirCareConnector.kt b/src/main/java/io/rudolph/netatmo/api/aircare/AirCareConnector.kt index f08998c..9b3e706 100644 --- a/src/main/java/io/rudolph/netatmo/api/aircare/AirCareConnector.kt +++ b/src/main/java/io/rudolph/netatmo/api/aircare/AirCareConnector.kt @@ -4,6 +4,7 @@ import io.rudolph.netatmo.api.aircare.model.AirCareBody import io.rudolph.netatmo.api.aircare.service.AirCareService import io.rudolph.netatmo.api.common.CommonConnector import io.rudolph.netatmo.api.energy.model.TypedBaseResult +import io.rudolph.netatmo.executable import io.rudolph.netatmo.executable.Executable import retrofit2.Retrofit @@ -25,10 +26,7 @@ class AirCareConnector(api: Retrofit) : CommonConnector(api) { return airCareService.getPublicData( "Empty", deviceId// will be replaced in Chain.proceed(accessToken: String) - ) - .let { - Executable(it) - } + ).executable } } \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/common/CommonConnector.kt b/src/main/java/io/rudolph/netatmo/api/common/CommonConnector.kt index db6feda..ce6df08 100644 --- a/src/main/java/io/rudolph/netatmo/api/common/CommonConnector.kt +++ b/src/main/java/io/rudolph/netatmo/api/common/CommonConnector.kt @@ -5,6 +5,7 @@ import io.rudolph.netatmo.api.common.model.Scale import io.rudolph.netatmo.api.common.model.ScaleType import io.rudolph.netatmo.api.common.service.CommonService import io.rudolph.netatmo.api.energy.model.TypedBaseResult +import io.rudolph.netatmo.executable import io.rudolph.netatmo.executable.Executable import io.rudolph.netatmo.oauth2.toTimestamp import retrofit2.Retrofit @@ -54,8 +55,6 @@ abstract class CommonConnector(api: Retrofit) { limit = limit, optimize = optimize, realTime = realTime - ).let { - Executable(it) - } + ).executable } } \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/energy/EnergyConnector.kt b/src/main/java/io/rudolph/netatmo/api/energy/EnergyConnector.kt index f1362ab..db832a8 100644 --- a/src/main/java/io/rudolph/netatmo/api/energy/EnergyConnector.kt +++ b/src/main/java/io/rudolph/netatmo/api/energy/EnergyConnector.kt @@ -4,6 +4,7 @@ import io.rudolph.netatmo.api.common.CommonConnector import io.rudolph.netatmo.api.common.model.DeviceType import io.rudolph.netatmo.api.energy.model.* import io.rudolph.netatmo.api.energy.service.EnergyService +import io.rudolph.netatmo.executable import io.rudolph.netatmo.executable.Executable import io.rudolph.netatmo.oauth2.toTimestamp import retrofit2.Retrofit @@ -27,9 +28,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { fun getHomesData(homeId: String? = null, gatewayTypes: List? = null): Executable> { return energyService.getHomeData(homeId, gatewayTypes?.toMutableList()) - .let { - Executable(it) - } + .executable } /** @@ -46,9 +45,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { fun getHomeStatus(homeId: String, deviceTypes: List? = null): Executable> { return energyService.getHomeStatus(homeId, deviceTypes?.toMutableList()) - .let { - Executable(it) - } + .executable } /** @@ -87,9 +84,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { limit, optimize, realTime) - .let { - Executable(it) - } + .executable } /** @@ -110,9 +105,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { return energyService.setRoomThermMode(homeId, thermMode, endTime.toTimestamp()) - .let { - Executable(it) - } + .executable } /** @@ -139,9 +132,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { mode, temperature, endTime.toTimestamp()) - .let { - Executable(it) - } + .executable } /** @@ -193,9 +184,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { hgTemp, awayTemp) return energyService.setSyncHomeSchedule(body) - .let { - Executable(it) - } + .executable } /** @@ -213,9 +202,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { fun renameHomeSchedule(scheduleId: String, name: String, homeId: String): Executable { - return energyService.postRenameHomeSchedule(scheduleId, name, homeId).let { - Executable(it) - } + return energyService.postRenameHomeSchedule(scheduleId, name, homeId).executable } /** @@ -231,9 +218,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { */ fun deleteHomeSchedule(scheduleId: String, homeId: String): Executable { - return energyService.deleteHomeSchedule(scheduleId, homeId).let { - Executable(it) - } + return energyService.deleteHomeSchedule(scheduleId, homeId).executable } /** @@ -264,9 +249,7 @@ class EnergyConnector(api: Retrofit) : CommonConnector(api) { hgTemp, awayTemp) return energyService.createNewHomeSchedule(body) - .let { - Executable(it) - } + .executable } diff --git a/src/main/java/io/rudolph/netatmo/api/presence/PresenceConnector.kt b/src/main/java/io/rudolph/netatmo/api/presence/PresenceConnector.kt new file mode 100644 index 0000000..0553467 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/PresenceConnector.kt @@ -0,0 +1,297 @@ +package io.rudolph.netatmo.api.presence + +import io.rudolph.netatmo.api.common.CommonConnector +import io.rudolph.netatmo.api.energy.model.BaseResult +import io.rudolph.netatmo.api.energy.model.TypedBaseResult +import io.rudolph.netatmo.api.presence.model.Camera +import io.rudolph.netatmo.api.presence.model.Event +import io.rudolph.netatmo.api.presence.model.Events +import io.rudolph.netatmo.api.presence.model.SecurityHome +import io.rudolph.netatmo.api.presence.service.PresenceService +import io.rudolph.netatmo.executable +import io.rudolph.netatmo.executable.Executable +import retrofit2.Retrofit + +open class PresenceConnector(api: Retrofit) : CommonConnector(api) { + private val presenceService = api.create(PresenceService::class.java) + + /** + * Returns the snapshot associated to an event. + * + * required scope: none required + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/getcamerapicture) + * + * @param imageId id of the image (can be retrieved as "id" in "face" in [getHomeData] for Welcome, or as "id" in "snapshot" in [getNextEvents], [io.rudolph.netatmo.api.welcome.WelcomeConnector.getLastEventOf] and [getEventsUntil]) + * @param key Security key to access snapshots + * @return an executable object to obtain the camera picture as jpg bytes wraped in [String] with size of 120x120 + */ + fun getCameraPicture(imageId: String, key: String): Executable { + return presenceService.getCamerapPicture(imageId = imageId, + key = key).executable + } + + /** + * Returns all the events until the one specified in the request. + * + * required scope: read_camera, access_camera, read_presence, access_presence + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/geteventsuntil) + * + * @param homeId ID of the Home you're interested in + * @param eventId Your request will retrieve all the events until this one + * @return an executable object to obtain the List of [Event] + */ + fun getEventsUntil(homeId: String, eventId: String): Executable> { + return presenceService.getEventsUntil( + accessToken = "", + homeId = homeId, + eventId = eventId + ).executable + } + + /** + * Returns information about users homes and cameras. + * + * required scope: read_camera, access_camera, read_presence, access_presence + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/gethomedata) + * + * @param homeId ID of the Home you're interested in + * @param eventId Your request will retrieve all the events until this one + * @return an executable object to obtain the [SecurityHome] + */ + fun getHomeData(homeId: String, eventId: String): Executable> { + return presenceService.getHomeData( + accessToken = "", + homeId = homeId, + eventId = eventId + ).executable + } + + /** + * Returns previous events. + * + * required scope: read_camera, access_camera, read_presence, access_presence + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/getnextevents) + * + * @param homeId ID of the Home you're interested in + * @param eventId Your request will retrieve events occured before this one + * @param size Number of event to retrieve. Default is 30. + * @return an executable object to obtain the List of [Event] + */ + fun getNextEvents(homeId: String, eventId: String, size: Int? = null): Executable> { + return presenceService.getNextEvents( + accessToken = "", + homeId = homeId, + eventId = eventId, + size = size + ).executable + } + + /** + * Links a callback url to a user. + * + * required scope: read_camera, read_presence + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/addwebhook) + * + * @param url Your webhook callback url + * @param appTypes Webhooks are only available for Welcome and Presence, use app_security. + * @return an executable object to obtain the [BaseResult] + */ + fun addWebHook(url: String, appTypes: String): Executable { + return presenceService.addWebHook( + accessToken = "", + url = url, + appTypes = appTypes + ).executable + } + + /** + * Dissociates a webhook from a user. + * + * required scope: none required + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/dropwebhook) + * + * @param appTypes For Welcome and Presence, use app_security + * @return an executable object to obtain the [BaseResult] + */ + fun dropWebHook(appTypes: String): Executable { + return presenceService.dropWebHook( + accessToken = " ", + appTypes = appTypes + ).executable + } + + + /** + * Checks if application and Welcome/Presence camera are on the same local network + * + * required scope: access_camera, access_presence + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/gethomedata) + * + * @param url ID of the Home you're interested in + * @param eventId Your request will retrieve all the events until this one + * @return an executable object to obtain the [SecurityHome] + */ + fun ping(url: String) = Executable(presenceService.ping(url)) + + private fun getLocalUrl(camera: Camera): String? { + return camera.vpnUrl?.let { + getLocalUrl(it) + } + } + + private fun getLocalUrl(url: String): String? { + presenceService.ping(url) + .execute() + .let { + if (!it.isSuccessful) { + return null + } + val newUrl = it.body()?.localUrl ?: return null + return presenceService.ping(newUrl) + .execute() + .let { response -> + it.body()?.localUrl?.let { responseUrl -> + if (responseUrl == newUrl) { + responseUrl + } else { + null + } + } ?: url + } + } + } + + fun getLocalUrlAsync(camera: Camera, success: (String) -> Unit, error: (String) -> Unit) { + val url = camera.vpnUrl ?: let { + error("no vpn url given") + return + } + getLocalUrlAsync(url, success, error) + } + + fun getLocalUrlAsync(camera: Camera, callback: Executable.Callback) { + val url = camera.vpnUrl ?: let { + callback.onError("no vpn url given") + return + } + getLocalUrlAsync(url, { + callback.onResult(it) + }, { + callback.onError(it) + }) + } + + fun getLocalUrlAsync(url: String, success: (String) -> Unit, error: (String) -> Unit) { + ping(url).apply { + onError(error).executeAsync { + val newurl = it.localUrl ?: let { + error("no new url retreived") + return@executeAsync + } + ping(newurl).onError { + kotlin.error("cannot access camera") + }.executeAsync { + it.localUrl?.apply { + if (this == newurl) { + success(this) + } else { + kotlin.error("result urls do not match") + } + } ?: apply { + error("camera ping error") + } + } + } + } + } + + fun getLiveSnapshotUrl(url: String): String? { + return getLocalUrl(url)?.let { + "$it/live/snapshot_720.jpg" + } + } + + fun getLiveSnapshotUrl(camera: Camera): String? { + return getLocalUrl(camera)?.let { + "$it/live/snapshot_720.jpg" + } + } + + fun getLiveSnapshotUrlAsync(camera: Camera, success: (String) -> Unit, error: (String) -> Unit) { + getLocalUrlAsync(camera, success, error) + } + + fun getStreamingUrl(baseUrl: String) { + getLocalUrl(baseUrl)?.let { + "$it/live/index_local.m3u8" + } + } + + fun getStreamingUrl(camera: Camera) { + getLocalUrl(camera)?.let { + "$it/live/index_local.m3u8" + } + } + + fun getStreamingUrlAsync(camera: Camera, success: (String) -> Unit, error: (String) -> Unit) { + val url = camera.vpnUrl ?: let { + error("no vpn url given") + return + } + getLocalUrlAsync(url, { + success("$it/live/index_local.m3u8") + }, { + success("$url/live/index_local.m3u8") + }) + } + + + fun getStreamingUrlAsync(url: String, success: (String) -> Unit = {}, error: (String) -> Unit = {}) { + getLocalUrlAsync(url, { + success("$it/live/index_local.m3u8") + }, { + success("$url/live/index_local.m3u8") + }) + } + + fun getStreamingUrlAsync(url: String, callback: Executable.Callback) { + getLocalUrlAsync(url, { + callback.onResult(it) + }, { + callback.onError(it) + }) + } + + fun getStreamingUrlAsync(camera: Camera, callback: Executable.Callback) { + getLocalUrlAsync(camera, { value: String -> callback.onResult(value) }, { value: String -> callback.onError(value) }) + } + + + fun getVodUrl(camera: Camera, videoId: String): String? { + return getLocalUrl(camera)?.let { + "$it/camera_url/vod/$videoId/index_local.m3u8" + } + } + + fun getVodUrlAsnyc(camera: Camera, videoId: String, success: (String) -> Unit, error: (String) -> Unit) { + getLocalUrlAsync(camera, { + success("$this/camera_url/vod/$videoId/index_local.m3u8") + }, error) + } + + fun getVodUrlAsnyc(camera: Camera, videoId: String, callback: Executable.Callback) { + getLocalUrlAsync(camera, { + callback.onResult("$it/camera_url/vod/$videoId/index_local.m3u8") + }, { + callback.onError(it) + }) + } + +} \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Camera.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Camera.kt new file mode 100644 index 0000000..f228773 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Camera.kt @@ -0,0 +1,30 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Camera( + @JsonProperty("alim_status") + val alimStatus: String? = null, + + @JsonProperty("sd_status") + val sdStatus: String? = null, + + @JsonProperty("name") + val name: String? = null, + + @JsonProperty("id") + val id: String? = null, + + @JsonProperty("type") + val type: String? = null, + + @JsonProperty("is_local") + val isLocal: Boolean? = null, + + @JsonProperty("vpn_url") + val vpnUrl: String? = null, + + @JsonProperty("status") + val status: String? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Event.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Event.kt new file mode 100644 index 0000000..aea4363 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Event.kt @@ -0,0 +1,74 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Event( + /** + * Subtypes of SD and Alim events. Go to Cameras page for further details. + * + * TODO change type to [EventSubType] + */ + @JsonProperty("sub_type") + private val subType: String? = null, + + /** + * Camera that detected the event + */ + @JsonProperty("camera_id") + private val cameraId: String? = null, + + /** + * Status of the video (recording, deleted or available) + */ + @JsonProperty("video_status") + private val videoStatus: String? = null, + + /** + * Identifier of the event + */ + @JsonProperty("id") + private val id: String? = null, + + /** + * Time of occurence of event + */ + @JsonProperty("time") + private val time: Int? = null, + + /** + * Type of events. + */ + @JsonProperty("type") + private val type: EventType? = null, + + /** + * User facing event description + */ + @JsonProperty("message") + private val message: String? = null, + + /** + * If person was considered "away" before being seen during this event + */ + @JsonProperty("is_arrival") + private val isArrival: Boolean? = null, + + /** + * Snapshot id, version and key. (Used in [io.rudolph.netatmo.api.presence.PresenceConnector.getCameraPicture]) + */ + @JsonProperty("snapshot") + private val snapshot: Snapshot? = null, + + /** + * Id of the person the event is about (if any) + */ + @JsonProperty("person_id") + private val personId: String? = null, + + /** + * Identifier of the video + */ + @JsonProperty("video_id") + private val videoId: String? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/EventSubType.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/EventSubType.kt new file mode 100644 index 0000000..b3aa45f --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/EventSubType.kt @@ -0,0 +1,5 @@ +package io.rudolph.netatmo.api.presence.model + +enum class EventSubType { + // TODO Add Subtypes from https://dev.netatmo.com/en-US/resources/technical/reference/security#event +} \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/EventType.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/EventType.kt new file mode 100644 index 0000000..a0076ea --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/EventType.kt @@ -0,0 +1,113 @@ +package io.rudolph.netatmo.api.presence.model + +enum class EventType(val value: String, val product: List) { + /** + * Event triggered when Welcome detects a face + */ + PERSON("Person", listOf("Welcome")), + + /** + * Event triggered when geofencing implies the person has left the home + */ + PERSON_AWAY("Person_away", listOf("Welcome")), + + /** + * Event triggered when Welcome detects a motion + */ + MOVEMENT("Movement", listOf("Welcome")), + + /** + * Event triggered when Presence detects a human, a car or an animal + */ + OUTDOOR("Outdoor", listOf("Presence")), + + /** + * When the camera connects to Netatmo servers + */ + CONNECTION("Connection", listOf("Welcome", "Presence")), + + /** + * When the camera loses connection to Netatmo servers + */ + DISCONNECTION("Disconnection", listOf("Welcome", "Presence")), + + /** + * Whenever monitoring is activated + */ + ON("On", listOf("Welcome", "Presence")), + + /** + * Whenever monitoring is suspended + */ + OFF("Off", listOf("Welcome", "Presence")), + + /** + * When the camera is booting + */ + BOOT("Boot", listOf("Welcome", "Presence")), + + /** + * Event triggered by the SD card status change + */ + SD("SD", listOf("Welcome", "Presence")), + + /** + * Event triggered by the power supply status change + */ + ALIM("Alim", listOf("Welcome", "Presence")), + + /** + * Event triggered when the video summary of the last 24 hours is available + */ + DAILY_SUMMARY("daily_summary", listOf("Presence")), + + /** + * A new module has been paired with Welcome + */ + NEW_MODULE("new_module", listOf("Welcome")), + + /** + * Module is connected with Welcome (after disconnection) + */ + MODULE_CONNECT("module_connect", listOf("Welcome")), + + /** + * Module lost its connection with Welcome + */ + MODULE_DISCONNECT("module_disconnect", listOf("Welcome")), + + /** + * Module's battery is low + */ + MODULE_LOW_BATTERY("module_low_battery", listOf("Welcome")), + + /** + * Module's firmware update is over + */ + MODULE_END_UPDATE("module_end_update", listOf("Welcome")), + + /** + * Tag detected a big move + */ + TAG_BIG_MOVE("tag_big_move", listOf("Welcome")), + + /** + * Tag detected a small move + */ + TAG_SMALL_MOVE("tag_small_move", listOf("Welcome")), + + /** + * Tag was uninstalled + */ + TAG_UNINSTALLED("tag_uninstalled", listOf("Welcome")), + + /** + * Tag detected the door/window was left open + */ + TAG_OPEN("tag_open", listOf("Welcome")), + + /** + * Unkown event + */ + UNKNOWN("unknown", listOf()) +} \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Events.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Events.kt new file mode 100644 index 0000000..fef2380 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Events.kt @@ -0,0 +1,9 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Events( + @JsonProperty("events_list") + val eventsList: List? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Face.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Face.kt new file mode 100644 index 0000000..21ea0cf --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Face.kt @@ -0,0 +1,15 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Face( + @JsonProperty("id") + private val id: String? = null, + + @JsonProperty("version") + private val version: Int? = null, + + @JsonProperty("key") + private val key: String? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/GlobalInfo.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/GlobalInfo.kt new file mode 100644 index 0000000..b1a3fc3 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/GlobalInfo.kt @@ -0,0 +1,9 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class GlobalInfo( + @JsonProperty("show_tags") + private val showTags: Boolean? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Home.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Home.kt new file mode 100644 index 0000000..550bfe9 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Home.kt @@ -0,0 +1,27 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Home( + @JsonProperty("cameras") + private val cameras: List? = null, + + @JsonProperty("persons") + private val persons: List? = null, + + @JsonProperty("name") + private val name: String? = null, + + @JsonProperty("id") + private val id: String? = null, + + @JsonProperty("place") + private val place: Place? = null, + + @JsonProperty("events") + private val events: List? = null, + + @JsonProperty("modules") + private val modules: List? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Module.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Module.kt new file mode 100644 index 0000000..2308430 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Module.kt @@ -0,0 +1,27 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Module( + @JsonProperty("last_activity") + private val lastActivity: Int? = null, + + @JsonProperty("rf") + private val rf: String? = null, + + @JsonProperty("battery_percent") + private val batteryPercent: Int? = null, + + @JsonProperty("name") + private val name: String? = null, + + @JsonProperty("id") + private val id: String? = null, + + @JsonProperty("type") + private val type: String? = null, + + @JsonProperty("status") + private val status: String? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Person.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Person.kt new file mode 100644 index 0000000..f6804e5 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Person.kt @@ -0,0 +1,21 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Person( + @JsonProperty("face") + private val face: Face? = null, + + @JsonProperty("last_seen") + private val lastSeen: Int? = null, + + @JsonProperty("id") + private val id: String? = null, + + @JsonProperty("pseudo") + private val pseudo: String? = null, + + @JsonProperty("out_of_sight") + private val outOfSight: Boolean? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Ping.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Ping.kt new file mode 100644 index 0000000..8b911aa --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Ping.kt @@ -0,0 +1,11 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + +data class Ping( + @JsonProperty("local_url") + var localUrl: String? = null, + + @JsonProperty("product_name") + var productName: String? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Place.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Place.kt new file mode 100644 index 0000000..591415c --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Place.kt @@ -0,0 +1,15 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Place( + @JsonProperty("country") + private val country: String? = null, + + @JsonProperty("city") + private val city: String? = null, + + @JsonProperty("timezone") + private val timezone: String? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/SecurityHome.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/SecurityHome.kt new file mode 100644 index 0000000..2f98106 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/SecurityHome.kt @@ -0,0 +1,17 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * information about users homes and cameras. + */ +data class SecurityHome( + @JsonProperty("global_info") + private val globalInfo: GlobalInfo? = null, + + @JsonProperty("homes") + private val homes: List? = null, + + @JsonProperty("user") + private val user: User? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/Snapshot.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/Snapshot.kt new file mode 100644 index 0000000..58e2a2f --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/Snapshot.kt @@ -0,0 +1,15 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class Snapshot( + @JsonProperty("id") + private val id: String? = null, + + @JsonProperty("version") + private val version: Int? = null, + + @JsonProperty("key") + private val key: String? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/model/User.kt b/src/main/java/io/rudolph/netatmo/api/presence/model/User.kt new file mode 100644 index 0000000..4e3d52d --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/model/User.kt @@ -0,0 +1,12 @@ +package io.rudolph.netatmo.api.presence.model + +import com.fasterxml.jackson.annotation.JsonProperty + + +data class User( + @JsonProperty("reg_locale") + private val regLocale: String? = null, + + @JsonProperty("lang") + private val lang: String? = null +) \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/service/PresenceService.kt b/src/main/java/io/rudolph/netatmo/api/presence/service/PresenceService.kt new file mode 100644 index 0000000..22ef429 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/service/PresenceService.kt @@ -0,0 +1,106 @@ +package io.rudolph.netatmo.api.presence.service + +import io.rudolph.netatmo.api.energy.model.BaseResult +import io.rudolph.netatmo.api.energy.model.TypedBaseResult +import io.rudolph.netatmo.api.presence.model.Events +import io.rudolph.netatmo.api.presence.model.Ping +import io.rudolph.netatmo.api.presence.model.SecurityHome +import retrofit2.Call +import retrofit2.http.* + + +internal interface PresenceService { + + @Headers("Content-Type:text/plain") + @GET("getcamerapicture") + fun getCamerapPicture( + @Query("image_id") imageId: String, + @Query("key") key: String + ): Call + + @Headers("Content-Type:text/plain") + @POST("getcamerapicture") + fun getCamerapPicturePost( + @Query("image_id") imageId: String, + @Query("key") key: String + ): Call + + @Headers("Content-Type:text/plain") + @GET("geteventsuntil") + fun getEventsUntil( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("event_id") eventId: String + ): Call> + + @Headers("Content-Type:text/plain") + @POST("geteventsuntil") + fun getEventsUntilPost( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("event_id") eventId: String + ): Call> + + @Headers("Content-Type:text/plain") + @GET("gethomedata") + fun getHomeData( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String? = null, + @Query("size") eventId: String + ): Call> + + @Headers("Content-Type:text/plain") + @POST("gethomedata") + fun getHomeDataPost( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String? = null, + @Query("size") eventId: String + ): Call> + + @Headers("Content-Type:text/plain") + @GET("getnextevents") + fun getNextEvents( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("event_id") eventId: String, + @Query("size") size: Int? = null + ): Call> + + @Headers("Content-Type:text/plain") + @POST("getnextevents") + fun getNextEventsPost( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("event_id") eventId: String, + @Query("size") size: Int? = null + ): Call> + + @Headers("Content-Type:text/plain") + @GET("/command/ping") + fun ping( + @Url url: String + ): Call + + @Headers("Content-Type:text/plain") + @GET("addwebhook") + fun addWebHookGet( + @Query("access_token") accessToken: String, + @Query("url") url: String, + @Query("app_types") app_types: String = "app_security" + ): Call + + @Headers("Content-Type:text/plain") + @POST("addwebhook") + fun addWebHook( + @Query("access_token") accessToken: String, + @Query("url") url: String, + @Query("app_types") appTypes: String = "app_security" + ): Call + + @Headers("Content-Type:text/plain") + @GET("dropwebhook") + fun dropWebHook( + @Query("access_token") accessToken: String, + @Query("app_types") appTypes: String = "app_security" + ): Call +} \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/presence/transform/EventTypeDeserializer.kt b/src/main/java/io/rudolph/netatmo/api/presence/transform/EventTypeDeserializer.kt new file mode 100644 index 0000000..28b4fa2 --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/presence/transform/EventTypeDeserializer.kt @@ -0,0 +1,17 @@ +package io.rudolph.netatmo.api.presence.transform + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import io.rudolph.netatmo.api.presence.model.EventType + + +class EventTypeDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): EventType { + return p?.valueAsString?.let { name -> + EventType.values().find { + it.value == name + } + } ?: EventType.UNKNOWN + } +} diff --git a/src/main/java/io/rudolph/netatmo/api/weather/WeatherConnector.kt b/src/main/java/io/rudolph/netatmo/api/weather/WeatherConnector.kt index cb869d1..fb6d263 100644 --- a/src/main/java/io/rudolph/netatmo/api/weather/WeatherConnector.kt +++ b/src/main/java/io/rudolph/netatmo/api/weather/WeatherConnector.kt @@ -4,6 +4,7 @@ import io.rudolph.netatmo.api.common.CommonConnector import io.rudolph.netatmo.api.energy.model.TypedBaseResult import io.rudolph.netatmo.api.weather.model.Station import io.rudolph.netatmo.api.weather.service.WeatherService +import io.rudolph.netatmo.executable import io.rudolph.netatmo.executable.Executable import retrofit2.Retrofit @@ -38,10 +39,8 @@ class WeatherConnector(api: Retrofit) : CommonConnector(api) { latitudeSouthWest, longitudeSouthWest, required, - filter) - .let { - Executable(it) - } + filter + ).executable } } \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/weather/model/transform/MeasureDeserializer.kt b/src/main/java/io/rudolph/netatmo/api/weather/model/transform/MeasureDeserializer.kt index cdd297e..05862f8 100644 --- a/src/main/java/io/rudolph/netatmo/api/weather/model/transform/MeasureDeserializer.kt +++ b/src/main/java/io/rudolph/netatmo/api/weather/model/transform/MeasureDeserializer.kt @@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonNode -import io.rudolph.netatmo.api.common.transform.JacksonTransform +import io.rudolph.netatmo.JacksonTransform import io.rudolph.netatmo.api.weather.model.BaseOutdoorMeasure import io.rudolph.netatmo.api.weather.model.Measure import io.rudolph.netatmo.api.weather.model.RainGaugeMeasure diff --git a/src/main/java/io/rudolph/netatmo/api/welcome/WelcomeConnector.kt b/src/main/java/io/rudolph/netatmo/api/welcome/WelcomeConnector.kt new file mode 100644 index 0000000..cfb0e3d --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/welcome/WelcomeConnector.kt @@ -0,0 +1,74 @@ +package io.rudolph.netatmo.api.welcome + +import io.rudolph.netatmo.api.energy.model.BaseResult +import io.rudolph.netatmo.api.energy.model.TypedBaseResult +import io.rudolph.netatmo.api.presence.PresenceConnector +import io.rudolph.netatmo.api.presence.model.Events +import io.rudolph.netatmo.api.welcome.service.WelcomeService +import io.rudolph.netatmo.executable +import io.rudolph.netatmo.executable.Executable +import retrofit2.Retrofit + +class WelcomeConnector(api: Retrofit) : PresenceConnector(api) { + private val welcomeService = api.create(WelcomeService::class.java) + + /** + * Returns most recent events. + * + * required scope: read_camera, access_camera + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/getlasteventof) + * @param homeId ID of the Home you're interested in + * @param personId Your request will retrieve all events of the given home until the most recent event of the given person + * @param offset Number of events to retrieve. Default is 30. + * @return an executable object to obtain the List of [Event] + */ + fun getLastEventOf(homeId: String, + personId: String, + offset: Int? = null): Executable> { + return welcomeService.getLastEventOf( + accessToken = "", + homeId = homeId, + personId = personId, + offset = offset + ).executable + } + + /** + * Sets a person as "Away" or the Home as "Empty". The event will be added to the user’s timeline. + * + * required scope: write_camera + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/setpersonsaway) + * + * @param homeId ID of the Home you're interested in + * @param personId If a person_id is specified, that person will be set as "Away". If no person_id is specified, the Home will be set as "Empty". + * @return an executable object to obtain the [BaseResult] + */ + fun setPersonsAway(homeId: String, personId: String? = null): Executable { + return welcomeService.setPersonsAway( + accessToken = "", + homeId = homeId, + personId = personId + ).executable + } + + /** + * Sets a person as "Home"". The event will be added to the user’s timeline. + * + * required scope: write_camera + * + * @see [Netatmo Api Reference] (https://dev.netatmo.com/resources/technical/reference/security/setpersonshome) + * + * @param homeId ID of the Home you're interested in + * @param personId Array of person_id + * @return an executable object to obtain the [BaseResult] + */ + fun setPersonsAway(homeId: String, personIds: List): Executable { + return welcomeService.setPersonsHome( + accessToken = "", + homeId = homeId, + personIds = personIds + ).executable + } +} \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/api/welcome/service/WelcomeService.kt b/src/main/java/io/rudolph/netatmo/api/welcome/service/WelcomeService.kt new file mode 100644 index 0000000..216d78c --- /dev/null +++ b/src/main/java/io/rudolph/netatmo/api/welcome/service/WelcomeService.kt @@ -0,0 +1,65 @@ +package io.rudolph.netatmo.api.welcome.service + +import io.rudolph.netatmo.api.energy.model.BaseResult +import io.rudolph.netatmo.api.energy.model.TypedBaseResult +import io.rudolph.netatmo.api.presence.model.Events +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.POST +import retrofit2.http.Query + + +internal interface WelcomeService { + + @Headers("Content-Type:text/plain") + @GET("getlasteventof") + fun getLastEventOf( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("person_id") personId: String, + @Query("offset") offset: Int? = null + ): Call> + + @Headers("Content-Type:text/plain") + @POST("getlasteventof") + fun getLastEventOfPost( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("person_id") personId: String, + @Query("offset") offset: Int? = null + ): Call> + + @Headers("Content-Type:text/plain") + @GET("setpersonsaway") + fun setPersonsAwayGet( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("person_id") personId: String? = null + ): Call + + @Headers("Content-Type:text/plain") + @POST("setpersonsaway") + fun setPersonsAway( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("person_id") personId: String? = null + ): Call + + @Headers("Content-Type:text/plain") + @GET("setpersonshome") + fun setPersonsHomeGet( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("person_ids") personIds: List + ): Call + + @Headers("Content-Type:text/plain") + @POST("setpersonshome") + fun setPersonsHome( + @Query("access_token") accessToken: String, + @Query("home_id") homeId: String, + @Query("person_ids") personIds: List + ): Call + +} \ No newline at end of file diff --git a/src/main/java/io/rudolph/netatmo/oauth2/networkinterceptor/AuthInterceptor.kt b/src/main/java/io/rudolph/netatmo/oauth2/networkinterceptor/AuthInterceptor.kt index 053af86..4be7990 100644 --- a/src/main/java/io/rudolph/netatmo/oauth2/networkinterceptor/AuthInterceptor.kt +++ b/src/main/java/io/rudolph/netatmo/oauth2/networkinterceptor/AuthInterceptor.kt @@ -1,6 +1,6 @@ package io.rudolph.netatmo.oauth2.networkinterceptor -import io.rudolph.netatmo.api.common.transform.JacksonTransform +import io.rudolph.netatmo.JacksonTransform import io.rudolph.netatmo.oauth2.TokenStorage import io.rudolph.netatmo.oauth2.errorbuilder import io.rudolph.netatmo.oauth2.logger diff --git a/src/test/kotlin/apitest/TestConfig.kt b/src/test/kotlin/apitest/TestConfig.kt index 7bb0a2a..98f4939 100644 --- a/src/test/kotlin/apitest/TestConfig.kt +++ b/src/test/kotlin/apitest/TestConfig.kt @@ -1,7 +1,7 @@ package apitest import com.fasterxml.jackson.annotation.JsonProperty -import io.rudolph.netatmo.api.common.transform.JacksonTransform +import io.rudolph.netatmo.JacksonTransform data class TestConfig(