implement AvailabilityDetector using chargecloud API (#1)

This commit is contained in:
Johan von Forstner
2020-03-31 19:04:34 +02:00
parent 118eb6a329
commit 14e314f54c
6 changed files with 149 additions and 7 deletions

View File

@@ -0,0 +1,86 @@
package com.johan.evmap.api
import kotlinx.coroutines.ExperimentalCoroutinesApi
import okhttp3.*
import org.json.JSONObject
import java.io.IOException
import java.util.concurrent.TimeUnit
private const val radius = 100 // max radius in meters
interface AvailabilityDetector {
suspend fun getAvailability(location: ChargeLocation): Map<Chargepoint, List<ChargepointStatus>>
}
enum class ChargepointStatus {
AVAILABLE, UNKNOWN, CHARGING
}
class ChargecloudAvailabilityDetector(private val client: OkHttpClient,
private val operatorId: String): AvailabilityDetector {
@ExperimentalCoroutinesApi
override suspend fun getAvailability(location: ChargeLocation): Map<Chargepoint, List<ChargepointStatus>> {
val url = "https://app.chargecloud.de/emobility:ocpi/$operatorId/app/2.0/locations?latitude=${location.coordinates.lat}&longitude=${location.coordinates.lng}&radius=$radius&offset=0&limit=10"
val request = Request.Builder().url(url).build()
val response = client.newCall(request).await()
if (!response.isSuccessful) throw IOException(response.message())
val json = JSONObject(response.body()!!.string())
val statusMessage = json.getString("status_message")
if (statusMessage != "Success") throw IOException(statusMessage)
val data = json.getJSONArray("data")
if (data.length() > 1) throw IOException("found multiple candidates.")
if (data.length() == 0) throw IOException("no candidates found.")
val evses = data.getJSONObject(0).getJSONArray("evses")
val chargepointStatus = mutableMapOf<Chargepoint, List<ChargepointStatus>>()
evses.iterator<JSONObject>().forEach { evse ->
evse.getJSONArray("connectors").iterator<JSONObject>().forEach connector@{ connector ->
val type = getType(connector.getString("standard"))
val power = connector.getDouble("max_power")
val status = ChargepointStatus.valueOf(connector.getString("status"))
if (type == null) return@connector
var chargepoint = chargepointStatus.keys.filter {
it.type == type
it.power == power
}.getOrNull(0)
val statusList: List<ChargepointStatus>
if (chargepoint == null) {
chargepoint = Chargepoint(type, power, 1)
statusList = listOf(status)
} else {
val previousStatus = chargepointStatus[chargepoint]!!
statusList = previousStatus + listOf(status)
chargepointStatus.remove(chargepoint)
chargepoint =
Chargepoint(chargepoint.type, chargepoint.power, chargepoint.count + 1)
}
chargepointStatus[chargepoint] = statusList
}
}
return chargepointStatus
}
private fun getType(string: String): String? {
return when (string) {
"IEC_62196_T2" -> Chargepoint.TYPE_2
"DOMESTIC_F" -> Chargepoint.SCHUKO
"IEC_62196_T2_COMBO" -> Chargepoint.CCS
"CHADEMO" -> Chargepoint.CHADEMO
else -> null
}
}
}
private val okhttp = OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.build()
val availabilityDetectors = listOf(
ChargecloudAvailabilityDetector(okhttp, "6336fe713f2eb7fa04b97ff6651b76f8")
)

View File

@@ -179,4 +179,12 @@ data class Chargepoint(val type: String, val power: Double, val count: Int) : Eq
}
return "$powerFmt kW"
}
companion object {
const val TYPE_2 = "Type2"
const val CCS = "CCS"
const val SCHUKO = "Schuko"
const val CHADEMO = "CHAdeMO"
const val SUPERCHARGER = "Tesla Supercharger"
}
}

View File

@@ -0,0 +1,38 @@
package com.johan.evmap.api
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import java.io.IOException
import kotlin.coroutines.resumeWithException
operator fun<T> JSONArray.iterator(): Iterator<T>
= (0 until length()).asSequence().map { get(it) as T }.iterator()
@ExperimentalCoroutinesApi
suspend fun Call.await(): Response {
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response) {}
}
override fun onFailure(call: Call, e: IOException) {
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation {
try {
cancel()
} catch (ex: Throwable) {
//Ignore cancel exception
}
}
}
}

View File

@@ -10,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.johan.evmap.R
import com.johan.evmap.api.Chargepoint
@BindingAdapter("goneUnless")
@@ -51,11 +52,11 @@ fun <T> setRecyclerViewData(recyclerView: ViewPager2, items: List<T>?) {
fun getConnectorItem(view: ImageView, type: String) {
view.setImageResource(
when (type) {
"CCS" -> R.drawable.ic_connector_ccs
"CHAdeMO" -> R.drawable.ic_connector_chademo
"Schuko" -> R.drawable.ic_connector_schuko
"Tesla Supercharger" -> R.drawable.ic_connector_supercharger
"Typ2" -> R.drawable.ic_connector_typ2
Chargepoint.CCS -> R.drawable.ic_connector_ccs
Chargepoint.CHADEMO -> R.drawable.ic_connector_chademo
Chargepoint.SCHUKO -> R.drawable.ic_connector_schuko
Chargepoint.SUPERCHARGER -> R.drawable.ic_connector_supercharger
Chargepoint.TYPE_2 -> R.drawable.ic_connector_typ2
// TODO: add other connectors
else -> 0
}