From 178fe804dcc8e365fe2ecf4905de5b6a69146e90 Mon Sep 17 00:00:00 2001 From: johan12345 Date: Mon, 20 Apr 2026 23:45:50 +0200 Subject: [PATCH] Remove NewMotionAvailabilityDetector old Shell Recharge/NewMotion map doesn't exist anymore, the new one is limited to Shell's own chargers --- .../api/availability/AvailabilityDetector.kt | 3 +- .../NewMotionAvailabilityDetector.kt | 231 ------------------ 2 files changed, 1 insertion(+), 233 deletions(-) delete mode 100644 app/src/main/java/net/vonforst/evmap/api/availability/NewMotionAvailabilityDetector.kt diff --git a/app/src/main/java/net/vonforst/evmap/api/availability/AvailabilityDetector.kt b/app/src/main/java/net/vonforst/evmap/api/availability/AvailabilityDetector.kt index fa7bd3b3..7c43106f 100644 --- a/app/src/main/java/net/vonforst/evmap/api/availability/AvailabilityDetector.kt +++ b/app/src/main/java/net/vonforst/evmap/api/availability/AvailabilityDetector.kt @@ -176,8 +176,7 @@ class AvailabilityRepository(context: Context) { teslaOwnerAvailabilityDetector, TeslaGuestAvailabilityDetector(okhttp), NobilAvailabilityDetector(okhttp, context), - EnBwAvailabilityDetector(okhttp), - NewMotionAvailabilityDetector(okhttp) + EnBwAvailabilityDetector(okhttp) ) suspend fun getAvailability(charger: ChargeLocation): Resource { diff --git a/app/src/main/java/net/vonforst/evmap/api/availability/NewMotionAvailabilityDetector.kt b/app/src/main/java/net/vonforst/evmap/api/availability/NewMotionAvailabilityDetector.kt deleted file mode 100644 index ee141834..00000000 --- a/app/src/main/java/net/vonforst/evmap/api/availability/NewMotionAvailabilityDetector.kt +++ /dev/null @@ -1,231 +0,0 @@ -package net.vonforst.evmap.api.availability - -import com.squareup.moshi.FromJson -import com.squareup.moshi.JsonClass -import com.squareup.moshi.Moshi -import com.squareup.moshi.ToJson -import net.vonforst.evmap.model.ChargeLocation -import net.vonforst.evmap.model.Chargepoint -import net.vonforst.evmap.utils.distanceBetween -import okhttp3.OkHttpClient -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import retrofit2.http.GET -import retrofit2.http.Path -import java.time.ZonedDateTime -import java.util.Locale - -private const val coordRange = 0.005 // range of latitude and longitude for loading the map -private const val maxDistance = 60 // max distance between reported positions in meters - -interface NewMotionApi { - @GET("markers/{lngMin}/{lngMax}/{latMin}/{latMax}/{zoom}") - suspend fun getMarkers( - @Path("lngMin") lngMin: Double, - @Path("lngMax") lngMax: Double, - @Path("latMin") latMin: Double, - @Path("latMax") latMax: Double, - @Path("zoom") zoom: Int = 22 - ): List - - @GET("locations/{id}") - suspend fun getLocation(@Path("id") id: Long): NMLocation - - @JsonClass(generateAdapter = true) - data class NMMarker(val coordinates: NMCoordinates, val locationUid: Long, val evseCount: Int) - - @JsonClass(generateAdapter = true) - data class NMCoordinates(val latitude: Double, val longitude: Double) - - @JsonClass(generateAdapter = true) - data class NMLocation( - val uid: Long, - val coordinates: NMCoordinates, - val operatorName: String, - val evses: List - ) - - @JsonClass(generateAdapter = true) - data class NMEvse( - val evseId: String?, - val status: String, - val connectors: List, - val updated: ZonedDateTime? - ) - - @JsonClass(generateAdapter = true) - data class NMConnector( - val uid: Long, - val connectorType: String, - val electricalProperties: NMElectricalProperties - ) - - @JsonClass(generateAdapter = true) - data class NMElectricalProperties(val powerType: String, val voltage: Int, val amperage: Int, val maxElectricPower: Double?) { - fun getPower(): Double { - maxElectricPower?.let { return it } - val phases = when (powerType) { - "AC1Phase" -> 1 - "AC3Phase" -> 3 - else -> 1 - } - val volt = when (voltage) { - 277 -> 230 - else -> voltage - } - val power = volt * amperage * phases - return when (power) { - 3680 -> 3.7 - 11040 -> 11.0 - 22080 -> 22.0 - 43470 -> 43.0 - else -> power / 1000.0 - } - } - } - - companion object { - fun create(client: OkHttpClient, baseUrl: String? = null): NewMotionApi { - val retrofit = Retrofit.Builder() - .baseUrl(baseUrl ?: "https://ui-map.shellrecharge.com/api/map/v2/") - .addConverterFactory( - MoshiConverterFactory.create( - Moshi.Builder().add(ZonedDateTimeAdapter()).build() - ) - ) - .client(client) - .build() - return retrofit.create(NewMotionApi::class.java) - } - } -} - -internal class ZonedDateTimeAdapter { - @FromJson - fun fromJson(value: String): ZonedDateTime? = ZonedDateTime.parse(value) - - @ToJson - fun toJson(value: ZonedDateTime): String = value.toString() -} - -data class NmStatus( - val conn: NewMotionApi.NMConnector, - val status: String, - val evseId: String?, - val updated: ZonedDateTime? -) - -class NewMotionAvailabilityDetector(client: OkHttpClient, baseUrl: String? = null) : - BaseAvailabilityDetector(client) { - val api = NewMotionApi.create(client, baseUrl) - - override suspend fun getAvailability(location: ChargeLocation): ChargeLocationStatus { - val lat = location.coordinates.lat - val lng = location.coordinates.lng - - // find nearest station to this position - var markers = - api.getMarkers(lng - coordRange, lng + coordRange, lat - coordRange, lat + coordRange) - val nearest = markers.minByOrNull { marker -> - distanceBetween(marker.coordinates.latitude, marker.coordinates.longitude, lat, lng) - } ?: throw AvailabilityDetectorException("no candidates found.") - - if (distanceBetween( - nearest.coordinates.latitude, - nearest.coordinates.longitude, - lat, - lng - ) > radius - ) { - throw AvailabilityDetectorException("no candidates found") - } - - markers = if (nearest.evseCount < location.totalChargepoints) { - // combine related stations - markers.filter { marker -> - distanceBetween( - marker.coordinates.latitude, - marker.coordinates.longitude, - nearest.coordinates.latitude, - nearest.coordinates.longitude - ) < maxDistance - } - } else { - listOf(nearest) - } - - // load details - var details = markers.map { - api.getLocation(it.locationUid) - } - // only include stations from same operator - details = details.filter { - it.operatorName == details[0].operatorName - } - val connectorStatus = details.flatMap { it.evses }.flatMap { evse -> - evse.connectors.map { connector -> - NmStatus(connector, evse.status, evse.evseId, evse.updated) - } - } - - val nmConnectors = mutableMapOf>() - val nmStatus = mutableMapOf() - val nmEvseId = mutableMapOf() - val nmUpdated = mutableMapOf() - connectorStatus.forEach { (connector, statusStr, evseId, updated) -> - val id = connector.uid - val power = connector.electricalProperties.getPower() - val type = when (connector.connectorType.lowercase(Locale.ROOT)) { - "type3" -> Chargepoint.TYPE_3C - "type2" -> Chargepoint.TYPE_2_UNKNOWN - "type1" -> Chargepoint.TYPE_1 - "domestic" -> Chargepoint.SCHUKO - "type1combo" -> Chargepoint.CCS_TYPE_1 // US CCS, aka type1_combo - "type2combo" -> Chargepoint.CCS_TYPE_2 // EU CCS, aka type2_combo - "tepcochademo" -> Chargepoint.CHADEMO - "unspecified" -> "unknown" - "unknown" -> "unknown" - "saej1772" -> "unknown" - else -> "unknown" - } - val status = when (statusStr) { - "Unavailable" -> ChargepointStatus.FAULTED - "Available" -> ChargepointStatus.AVAILABLE - "Occupied" -> ChargepointStatus.CHARGING - "Unspecified" -> ChargepointStatus.UNKNOWN - else -> ChargepointStatus.UNKNOWN - } - nmConnectors.put(id, power to type) - nmStatus.put(id, status) - evseId?.let { nmEvseId[id] = it } - updated?.let { nmUpdated[id] = it } - } - - val match = matchChargepoints(nmConnectors, location.chargepointsMerged) - val chargepointStatus = match.mapValues { entry -> - entry.value.map { nmStatus[it]!! } - } - val evseIds = if (nmEvseId.size == nmStatus.size) match.mapValues { entry -> - entry.value.map { nmEvseId[it]!! } - } else null - val updated = match.mapValues { entry -> entry.value.map { nmUpdated[it]?.toInstant() } } - return ChargeLocationStatus( - chargepointStatus, - "NewMotion", - evseIds, - lastChange = updated - ) - } - - override fun isChargerSupported(charger: ChargeLocation): Boolean { - // NewMotion is our fallback - return when (charger.dataSource) { - "goingelectric" -> charger.network != "Tesla Supercharger" - "nobil" -> charger.network != "Tesla" - "openchargemap" -> charger.chargepriceData?.network !in listOf("23", "3534") - "openstreetmap" -> charger.operator !in listOf("Tesla, Inc.", "Tesla") - else -> false - } - } - -} \ No newline at end of file