diff --git a/.travis.yml b/.travis.yml
index e4a2a017..4057a40d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ env:
- secure: KYdFlMarsyXw+OHht1Atp+Kirbw9O09Ck14EjFuKb1eNtknurZ/tGEXuD+8xWh1W8W21kgHEG7s3rzru53t29buz+FW9f+ZmhEWXFP3OydyvXLw4BAVVOjm6xG2uHX/8MOGLJNM7cfaF25EPQ+kznHe84R29KaLH90mNRr2lPa4VnfbcnvDStiVaez/vJ72UoYSP5HICAzoF70yC3ZvvCK1hZv71UIysCbFE2IkxvMhG9OOGebdnRmFssaRCrvfRLjitobcLzkPWzZZIqdjNASf8/iAxX8VgGBYfVj8ID06AfMrtgXNJRCvcD0LICraQ+WPUbikMunRieGO8PNHSB5vKdPoC50aLUa0RoRb4G3QM1pR2A8xAFlIJFX2R7iY+2t24L9hRFqB98+QoQzutfkAI1T0rzem/wtpZpuan+bDawDJEHGCeYbE0aPDAl6lytgrEE9fRgV3c1jJLQzu0xIWG8YLl3iMg0hL+c0wCKXoeqrfCFS6kYmmG7W+rQp4tCZifvRbWfAXwIPQieffKxqdEuUwiUsYxdzCu9v9uU3nflEOLLuRgeMP3gV8mpur9b5GztpkfgfzcAqsF+NiY01kYgGtrgCYlMy0TxASE+UuALrtkQtU01wwhs9RH7Az0Ib3C+MT5DTjxHQCYETIViocmNEG2vfAbgHazCpGAhcY=
- secure: Hoko50vP+Mwm/O4CWvPvjMxd1gGhi+Bultjyy1WpludSmZFCfKz45Mj1EqzeYk6MeMLvGOEkSLB5wjdXdgJ9j5gEOF5K34k4vESJA7+DqDO3I7Xw9cnWgOXdFqB0qGHar0TVP3Dfg7ZcRgtmeX6t2uoELFLiS9GvTnbZXk4PCUybd979Xi8XHjEQV7+3EZbSjtsI4GFeIK1rrjd0I+UM88zYrWnz1KhdCjWvQb4iZjo+ib6NmGGEMqR7jJPRZz3KB01Y+n5h21qq9/Nv31zQIqt2B5nRxy3vBvvqKapgprIk+hVOpNnBU8w89uWUU6tYUeFk0t7z1TYWjgaBrMmGCM+aKkQ2q5F/ygNzDwB+KkpJx709Yhf0ZX8g2yvkdz+Ok7moYuvmrOPOf4E/U3BlfZxbGtRD2bGYbDgHLFYlTn6v5J2kJHDJAz31yvF5jvJejDaPp2IBVfoMRy7ZJFmGUNHGd9Se6bRwxS++AoobP5WDrBiUXNe2KKDMs3e7vbaO+hLbZ9XHpjeWIJGUfvtTee8EHZqF/8A3ju53V4/R0ehlOv2UZbpYNqcmwrsy9/R4pgMfDkG3Q054LmYrxD8DIC9b8excVMwWRP5aQ1TqZnxO2B1/vJU87RcnGl3jekeHTdHXbRq9BMV4dAdPfB9X3nGIi2GgV0iTBk+24xccOc0=
- secure: RsN/2nBVv0byMzwchuAhDti1AWKECg3Uzqk6Ahgaqg02zx8GHj60j0qNax7KuMUg70X3G4b3m8ZzndAU0wcJ98UyIku7ofUYgXHm0XYKTJwiyyhrKJ8pZ4qoeCoRkitIGIitlb84fSufVamcoLyLNbLUsO5OzL+Uscyhq/BwAhyOhj5FB9DM+GE9ntanQ4m3k4guMyMctR9CGS6Tk4LKJ1WCm0AnjTPalc+we+7yORY2J/9d6VHLKfaFXbpuWmrdnFfDZqxqcUODsxPVE0LuUtXyKQDTjjfQc8106Z/z19AJS+oLm/2UND84PD3MqsjX6EX0/9k3fY0OsoCuQAivDRmtevQ0bDQrTAyeBLcfnPCw/MYiJWyBcaYBAYK42EAfsFTDBRIduFB/Dpvg+8GuLZSdm4xVYpTlQ2pMtlGNWIGIRQsjk9LZ8swq8QBMiiF/bpMGKdfmnyQj8jkEOCWaAzkR9O+4E4Y7PuBENef9XuV0hLMryCrML2YXigxAKkEUOPhfnG+AbEY+g2kAMp+2EtXaG3tsGxoMhEYA+uRd3o2cacQMMwRpnePH5DYg6F05mrvdHPpt+P9UR1iHaHmjBPAYeksmrdP8bS088zcnePgiL6+6N0m3+l8Krihmxg5RyWjnH18IwX4RO+xg4x3cW+zaScCDbbotDMEYtChF+Hs=
+ - secure: LQHMdhaPUlCuJPFrCPpUphJSY6xzAFI/7RrcAVLtLcPhGdS+MeNifIkkAH7MeitTHroOC0dGkZ4bg/8/7bKfgwY4vPH9P50kZcnX5mI6zfBHgNYJzuthj+vJH9RAtkdQOW9Fe1uPIx8R9GUWUOVnkoJh0PQ1gDXdZW5fePqUtn1kYrcCCBE+Bhe3wz6QzTBqGS1nsVRTxQfSJNGi9uH1oi9kQGgQFuCCiJ/P0A6MIhSItkOfuggx/iorA+iASbhWkB4nXYQBbFe/ZhFJWbVfgYlOM0HtpKh8B2AqKw21Em32JoovCbUof4adkY7cH8/4Rt9SujC9YOw+a6oM+e//jJT0sie77V7zl670j+qODTuNvV4qVUwtoxShyc1Sfbd+Xb0xn/OC7DzBg97YuYCF/84yyuq12rl/cofynWE1L5YvGNSJk241XUw98Bvl0MK4VIfQvG9zJP0HnQZcWKt6kFOIEJSCRbmkd2tPPAZFBXBQf/bvpULOoKwneGJZBSapRoCyGwemM+EAzVB9UOXAqsXZ4FHkt1SSJVrTVwgxvXpCfmF6LZPhbz6nvouRWGsC/GdWjrHtdW5lEOvS27qKEL5rXwQ0o+71ZICGo8j4E0GOHXyi857qZhvO7cbOnts+iiawXiWzPXv2gGGabuqPwcU8JPEoWdaiIaeGUczfjBU=
- ANDROID_HOME=$HOME/android-sdk
before_install:
- openssl aes-256-cbc -K $encrypted_53968681344a_key -iv $encrypted_53968681344a_iv -in _ci/keystore.jks.enc -out _ci/keystore.jks -d
diff --git a/_img/powered_by_chargeprice.svg b/_img/powered_by_chargeprice.svg
new file mode 100644
index 00000000..d97f8e43
--- /dev/null
+++ b/_img/powered_by_chargeprice.svg
@@ -0,0 +1,130 @@
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 045b3054..59dc70b6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -85,6 +85,10 @@ android {
if (mapboxKey != null) {
variant.resValue "string", "mapbox_key", mapboxKey
}
+ def chargepriceKey = env.CHARGEPRICE_API_KEY ?: project.findProperty("CHARGEPRICE_API_KEY")
+ if (chargepriceKey != null) {
+ variant.resValue "string", "chargeprice_key", chargepriceKey
+ }
}
}
@@ -108,6 +112,8 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.9.0'
implementation 'com.squareup.moshi:moshi-kotlin:1.9.2'
+ implementation 'moe.banana:moshi-jsonapi:3.5.0'
+ implementation 'moe.banana:moshi-jsonapi-retrofit-converter:3.5.0'
implementation 'io.coil-kt:coil:1.1.0'
implementation 'com.github.MikeOrtiz:TouchImageView:3.0.3'
implementation "com.mikepenz:aboutlibraries-core:$about_libs_version"
diff --git a/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt b/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt
index 702c7db5..f52334de 100644
--- a/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt
+++ b/app/src/main/java/net/vonforst/evmap/adapter/DataBindingAdapters.kt
@@ -1,16 +1,24 @@
package net.vonforst.evmap.adapter
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import net.vonforst.evmap.BR
import net.vonforst.evmap.R
import net.vonforst.evmap.api.availability.ChargepointStatus
+import net.vonforst.evmap.api.chargeprice.ChargePrice
+import net.vonforst.evmap.api.chargeprice.ChargepriceChargepointMeta
+import net.vonforst.evmap.api.chargeprice.ChargepriceTag
import net.vonforst.evmap.api.goingelectric.Chargepoint
+import net.vonforst.evmap.databinding.ItemChargepriceBinding
+import net.vonforst.evmap.databinding.ItemConnectorButtonBinding
+import net.vonforst.evmap.ui.CheckableConstraintLayout
import net.vonforst.evmap.viewmodel.FavoritesViewModel
interface Equatable {
@@ -88,4 +96,73 @@ class FavoritesAdapter(val vm: FavoritesViewModel) :
override fun getItemViewType(position: Int): Int = R.layout.item_favorite
override fun getItemId(position: Int): Long = getItem(position).charger.id
+}
+
+class ChargepriceAdapter() :
+ DataBindingAdapter() {
+
+ val viewPool = RecyclerView.RecycledViewPool();
+ var meta: ChargepriceChargepointMeta? = null
+ set(value) {
+ field = value
+ notifyDataSetChanged()
+ }
+
+ override fun getItemViewType(position: Int): Int = R.layout.item_chargeprice
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val holder = super.onCreateViewHolder(parent, viewType)
+ val binding = holder.binding as ItemChargepriceBinding
+ binding.rvTags.apply {
+ adapter = ChargepriceTagsAdapter()
+ layoutManager =
+ LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply {
+ recycleChildrenOnDetach = true
+ }
+ itemAnimator = null
+ setRecycledViewPool(viewPool)
+ }
+ return holder
+ }
+
+ override fun bind(holder: ViewHolder, item: ChargePrice) {
+ super.bind(holder, item)
+ (holder.binding as ItemChargepriceBinding).meta = meta
+ }
+}
+
+class CheckableConnectorAdapter : DataBindingAdapter() {
+ private var checkedItem: Int = 0
+
+ override fun getItemViewType(position: Int): Int = R.layout.item_connector_button
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ super.bind(holder, getItem(position))
+ val binding = holder.binding as ItemConnectorButtonBinding
+ val root = binding.root as CheckableConstraintLayout
+ root.isChecked = checkedItem == position
+ root.setOnClickListener {
+ root.isChecked = true
+ }
+ root.setOnCheckedChangeListener { v: View, checked: Boolean ->
+ if (checked) {
+ checkedItem = position
+ notifyDataSetChanged()
+ onCheckedItemChangedListener?.invoke(getCheckedItem())
+ }
+ }
+ }
+
+ fun getCheckedItem(): Chargepoint = getItem(checkedItem)
+
+ fun setCheckedItem(item: Chargepoint) {
+ checkedItem = currentList.indexOf(item)
+ }
+
+ var onCheckedItemChangedListener: ((Chargepoint) -> Unit)? = null
+}
+
+class ChargepriceTagsAdapter() :
+ DataBindingAdapter() {
+ override fun getItemViewType(position: Int): Int = R.layout.item_chargeprice_tag
}
\ No newline at end of file
diff --git a/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceApi.kt b/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceApi.kt
new file mode 100644
index 00000000..24c410b9
--- /dev/null
+++ b/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceApi.kt
@@ -0,0 +1,75 @@
+package net.vonforst.evmap.api.chargeprice
+
+import android.content.Context
+import com.facebook.stetho.okhttp3.StethoInterceptor
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+import moe.banana.jsonapi2.ArrayDocument
+import moe.banana.jsonapi2.JsonApiConverterFactory
+import moe.banana.jsonapi2.ResourceAdapterFactory
+import net.vonforst.evmap.BuildConfig
+import okhttp3.Cache
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.POST
+
+interface ChargepriceApi {
+ @POST("charge_prices")
+ suspend fun getChargePrices(
+ @Body request: ChargepriceRequest,
+ @Header("Accept-Language") language: String
+ ): ArrayDocument
+
+ @GET("vehicles")
+ suspend fun getVehicles(): ArrayDocument
+
+ companion object {
+ private val cacheSize = 1L * 1024 * 1024 // 1MB
+ val supportedLanguages = setOf("de", "en", "fr", "nl")
+
+ private val jsonApiAdapterFactory = ResourceAdapterFactory.builder()
+ .add(ChargepriceRequest::class.java)
+ .add(ChargepriceTariff::class.java)
+ .add(ChargepriceBrand::class.java)
+ .add(ChargePrice::class.java)
+ .add(ChargepriceCar::class.java)
+ .build()
+ val moshi = Moshi.Builder()
+ .add(jsonApiAdapterFactory)
+ .add(KotlinJsonAdapterFactory())
+ .build()
+ fun create(
+ apikey: String,
+ baseurl: String = "https://api.chargeprice.app/v1/",
+ context: Context? = null
+ ): ChargepriceApi {
+ val client = OkHttpClient.Builder().apply {
+ addInterceptor { chain ->
+ // add API key to every request
+ val original = chain.request()
+ val new = original.newBuilder()
+ .header("API-Key", apikey)
+ .header("Content-Type", "application/json")
+ .build()
+ chain.proceed(new)
+ }
+ if (BuildConfig.DEBUG) {
+ addNetworkInterceptor(StethoInterceptor())
+ }
+ if (context != null) {
+ cache(Cache(context.getCacheDir(), cacheSize))
+ }
+ }.build()
+
+ val retrofit = Retrofit.Builder()
+ .baseUrl(baseurl)
+ .addConverterFactory(JsonApiConverterFactory.create(moshi))
+ .client(client)
+ .build()
+ return retrofit.create(ChargepriceApi::class.java)
+ }
+ }
+}
diff --git a/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceModel.kt b/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceModel.kt
new file mode 100644
index 00000000..0235b596
--- /dev/null
+++ b/app/src/main/java/net/vonforst/evmap/api/chargeprice/ChargepriceModel.kt
@@ -0,0 +1,280 @@
+package net.vonforst.evmap.api.chargeprice
+
+import android.content.Context
+import com.squareup.moshi.Json
+import moe.banana.jsonapi2.HasMany
+import moe.banana.jsonapi2.HasOne
+
+import moe.banana.jsonapi2.JsonApi
+import moe.banana.jsonapi2.Resource
+import net.vonforst.evmap.R
+import net.vonforst.evmap.adapter.Equatable
+import net.vonforst.evmap.api.goingelectric.ChargeLocation
+import net.vonforst.evmap.ui.currency
+import kotlin.math.ceil
+import kotlin.math.floor
+
+
+@JsonApi(type = "charge_price_request")
+class ChargepriceRequest : Resource() {
+ @field:Json(name = "data_adapter")
+ lateinit var dataAdapter: String
+ lateinit var station: ChargepriceStation
+ lateinit var options: ChargepriceOptions
+ var tariffs: HasMany? = null
+ var vehicle: HasOne? = null
+}
+
+data class ChargepriceStation(
+ val longitude: Double,
+ val latitude: Double,
+ val country: String?,
+ val network: String?,
+ @Json(name = "charge_points") val chargePoints: List
+) {
+ companion object {
+ fun fromGoingelectric(geCharger: ChargeLocation): ChargepriceStation {
+ return ChargepriceStation(
+ geCharger.coordinates.lng,
+ geCharger.coordinates.lat,
+ geCharger.address.country,
+ geCharger.network,
+ geCharger.chargepoints.map {
+ ChargepriceChargepoint(it.power, it.type)
+ }
+ )
+ }
+ }
+}
+
+data class ChargepriceChargepoint(
+ val power: Double,
+ val plug: String
+)
+
+data class ChargepriceOptions(
+ @Json(name = "max_monthly_fees") val maxMonthlyFees: Double? = null,
+ val energy: Double? = null,
+ val duration: Int? = null,
+ @Json(name = "battery_range") val batteryRange: List? = null,
+ @Json(name = "car_ac_phases") val carAcPhases: Int? = null,
+ val currency: String? = null,
+ @Json(name = "start_time") val startTime: Int? = null,
+ @Json(name = "allow_unbalanced_load") val allowUnbalancedLoad: Boolean? = null,
+ @Json(name = "provider_customer_tariffs") val providerCustomerTariffs: Boolean? = null
+)
+
+@JsonApi(type = "tariff")
+data class ChargepriceTariff(
+ val provider: String,
+ val name: String,
+ @field:Json(name = "direct_payment") val directPayment: Boolean,
+ @field:Json(name = "provider_customer_tariff") val providerCustomerTariff: Boolean,
+ @field:Json(name = "charge_card_id") val chargeCardId: String // GE charge card ID
+) : Resource()
+
+@JsonApi(type = "car")
+class ChargepriceCar : Resource() {
+ lateinit var name: String
+ lateinit var brand: String
+
+ @field:Json(name = "dc_charge_ports")
+ lateinit var dcChargePorts: List
+ lateinit var manufacturer: HasOne
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ if (!super.equals(other)) return false
+
+ other as ChargepriceCar
+
+ if (name != other.name) return false
+ if (brand != other.brand) return false
+ if (dcChargePorts != other.dcChargePorts) return false
+ if (manufacturer != other.manufacturer) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + brand.hashCode()
+ result = 31 * result + dcChargePorts.hashCode()
+ result = 31 * result + manufacturer.hashCode()
+ return result
+ }
+}
+
+@JsonApi(type = "brand")
+class ChargepriceBrand : Resource()
+
+@JsonApi(type = "charge_price")
+class ChargePrice : Resource(), Equatable, Cloneable {
+ lateinit var provider: String
+
+ @field:Json(name = "tariff_name")
+ lateinit var tariffName: String
+ lateinit var url: String
+
+ @field:Json(name = "monthly_min_sales")
+ var monthlyMinSales: Double = 0.0
+
+ @field:Json(name = "total_monthly_fee")
+ var totalMonthlyFee: Double = 0.0
+
+ @field:Json(name = "flat_rate")
+ var flatRate: Boolean = false
+
+ @field:Json(name = "direct_payment")
+ var directPayment: Boolean = false
+
+ @field:Json(name = "provider_customer_tariff")
+ var providerCustomerTariff: Boolean = false
+ lateinit var currency: String
+
+ @field:Json(name = "start_time")
+ var startTime: Int = 0
+ lateinit var tags: List
+
+ @field:Json(name = "charge_point_prices")
+ lateinit var chargepointPrices: List
+
+
+ fun formatMonthlyFees(ctx: Context): String {
+ return listOfNotNull(
+ if (totalMonthlyFee > 0) {
+ ctx.getString(R.string.chargeprice_base_fee, totalMonthlyFee, currency(currency))
+ } else null,
+ if (monthlyMinSales > 0) {
+ ctx.getString(R.string.chargeprice_min_spend, monthlyMinSales, currency(currency))
+ } else null
+ ).joinToString(", ")
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+ if (!super.equals(other)) return false
+
+ other as ChargePrice
+
+ if (provider != other.provider) return false
+ if (tariffName != other.tariffName) return false
+ if (url != other.url) return false
+ if (monthlyMinSales != other.monthlyMinSales) return false
+ if (totalMonthlyFee != other.totalMonthlyFee) return false
+ if (flatRate != other.flatRate) return false
+ if (directPayment != other.directPayment) return false
+ if (providerCustomerTariff != other.providerCustomerTariff) return false
+ if (currency != other.currency) return false
+ if (startTime != other.startTime) return false
+ if (tags != other.tags) return false
+ if (chargepointPrices != other.chargepointPrices) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = super.hashCode()
+ result = 31 * result + provider.hashCode()
+ result = 31 * result + tariffName.hashCode()
+ result = 31 * result + url.hashCode()
+ result = 31 * result + monthlyMinSales.hashCode()
+ result = 31 * result + totalMonthlyFee.hashCode()
+ result = 31 * result + flatRate.hashCode()
+ result = 31 * result + directPayment.hashCode()
+ result = 31 * result + providerCustomerTariff.hashCode()
+ result = 31 * result + currency.hashCode()
+ result = 31 * result + startTime
+ result = 31 * result + tags.hashCode()
+ result = 31 * result + chargepointPrices.hashCode()
+ return result
+ }
+
+ public override fun clone(): ChargePrice {
+ return ChargePrice().apply {
+ chargepointPrices = this@ChargePrice.chargepointPrices
+ currency = this@ChargePrice.currency
+ directPayment = this@ChargePrice.directPayment
+ flatRate = this@ChargePrice.flatRate
+ monthlyMinSales = this@ChargePrice.monthlyMinSales
+ provider = this@ChargePrice.provider
+ providerCustomerTariff = this@ChargePrice.providerCustomerTariff
+ startTime = this@ChargePrice.startTime
+ tags = this@ChargePrice.tags
+ tariffName = this@ChargePrice.tariffName
+ totalMonthlyFee = this@ChargePrice.totalMonthlyFee
+ url = this@ChargePrice.url
+ }
+ }
+}
+
+data class ChargepointPrice(
+ val power: Double,
+ val plug: String,
+ val price: Double,
+ @Json(name = "price_distribution") val priceDistribution: PriceDistribution,
+ @Json(name = "blocking_fee_start") val blockingFeeStart: Int?,
+ @Json(name = "no_price_reason") var noPriceReason: String?
+) {
+ fun formatDistribution(ctx: Context): String {
+ fun percent(value: Double): String {
+ return ctx.getString(R.string.percent_format, value * 100) + "\u00a0"
+ }
+
+ fun time(value: Int): String {
+ val h = floor(value.toDouble() / 60).toInt();
+ val min = ceil(value.toDouble() % 60).toInt();
+ if (h == 0 && min > 0) return "${min}min";
+ // be slightly sloppy (3:01 is shown as 3h) to save space
+ else if (h > 0 && (min == 0 || min == 1)) return "${h}h";
+ else return "%d:%02dh".format(h, min);
+ }
+
+ // based on https://github.com/chargeprice/chargeprice-client/blob/d420bb2f216d9ad91a210a36dd0859a368a8229a/src/views/priceList.js
+ with(priceDistribution) {
+ return listOfNotNull(
+ if (session != null && session > 0.0) {
+ (if (session < 1) percent(session) else "") + ctx.getString(R.string.chargeprice_session_fee)
+ } else null,
+ if (kwh != null && kwh > 0.0 && !isOnlyKwh) {
+ (if (kwh < 1) percent(kwh) else "") + ctx.getString(R.string.chargeprice_per_kwh)
+ } else null,
+ if (minute != null && minute > 0.0) {
+ (if (minute < 1) percent(minute) else "") + ctx.getString(R.string.chargeprice_per_minute) +
+ if (blockingFeeStart != null) {
+ " (${
+ ctx.getString(
+ R.string.chargeprice_blocking_fee,
+ time(blockingFeeStart)
+ )
+ })"
+ } else ""
+ } else null,
+ if ((minute == null || minute == 0.0) && blockingFeeStart != null) {
+ ctx.getString(R.string.chargeprice_blocking_fee, time(blockingFeeStart))
+ } else null
+ ).joinToString(" +\u00a0")
+ }
+ }
+}
+
+data class PriceDistribution(val kwh: Double?, val session: Double?, val minute: Double?) {
+ val isOnlyKwh =
+ kwh != null && kwh > 0 && (session == null || session == 0.0) && (minute == null || minute == 0.0)
+}
+
+data class ChargepriceTag(val kind: String, val text: String, val url: String?) : Equatable
+
+data class ChargepriceMeta(
+ @Json(name = "charge_points") val chargePoints: List
+)
+
+data class ChargepriceChargepointMeta(
+ val power: Double,
+ val plug: String,
+ val energy: Double,
+ val duration: Double
+)
\ No newline at end of file
diff --git a/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricApi.kt b/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricApi.kt
index f6333be6..6c3f502f 100644
--- a/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricApi.kt
+++ b/app/src/main/java/net/vonforst/evmap/api/goingelectric/GoingElectricApi.kt
@@ -69,6 +69,13 @@ interface GoingElectricApi {
companion object {
private val cacheSize = 10L * 1024 * 1024 // 10MB
+ val moshi = Moshi.Builder()
+ .add(ChargepointListItemJsonAdapterFactory())
+ .add(JsonObjectOrFalseAdapter.Factory())
+ .add(HoursAdapter())
+ .add(InstantAdapter())
+ .build()
+
fun create(
apikey: String,
baseurl: String = "https://api.goingelectric.de",
@@ -90,13 +97,6 @@ interface GoingElectricApi {
}
}.build()
- val moshi = Moshi.Builder()
- .add(ChargepointListItemJsonAdapterFactory())
- .add(JsonObjectOrFalseAdapter.Factory())
- .add(HoursAdapter())
- .add(InstantAdapter())
- .build()
-
val retrofit = Retrofit.Builder()
.baseUrl(baseurl)
.addConverterFactory(MoshiConverterFactory.create(moshi))
diff --git a/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt
new file mode 100644
index 00000000..28937a0f
--- /dev/null
+++ b/app/src/main/java/net/vonforst/evmap/fragment/ChargepriceFragment.kt
@@ -0,0 +1,163 @@
+package net.vonforst.evmap.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.widget.Toolbar
+import androidx.databinding.DataBindingUtil
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.Observer
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.ui.setupWithNavController
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import net.vonforst.evmap.MapsActivity
+import net.vonforst.evmap.R
+import net.vonforst.evmap.adapter.ChargepriceAdapter
+import net.vonforst.evmap.adapter.CheckableConnectorAdapter
+import net.vonforst.evmap.api.goingelectric.ChargeLocation
+import net.vonforst.evmap.api.goingelectric.Chargepoint
+import net.vonforst.evmap.api.goingelectric.GoingElectricApi
+import net.vonforst.evmap.databinding.FragmentChargepriceBinding
+import net.vonforst.evmap.viewmodel.ChargepriceViewModel
+import net.vonforst.evmap.viewmodel.viewModelFactory
+import java.text.NumberFormat
+
+class ChargepriceFragment : DialogFragment() {
+ private lateinit var binding: FragmentChargepriceBinding
+
+ private val vm: ChargepriceViewModel by viewModels(factoryProducer = {
+ viewModelFactory {
+ ChargepriceViewModel(
+ requireActivity().application,
+ getString(R.string.chargeprice_key)
+ )
+ }
+ })
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ dialog?.window?.attributes?.windowAnimations = R.style.ChargepriceDialogAnimation
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = DataBindingUtil.inflate(
+ inflater,
+ R.layout.fragment_chargeprice, container, false
+ )
+ binding.lifecycleOwner = this
+ binding.vm = vm
+
+ binding.toolbar.inflateMenu(R.menu.chargeprice)
+ binding.toolbar.setTitle(R.string.chargeprice_title)
+
+ return binding.root
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ // dialog with 95% screen height
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ (resources.displayMetrics.heightPixels * 0.95).toInt()
+ )
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val toolbar = view.findViewById(R.id.toolbar) as Toolbar
+
+ val navController = findNavController()
+ toolbar.setupWithNavController(
+ navController,
+ (requireActivity() as MapsActivity).appBarConfiguration
+ )
+
+ val jsonAdapter = GoingElectricApi.moshi.adapter(ChargeLocation::class.java)
+ val charger = jsonAdapter.fromJson(requireArguments().getString(ARG_CHARGER)!!)!!
+ vm.charger.value = charger
+ if (vm.chargepoint.value == null) {
+ vm.chargepoint.value = charger.chargepointsMerged.get(0)
+ }
+
+ val chargepriceAdapter = ChargepriceAdapter().apply {
+ onClickListener = {
+ (requireActivity() as MapsActivity).openUrl(it.url)
+ }
+ }
+ binding.chargePricesList.apply {
+ adapter = chargepriceAdapter
+ layoutManager =
+ LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
+ addItemDecoration(
+ DividerItemDecoration(
+ context, LinearLayoutManager.VERTICAL
+ )
+ )
+ }
+ vm.chargepriceMetaForChargepoint.observe(viewLifecycleOwner) {
+ chargepriceAdapter.meta = it.data
+ }
+
+ val connectorsAdapter = CheckableConnectorAdapter()
+ binding.connectorsList.apply {
+ adapter = connectorsAdapter
+ layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
+ }
+
+ val observer: Observer = Observer {
+ connectorsAdapter.setCheckedItem(it)
+ }
+ vm.chargepoint.observe(viewLifecycleOwner, observer)
+ connectorsAdapter.onCheckedItemChangedListener = {
+ vm.chargepoint.removeObserver(observer)
+ vm.chargepoint.value = it
+ vm.chargepoint.observe(viewLifecycleOwner, observer)
+ }
+
+ binding.imgChargepriceLogo.setOnClickListener {
+ (requireActivity() as MapsActivity).openUrl("https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=going_electric")
+ }
+
+ binding.btnSettings.setOnClickListener {
+ navController.navigate(R.id.action_chargeprice_to_settingsFragment)
+ }
+
+ binding.batteryRange.setLabelFormatter { value: Float ->
+ val fmt = NumberFormat.getNumberInstance()
+ fmt.maximumFractionDigits = 0
+ fmt.format(value.toDouble())
+ }
+
+ binding.toolbar.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.menu_close -> {
+ dismiss()
+ true
+ }
+ else -> false
+ }
+ }
+ }
+
+ companion object {
+ val ARG_CHARGER = "charger"
+
+ fun showCharger(charger: ChargeLocation): Bundle {
+ return Bundle().apply {
+ putString(
+ ARG_CHARGER,
+ GoingElectricApi.moshi.adapter(ChargeLocation::class.java).toJson(charger)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt
index 4f1a2278..5525ce96 100644
--- a/app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt
+++ b/app/src/main/java/net/vonforst/evmap/fragment/MapFragment.kt
@@ -296,8 +296,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
}
binding.detailView.btnChargeprice.setOnClickListener {
val charger = vm.charger.value?.data ?: return@setOnClickListener
- (activity as? MapsActivity)?.openUrl(
- "https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=going_electric"
+ findNavController().navigate(
+ R.id.action_map_to_chargepriceFragment,
+ ChargepriceFragment.showCharger(charger)
)
}
binding.detailView.topPart.setOnClickListener {
diff --git a/app/src/main/java/net/vonforst/evmap/fragment/SettingsFragment.kt b/app/src/main/java/net/vonforst/evmap/fragment/SettingsFragment.kt
index 148dbb37..7d85892c 100644
--- a/app/src/main/java/net/vonforst/evmap/fragment/SettingsFragment.kt
+++ b/app/src/main/java/net/vonforst/evmap/fragment/SettingsFragment.kt
@@ -4,20 +4,33 @@ import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.Toolbar
+import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
+import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import net.vonforst.evmap.MapsActivity
import net.vonforst.evmap.R
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.ui.updateNightMode
+import net.vonforst.evmap.viewmodel.SettingsViewModel
+import net.vonforst.evmap.viewmodel.viewModelFactory
class SettingsFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var prefs: PreferenceDataSource
+ private val vm: SettingsViewModel by viewModels(factoryProducer = {
+ viewModelFactory {
+ SettingsViewModel(
+ requireActivity().application,
+ getString(R.string.chargeprice_key)
+ )
+ }
+ })
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val toolbar = view.findViewById(R.id.toolbar) as Toolbar
@@ -28,6 +41,20 @@ class SettingsFragment : PreferenceFragmentCompat(),
navController,
(requireActivity() as MapsActivity).appBarConfiguration
)
+
+ val myVehiclePreference = findPreference("chargeprice_my_vehicle")!!
+ myVehiclePreference.isEnabled = false
+ vm.vehicles.observe(viewLifecycleOwner) { res ->
+ res.data?.let { cars ->
+ val sortedCars = cars.sortedBy { it.brand }
+ myVehiclePreference.entryValues = sortedCars.map { it.id }.toTypedArray()
+ myVehiclePreference.entries =
+ sortedCars.map { "${it.brand} ${it.name}" }.toTypedArray()
+ myVehiclePreference.isEnabled = true
+ myVehiclePreference.summary = cars.find { it.id == prefs.chargepriceMyVehicle }
+ ?.let { "${it.brand} ${it.name}" }
+ }
+ }
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
diff --git a/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt b/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt
index 782aba1a..486b7dc7 100644
--- a/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt
+++ b/app/src/main/java/net/vonforst/evmap/storage/PreferenceDataSource.kt
@@ -97,4 +97,22 @@ class PreferenceDataSource(val context: Context) {
set(value) {
sp.edit().putBoolean("update_0.6.0_androidauto_dialog_shown", value).apply()
}
+
+ var chargepriceMyVehicle: String?
+ get() = sp.getString("chargeprice_my_vehicle", null)
+ set(value) {
+ sp.edit().putString("chargeprice_my_vehicle", value).apply()
+ }
+
+ var chargepriceNoBaseFee: Boolean
+ get() = sp.getBoolean("chargeprice_no_base_fee", false)
+ set(value) {
+ sp.edit().putBoolean("chargeprice_no_base_fee", value).apply()
+ }
+
+ var chargepriceShowProviderCustomerTariffs: Boolean
+ get() = sp.getBoolean("chargeprice_show_provider_customer_tariffs", false)
+ set(value) {
+ sp.edit().putBoolean("chargeprice_show_provider_customer_tariffs", value).apply()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/net/vonforst/evmap/ui/BalancedBreakingTextView.kt b/app/src/main/java/net/vonforst/evmap/ui/BalancedBreakingTextView.kt
new file mode 100644
index 00000000..844f84ec
--- /dev/null
+++ b/app/src/main/java/net/vonforst/evmap/ui/BalancedBreakingTextView.kt
@@ -0,0 +1,33 @@
+package net.vonforst.evmap.ui
+
+import android.content.Context
+import android.text.Layout
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatTextView
+import kotlin.math.ceil
+
+class BalancedBreakingTextView(context: Context, attrs: AttributeSet) :
+ AppCompatTextView(context, attrs) {
+
+ @Override
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ if (layout != null) {
+ val width =
+ ceil(getMaxLineWidth(layout)).toInt() + compoundPaddingLeft + compoundPaddingRight
+ val height = measuredHeight
+ setMeasuredDimension(width, height)
+ }
+ }
+
+ private fun getMaxLineWidth(layout: Layout): Float {
+ var maxWidth = 0.0f
+ for (i in 0 until layout.lineCount) {
+ if (layout.getLineWidth(i) > maxWidth) {
+ maxWidth = layout.getLineWidth(i)
+ }
+ }
+ return maxWidth
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt b/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt
index 68f6d60c..9d3cc87c 100644
--- a/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt
+++ b/app/src/main/java/net/vonforst/evmap/ui/BindingAdapters.kt
@@ -11,10 +11,13 @@ import androidx.core.content.ContextCompat
import androidx.core.content.res.use
import androidx.core.text.HtmlCompat
import androidx.databinding.BindingAdapter
+import androidx.databinding.InverseBindingAdapter
+import androidx.databinding.InverseBindingListener
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.google.android.material.slider.RangeSlider
import net.vonforst.evmap.R
import net.vonforst.evmap.api.availability.ChargepointStatus
import net.vonforst.evmap.api.iconForPlugType
@@ -177,6 +180,35 @@ fun setLinkify(textView: TextView, oldValue: Int, newValue: Int) {
}
}
+@BindingAdapter("chargepriceTagColor")
+fun setChargepriceTagColor(view: TextView, kind: String) {
+ view.backgroundTintList = ColorStateList.valueOf(
+ ContextCompat.getColor(
+ view.context,
+ when (kind) {
+ "star" -> R.color.chargeprice_star
+ "alert" -> R.color.chargeprice_alert
+ "info" -> R.color.chargeprice_info
+ "lock" -> R.color.chargeprice_lock
+ else -> R.color.chip_background
+ }
+ )
+ )
+}
+
+@BindingAdapter("chargepriceTagIcon")
+fun setChargepriceTagIcon(view: TextView, kind: String) {
+ view.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ when (kind) {
+ "star" -> R.drawable.ic_chargeprice_star
+ "alert" -> R.drawable.ic_chargeprice_alert
+ "info" -> R.drawable.ic_chargeprice_info
+ "lock" -> R.drawable.ic_chargeprice_lock
+ else -> 0
+ }, 0, 0, 0
+ )
+}
+
private fun availabilityColor(
status: List?,
context: Context
@@ -210,4 +242,31 @@ fun availabilityText(status: List?): String? {
fun flatten(it: Iterable>?): List? {
return it?.flatten()
+}
+
+fun currency(currency: String): String {
+ // shorthands for currencies
+ return when (currency) {
+ "EUR" -> "€"
+ "USD" -> "$"
+ "DKK", "SEK", "NOK" -> "kr."
+ "PLN" -> "zł"
+ "CHF" -> "Fr."
+ "CZK" -> "Kč"
+ "GBP" -> "£"
+ "HRK" -> "kn"
+ "HUF" -> "Ft"
+ "ISK" -> "Kr"
+ else -> currency
+ }
+}
+
+@InverseBindingAdapter(attribute = "app:values")
+fun getRangeSliderValue(slider: RangeSlider) = slider.values
+
+@BindingAdapter("app:valuesAttrChanged")
+fun setRangeSliderListeners(slider: RangeSlider, attrChange: InverseBindingListener) {
+ slider.addOnChangeListener { _, _, _ ->
+ attrChange.onChange()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/net/vonforst/evmap/ui/CheckableConstraintLayout.kt b/app/src/main/java/net/vonforst/evmap/ui/CheckableConstraintLayout.kt
new file mode 100644
index 00000000..92443f65
--- /dev/null
+++ b/app/src/main/java/net/vonforst/evmap/ui/CheckableConstraintLayout.kt
@@ -0,0 +1,48 @@
+package net.vonforst.evmap.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.Checkable
+import androidx.constraintlayout.widget.ConstraintLayout
+
+
+class CheckableConstraintLayout(ctx: Context, attrs: AttributeSet) : ConstraintLayout(ctx, attrs),
+ Checkable {
+ private var onCheckedChangeListener: ((View, Boolean) -> Unit)? = null
+ private var checked = false
+ private val CHECKED_STATE_SET = intArrayOf(android.R.attr.state_checked)
+
+ override fun setChecked(b: Boolean) {
+ if (b != checked) {
+ checked = b;
+ refreshDrawableState();
+ onCheckedChangeListener?.invoke(this, checked);
+ }
+ }
+
+ override fun isChecked(): Boolean {
+ return checked
+ }
+
+ override fun toggle() {
+ checked = !checked
+ }
+
+ override fun onCreateDrawableState(extraSpace: Int): IntArray? {
+ val drawableState = super.onCreateDrawableState(extraSpace + 1)
+ if (isChecked) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET)
+ }
+ return drawableState
+ }
+
+ /**
+ * Register a callback to be invoked when the checked state of this view changes.
+ *
+ * @param listener the callback to call on checked state change
+ */
+ fun setOnCheckedChangeListener(listener: (View, Boolean) -> Unit) {
+ onCheckedChangeListener = listener
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceViewModel.kt b/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceViewModel.kt
new file mode 100644
index 00000000..e3d423bb
--- /dev/null
+++ b/app/src/main/java/net/vonforst/evmap/viewmodel/ChargepriceViewModel.kt
@@ -0,0 +1,152 @@
+package net.vonforst.evmap.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.*
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import moe.banana.jsonapi2.HasOne
+import net.vonforst.evmap.api.chargeprice.*
+import net.vonforst.evmap.api.goingelectric.ChargeLocation
+import net.vonforst.evmap.api.goingelectric.Chargepoint
+import net.vonforst.evmap.storage.PreferenceDataSource
+import java.io.IOException
+import java.util.*
+
+class ChargepriceViewModel(application: Application, chargepriceApiKey: String) :
+ AndroidViewModel(application) {
+ private var api = ChargepriceApi.create(chargepriceApiKey)
+ private var prefs = PreferenceDataSource(application)
+
+ val charger: MutableLiveData by lazy {
+ MutableLiveData()
+ }
+
+ val chargepoint: MutableLiveData by lazy {
+ MutableLiveData()
+ }
+
+ val vehicle: LiveData by lazy {
+ MutableLiveData().apply {
+ value = prefs.chargepriceMyVehicle?.let { ChargepriceCar().apply { id = it } }
+ }
+ }
+
+ val batteryRange: MutableLiveData> by lazy {
+ MutableLiveData>().apply {
+ value = listOf(20f, 80f)
+ }
+ }
+
+ val chargePrices: MediatorLiveData>> by lazy {
+ MediatorLiveData>>().apply {
+ value = Resource.loading(null)
+ listOf(charger, vehicle, batteryRange).forEach {
+ addSource(it) {
+ loadPrices()
+ }
+ }
+ }
+ }
+
+ val chargePriceMeta: MutableLiveData> by lazy {
+ MutableLiveData>().apply {
+ value = Resource.loading(null)
+ }
+ }
+
+ val chargePricesForChargepoint: MediatorLiveData>> by lazy {
+ MediatorLiveData>>().apply {
+ listOf(chargePrices, chargepoint).forEach {
+ addSource(it) {
+ val cps = chargePrices.value
+ val chargepoint = chargepoint.value
+ if (cps == null || chargepoint == null) {
+ value = null
+ } else if (cps.status == Status.ERROR) {
+ value = Resource.error(cps.message, null)
+ } else if (cps.status == Status.LOADING) {
+ value = Resource.loading(null)
+ } else {
+ value = Resource.success(cps.data!!.map { cp ->
+ val filteredPrices =
+ cp.chargepointPrices.filter { it.plug == chargepoint.type && it.power == chargepoint.power }
+ if (filteredPrices.isEmpty()) {
+ null
+ } else {
+ cp.clone().apply {
+ chargepointPrices = filteredPrices
+ }
+ }
+ }.filterNotNull().sortedBy { it.chargepointPrices.first().price })
+ }
+ }
+ }
+ }
+ }
+
+ val chargepriceMetaForChargepoint: MediatorLiveData> by lazy {
+ MediatorLiveData>().apply {
+ listOf(chargePriceMeta, chargepoint).forEach {
+ addSource(it) {
+ val cpMeta = chargePriceMeta.value
+ val chargepoint = chargepoint.value
+ if (cpMeta == null || chargepoint == null) {
+ value = null
+ } else if (cpMeta.status == Status.ERROR) {
+ value = Resource.error(cpMeta.message, null)
+ } else if (cpMeta.status == Status.LOADING) {
+ value = Resource.loading(null)
+ } else {
+ value =
+ Resource.success(cpMeta.data!!.chargePoints.filter { it.plug == chargepoint.type && it.power == chargepoint.power }[0])
+ }
+ }
+ }
+ }
+ }
+
+ private var loadPricesJob: Job? = null
+ private fun loadPrices() {
+ chargePrices.value = Resource.loading(null)
+ val geCharger = charger.value
+ val car = vehicle.value
+ if (geCharger == null || car == null) {
+ chargePrices.value = Resource.error(null, null)
+ return
+ }
+
+ loadPricesJob?.cancel()
+ loadPricesJob = viewModelScope.launch {
+ delay(800)
+ try {
+ val result = api.getChargePrices(ChargepriceRequest().apply {
+ dataAdapter = "going_electric"
+ station = ChargepriceStation.fromGoingelectric(geCharger)
+ vehicle = HasOne(car)
+ options = ChargepriceOptions(
+ batteryRange = batteryRange.value!!.map { it.toDouble() },
+ providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
+ maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null
+ )
+ }, getChargepriceLanguage())
+ val meta =
+ result.meta.get(ChargepriceApi.moshi.adapter(ChargepriceMeta::class.java)) as ChargepriceMeta
+ chargePrices.value = Resource.success(result)
+ chargePriceMeta.value = Resource.success(meta)
+ } catch (e: IOException) {
+ chargePrices.value = Resource.error(e.message, null)
+ chargePriceMeta.value = Resource.error(e.message, null)
+ }
+ }
+ }
+
+ private fun getChargepriceLanguage(): String {
+ val locale = Locale.getDefault().language
+ return if (ChargepriceApi.supportedLanguages.contains(locale)) {
+ locale
+ } else {
+ "en"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/vonforst/evmap/viewmodel/SettingsViewModel.kt b/app/src/main/java/net/vonforst/evmap/viewmodel/SettingsViewModel.kt
new file mode 100644
index 00000000..e5123e72
--- /dev/null
+++ b/app/src/main/java/net/vonforst/evmap/viewmodel/SettingsViewModel.kt
@@ -0,0 +1,33 @@
+package net.vonforst.evmap.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.launch
+import net.vonforst.evmap.api.chargeprice.ChargepriceApi
+import net.vonforst.evmap.api.chargeprice.ChargepriceCar
+import java.io.IOException
+
+class SettingsViewModel(application: Application, chargepriceApiKey: String) :
+ AndroidViewModel(application) {
+ private var api = ChargepriceApi.create(chargepriceApiKey)
+
+ val vehicles: MutableLiveData>> by lazy {
+ MutableLiveData>>().apply {
+ value = Resource.loading(null)
+ loadVehicles()
+ }
+ }
+
+ private fun loadVehicles() {
+ viewModelScope.launch {
+ try {
+ val result = api.getVehicles()
+ vehicles.value = Resource.success(result)
+ } catch (e: IOException) {
+ vehicles.value = Resource.error(e.message, null)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/anim/chargeprice_dialog_enter.xml b/app/src/main/res/anim/chargeprice_dialog_enter.xml
new file mode 100644
index 00000000..1c6531f0
--- /dev/null
+++ b/app/src/main/res/anim/chargeprice_dialog_enter.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/chargeprice_dialog_exit.xml b/app/src/main/res/anim/chargeprice_dialog_exit.xml
new file mode 100644
index 00000000..a30075e0
--- /dev/null
+++ b/app/src/main/res/anim/chargeprice_dialog_exit.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/button_outline.xml b/app/src/main/res/drawable/button_outline.xml
new file mode 100644
index 00000000..bb4c2ade
--- /dev/null
+++ b/app/src/main/res/drawable/button_outline.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_chargeprice_alert.xml b/app/src/main/res/drawable/ic_chargeprice_alert.xml
new file mode 100644
index 00000000..2484c15a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chargeprice_alert.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_chargeprice_info.xml b/app/src/main/res/drawable/ic_chargeprice_info.xml
new file mode 100644
index 00000000..ddb2b894
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chargeprice_info.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_chargeprice_lock.xml b/app/src/main/res/drawable/ic_chargeprice_lock.xml
new file mode 100644
index 00000000..fb4d8fbe
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chargeprice_lock.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_chargeprice_star.xml b/app/src/main/res/drawable/ic_chargeprice_star.xml
new file mode 100644
index 00000000..c1ee35ac
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chargeprice_star.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml
new file mode 100644
index 00000000..95cc170f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_close.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_powered_by_chargeprice.xml b/app/src/main/res/drawable/ic_powered_by_chargeprice.xml
new file mode 100644
index 00000000..51606907
--- /dev/null
+++ b/app/src/main/res/drawable/ic_powered_by_chargeprice.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/rounded_rect_16dp.xml b/app/src/main/res/drawable/rounded_rect_16dp.xml
new file mode 100644
index 00000000..ac4e6385
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_rect_16dp.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_chargeprice.xml b/app/src/main/res/layout/fragment_chargeprice.xml
new file mode 100644
index 00000000..51c1846a
--- /dev/null
+++ b/app/src/main/res/layout/fragment_chargeprice.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_chargeprice.xml b/app/src/main/res/layout/item_chargeprice.xml
new file mode 100644
index 00000000..af08fbf6
--- /dev/null
+++ b/app/src/main/res/layout/item_chargeprice.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_chargeprice_tag.xml b/app/src/main/res/layout/item_chargeprice_tag.xml
new file mode 100644
index 00000000..0db4baae
--- /dev/null
+++ b/app/src/main/res/layout/item_chargeprice_tag.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_connector_button.xml b/app/src/main/res/layout/item_connector_button.xml
new file mode 100644
index 00000000..93dd0a4d
--- /dev/null
+++ b/app/src/main/res/layout/item_connector_button.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_detail.xml b/app/src/main/res/layout/item_detail.xml
index 96b84f56..f628a21b 100644
--- a/app/src/main/res/layout/item_detail.xml
+++ b/app/src/main/res/layout/item_detail.xml
@@ -18,7 +18,7 @@
app:selectableItemBackground="@{item.clickable}">
diff --git a/app/src/main/res/layout/item_detail_openinghours.xml b/app/src/main/res/layout/item_detail_openinghours.xml
index 8eca0e9a..1775aa89 100644
--- a/app/src/main/res/layout/item_detail_openinghours.xml
+++ b/app/src/main/res/layout/item_detail_openinghours.xml
@@ -24,7 +24,7 @@
app:selectableItemBackground="@{item.clickable}">
@@ -82,8 +82,8 @@
app:goneUnless="@{expandToggle.checked}"
app:hours="@{item.hoursDays}"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="@+id/txtTitle"
- app:layout_constraintTop_toBottomOf="@+id/txtContent" />
+ app:layout_constraintStart_toStartOf="@+id/txtTariff"
+ app:layout_constraintTop_toBottomOf="@+id/txtProvider" />
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
index d54c91ff..f476f09b 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -31,6 +31,13 @@
app:enterAnim="@anim/fragment_fade_enter"
app:popEnterAnim="@anim/fragment_fade_enter"
app:popExitAnim="@anim/fragment_fade_exit" />
+
@@ -76,6 +83,19 @@
android:name="net.vonforst.evmap.fragment.FilterProfilesFragment"
android:label="@string/menu_manage_filter_profiles"
tools:layout="@layout/fragment_filter_profiles" />
+
Verifiziert von der GoingElectric.de Community – nicht zwangsläufig auch aktuell verfügbar.
Neues Update: Android Auto
Mit diesem neuen Update kannst du EVMap nutzen, um Ladestationen in der Nähe auf unterstützen Autos direkt aus Android Auto zu finden. Öffne einfach die EVMap-App aus dem Menü von Android Auto.
+ %1$.2f %2$s
+ ⌀ %1$.2f %2$s/kWh
+ %1$.2f %2$s/kWh
+ Anschluss auswählen
+ Nur für Energiekunden
+ %.0f%%
+ Startgebühr
+ pro kWh
+ pro min
+ Blockiergeb. >%s
+ Keine geeigneten Tarife für diese Ladestation bei Chargeprice.app gefunden.
+ powered by Chargeprice
+ Fixkosten: %1$.2f %2$s/Monat
+ Mindestumsatz: %1$.2f %2$s/Monat
+ Preisvergleich
+ Mein Fahrzeug
+ Nur Tarife ohne monatliche Gebühren
+ Exklusive Energiekunden-Tarife anzeigen
+ Einige Anbieter bieten für ihre Kunden (z.B. Haushaltsstrom, Gas) günstigere Tarife an
+ Bitte wähle zuerst dein Auto in den Einstellungen aus.
+ Laden von %1$.0f%% bis %2$.0f%%
Falls hier nur eine leere Seite erscheint, logge dich bitte zuerst bei GoingElectric.de ein.
+ schließen
+ Preisvergleich
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 8696f9d0..400eb02e 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -15,4 +15,9 @@
#C3000000
#f44336
#039be5
+ #DD2C00
+ #2979FF
+ #546E7A
+ #00C853
+ #1F000000
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 27660294..bab5c9df 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -161,5 +161,28 @@
Charger verified by a member at the GoingElectric.de community — not necessarily working right now.
New update: Android Auto
With this new update, you can also use EVMap to find nearby chargers from within Android Auto on supported cars. Simply select the EVMap app in the Android Auto menu.
+ %2$s%1$.2f
+ ⌀ %2$s%1$.2f/kWh
+ %2$s%1$.2f/kWh
+ Choose connector
+ Only for provider customers
If only an empty page is showing here, please first log in to GoingElectric.de.
+ %.0f%%
+ session fee
+ per kWh
+ per min
+ Blocking fee >%s
+ Keine geeigneten Tarife für diese Ladestation bei Chargeprice.app gefunden.
+ powered by Chargeprice
+ Base fee: %2$s%1$.2f/month
+ Minimum spend: %2$s%1$.2f/month
+ Price comparison
+ My vehicle
+ Only show plans with no monthly fees
+ Show customer-exclusive plans
+ Please first select your car model in the settings.
+ Charge from %1$.0f%% to %2$.0f%%
+ Some providers offer cheaper plans exclusively to their customers (e.g., household electricity, gas)
+ close
+ Prices
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 7a5158c4..24e03b96 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -26,4 +26,9 @@
- false
+
+
diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml
index 83e5038f..b8f7ce24 100644
--- a/app/src/main/res/xml/settings.xml
+++ b/app/src/main/res/xml/settings.xml
@@ -2,7 +2,6 @@
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/net/vonforst/evmap/api/chargeprice/ChargepriceApiTest.kt b/app/src/test/java/net/vonforst/evmap/api/chargeprice/ChargepriceApiTest.kt
new file mode 100644
index 00000000..41b30a01
--- /dev/null
+++ b/app/src/test/java/net/vonforst/evmap/api/chargeprice/ChargepriceApiTest.kt
@@ -0,0 +1,76 @@
+package net.vonforst.evmap.api.chargeprice
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import net.vonforst.evmap.api.goingelectric.ChargeLocation
+import net.vonforst.evmap.api.goingelectric.GoingElectricApi
+import net.vonforst.evmap.okResponse
+import okhttp3.mockwebserver.Dispatcher
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import okhttp3.mockwebserver.RecordedRequest
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.net.HttpURLConnection
+
+class ChargepriceApiTest {
+ val ge: GoingElectricApi
+ val webServer = MockWebServer()
+ val chargeprice: ChargepriceApi
+
+ init {
+ webServer.start()
+
+ val apikey = ""
+ val baseurl = webServer.url("/ge/").toString()
+ ge = GoingElectricApi.create(apikey, baseurl)
+ chargeprice = ChargepriceApi.create(
+ apikey,
+ webServer.url("/cp/").toString()
+ )
+
+ webServer.dispatcher = object : Dispatcher() {
+ val notFoundResponse = MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
+
+ override fun dispatch(request: RecordedRequest): MockResponse {
+ val segments = request.requestUrl!!.pathSegments
+ val urlHead = segments.subList(0, 2).joinToString("/")
+ when (urlHead) {
+ "ge/chargepoints" -> {
+ val id = request.requestUrl!!.queryParameter("ge_id")
+ return okResponse("/chargers/$id.json")
+ }
+ "cp/charge_prices" -> {
+ val body = request.body.readUtf8()
+ return okResponse("/chargeprice/2105.json")
+ }
+ else -> return notFoundResponse
+ }
+ }
+ }
+ }
+
+ private fun readResource(s: String) =
+ ChargepriceApiTest::class.java.getResource(s)?.readText()
+
+ @ExperimentalCoroutinesApi
+ @Test
+ fun apiTest() {
+ for (chargepoint in listOf(2105L, 18284L)) {
+ val charger = runBlocking { ge.getChargepointDetail(chargepoint).body()!! }
+ .chargelocations[0] as ChargeLocation
+ println(charger)
+
+ runBlocking {
+ val result = chargeprice.getChargePrices(
+ ChargepriceRequest().apply {
+ dataAdapter = "going_electric"
+ station = ChargepriceStation.fromGoingelectric(charger)
+ options = ChargepriceOptions(energy = 22.0, duration = 60)
+ }, "en"
+ )
+ assertEquals(25, result.size)
+ }
+ }
+ }
+}
diff --git a/app/src/test/resources/chargeprice/2105.json b/app/src/test/resources/chargeprice/2105.json
new file mode 100644
index 00000000..497d84e1
--- /dev/null
+++ b/app/src/test/resources/chargeprice/2105.json
@@ -0,0 +1,1287 @@
+{
+ "data": [
+ {
+ "id": "a7a44895-b23a-47b0-9674-f08039a4af9e",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "EnBW ODR",
+ "tariff_name": "MobilityMe Privat",
+ "url": "https://www.odr.de/Mobility_Me/mobilityme.html",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 2.95,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "info",
+ "text": "New tariffs for new customers!",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 8.58,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 8.58,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "0b6d0281-0b2f-484e-8f1a-1aed19ef9a82"
+ }
+ }
+ }
+ },
+ {
+ "id": "a1aea427-53b6-4fee-9852-6b77359b9f4b",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "eins",
+ "tariff_name": "E-Mobil",
+ "url": "https://www.eins.de/privatkunden/elektromobilitaet/oeffentliches-laden/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 8.8,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 8.8,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "9a10db7b-3738-4dd6-b040-9f6bf25ac2da"
+ }
+ }
+ }
+ },
+ {
+ "id": "b19c3df3-bda9-45c6-9c69-ad92acd57381",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Enel X",
+ "tariff_name": "JuicePass",
+ "url": "https://www.enelx.com/it/en/electric-mobility/products/privates/juicepass-app",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 11.0,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 11.0,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "7221b94b-4128-470e-befd-9f96ada009ef"
+ }
+ }
+ }
+ },
+ {
+ "id": "466090c2-0d6c-4856-85c3-bb2ef4127ca5",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "EV Rodau",
+ "tariff_name": "Rodaustrom",
+ "url": "https://www.ev-rodau.de/Navigation/E-Mobilitaet/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "info",
+ "text": "Existing customers (registriert before 15.02.2021): 0.49/kWh",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 20.68,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 20.68,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "6640c45d-0704-4adb-9a7a-a1afea17b3eb"
+ }
+ }
+ }
+ },
+ {
+ "id": "e87a391f-b8f6-4581-a190-7e4c542928e9",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "MVV eMotion",
+ "tariff_name": "MVV eMotion",
+ "url": "https://mvv.chargecloud.de/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 8.58,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 8.58,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "091adf0a-ae49-4c53-bb11-2eccb409685c"
+ }
+ }
+ }
+ },
+ {
+ "id": "dfbf56b7-424a-4f1b-8749-9a4edca3eef3",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "EnBW",
+ "tariff_name": "mobility+ Viellader",
+ "url": "https://www.enbw.com/elektromobilitaet/produkte/mobilityplus-app/laden-und-bezahlen",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 4.99,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 6.38,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 6.38,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "14037a35-45d8-46ab-b348-187f7e23eba4"
+ }
+ }
+ }
+ },
+ {
+ "id": "9f6a2795-0319-4200-b871-7af040a27f14",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "EnBW",
+ "tariff_name": "ADAC e-CHARGE",
+ "url": "https://www.adac.de/rund-ums-fahrzeug/e-mobilitaet/laden/adac-e-charge/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "lock",
+ "text": "Club members only",
+ "url": "https://www.adac.de/mitgliedschaft/mitglied-werden/"
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 6.38,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 6.38,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "36c2017a-097a-4072-9a58-6ef904b6173d"
+ }
+ }
+ }
+ },
+ {
+ "id": "ba713da6-a97d-48d7-89c8-f49b529faec0",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "EnBW",
+ "tariff_name": "mobility+ Standard",
+ "url": "https://www.enbw.com/elektromobilitaet/produkte/mobilityplus-app/laden-und-bezahlen",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 8.58,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 8.58,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "dba260fc-f006-4ea8-881e-6b9f3f4e0528"
+ }
+ }
+ }
+ },
+ {
+ "id": "3bf34f4c-de37-45e9-83e9-0300cc09b604",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Lichtblick SE",
+ "tariff_name": "FahrStrom",
+ "url": "https://www.lichtblick.de/e-mobilitaet/fahrstrom-unterwegs#fahrstrom-calculator",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 10.78,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 10.78,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "21a10712-bd10-4f91-89b6-15d20890a0d0"
+ }
+ }
+ }
+ },
+ {
+ "id": "85e5162a-4306-45a7-82a9-f3a9e6d3d5fc",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Plugsurfing",
+ "tariff_name": "Plugsurfing",
+ "url": "https://www.plugsurfing.com/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 10.78,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 10.78,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "88257a81-924b-49ee-945b-304f1660343b"
+ }
+ }
+ }
+ },
+ {
+ "id": "dc3be456-3167-4d3c-9d43-ec3ec99b8086",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "evpass",
+ "tariff_name": "evpass explorer",
+ "url": "https://www.evpass.ch/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 4.441433303221921,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 24.390243902439025,
+ "price_distribution": {
+ "minute": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 24.390243902439025,
+ "price_distribution": {
+ "minute": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "ea1454d2-5944-4723-9e98-6918ffc14ac0"
+ }
+ }
+ }
+ },
+ {
+ "id": "880a67b9-df4c-4fd7-8292-c3292f06d38b",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "evpass",
+ "tariff_name": "evpass Day Flat",
+ "url": "https://www.evpass.ch/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 81.30081300813008,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 24.390243902439025,
+ "price_distribution": {
+ "minute": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 24.390243902439025,
+ "price_distribution": {
+ "minute": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "2c361bb5-6caf-48a9-b769-682c0389164c"
+ }
+ }
+ }
+ },
+ {
+ "id": "a0f464ee-b508-4ef7-962f-54ac61291435",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "evpass",
+ "tariff_name": "evpass Anytime Flat",
+ "url": "https://www.evpass.ch/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 108.40108401084011,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 24.390243902439025,
+ "price_distribution": {
+ "minute": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 24.390243902439025,
+ "price_distribution": {
+ "minute": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "02cd2e4b-0a27-4f67-a680-65be6b2ed8d7"
+ }
+ }
+ }
+ },
+ {
+ "id": "e82421e5-bb97-4d4f-bfaa-275e6183148a",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "evpass",
+ "tariff_name": "evpass Night Flat",
+ "url": "https://www.evpass.ch/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 45.16711833785005,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 24.390243902439025,
+ "price_distribution": {
+ "minute": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 24.390243902439025,
+ "price_distribution": {
+ "minute": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "a6be130c-ed42-4d09-b4b3-d5ff018ece44"
+ }
+ }
+ }
+ },
+ {
+ "id": "36d1692a-1eea-4ca9-a753-06fe0a9b74da",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Stadtwerke Ingolstadt",
+ "tariff_name": "SWI e-Motion",
+ "url": "https://sw-i.de/mobilitaet/elektromobilitaet/oeffentliches-laden/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 10.1978,
+ "price_distribution": {
+ "session": 0.2451509149032144,
+ "kwh": 0.7548490850967855
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 10.1978,
+ "price_distribution": {
+ "session": 0.2451509149032144,
+ "kwh": 0.7548490850967855
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "0efd4ffd-cc9d-4ddd-8a74-5dca40728ff4"
+ }
+ }
+ }
+ },
+ {
+ "id": "2babdda3-c2b9-4d3e-8bdc-794c10d73b6b",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "evway",
+ "tariff_name": "evway",
+ "url": "https://evway.net/de/tarif/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "info",
+ "text": "Transparent provider",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 8.8,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 8.8,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "c9fa56cc-6365-467c-b46f-eba2d13f9849"
+ }
+ }
+ }
+ },
+ {
+ "id": "5e0a3e4d-8657-481a-8483-e6cf2053ee50",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Move",
+ "tariff_name": "Move light",
+ "url": "https://www.move.ch/en/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "info",
+ "text": "New prices: No minute fee on AC!",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 14.272809394760616,
+ "price_distribution": {
+ "kwh": 0.9050632911392406,
+ "session": 0.09493670886075949
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 14.272809394760616,
+ "price_distribution": {
+ "kwh": 0.9050632911392406,
+ "session": 0.09493670886075949
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "3b564f24-182f-4cbf-8390-7957f4f9eaa5"
+ }
+ }
+ }
+ },
+ {
+ "id": "c2bc99d2-ea36-44fa-9279-977ced74a309",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Move",
+ "tariff_name": "Move comfort",
+ "url": "https://www.move.ch/en/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 4.441433303221921,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "info",
+ "text": "New prices: No minute fee on AC!",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 12.285456187895214,
+ "price_distribution": {
+ "kwh": 0.8897058823529412,
+ "session": 0.1102941176470588
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 12.285456187895214,
+ "price_distribution": {
+ "kwh": 0.8897058823529412,
+ "session": 0.1102941176470588
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "e7cfcc90-5ff7-4821-b315-ab1e9a46cd70"
+ }
+ }
+ }
+ },
+ {
+ "id": "f413f87c-2b0b-480a-93d0-4c831a367389",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "e-laden",
+ "tariff_name": "e-laden (Stadtwerke Bruchsal)",
+ "url": "https://www.e-laden.info/html/page.php?page_id=8",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 7.6,
+ "price_distribution": {
+ "session": 0.13157894736842105,
+ "kwh": 0.868421052631579,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 120,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 7.6,
+ "price_distribution": {
+ "session": 0.13157894736842105,
+ "kwh": 0.868421052631579,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 120,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "7bc071d3-a170-4755-9b71-bc87642e3a2e"
+ }
+ }
+ }
+ },
+ {
+ "id": "3bc056aa-dc28-4493-930c-f5ec00aca00b",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "e-laden",
+ "tariff_name": "e-laden (Stadtwerke Baden-Baden)",
+ "url": "https://www.e-laden.info/html/page.php?page_id=8",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "info",
+ "text": "New prices with 01.03.!",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 8.58,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 60,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 8.58,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 60,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "b9f932f5-dd09-46f5-862e-3d031963d08f"
+ }
+ }
+ }
+ },
+ {
+ "id": "8e86ed49-d143-4f6a-9340-a27c7ffc6c70",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "swisscharge",
+ "tariff_name": "Swisscharge",
+ "url": "https://www.swisscharge.ch/",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 16.25,
+ "price_distribution": {
+ "kwh": 0.6363076923076924,
+ "minute": 0.3636923076923077
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 16.25,
+ "price_distribution": {
+ "kwh": 0.6363076923076924,
+ "minute": 0.3636923076923077
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "04aab3f1-1b4b-4c6b-abb8-6e5d38f5237e"
+ }
+ }
+ }
+ },
+ {
+ "id": "8c0ba631-7c48-4d11-80bd-e2bb61e95f48",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Stadtwerke Tübingen",
+ "tariff_name": "Lade TüStrom",
+ "url": "https://www.swtue.de/e-mobilitaet/ladestationen-in-tuebingen.html",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 9.68,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 9.68,
+ "price_distribution": {
+ "kwh": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "599dce51-072a-4265-b866-e5941491329a"
+ }
+ }
+ }
+ },
+ {
+ "id": "e1b6ea8b-8b9b-4b84-9e8a-ed2d978de830",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "E.ON",
+ "tariff_name": "Drive",
+ "url": "https://www.eon.de/de/pk/e-mobility/unterwegs.html",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 4.95,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "info",
+ "text": "From 01.01.2021",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 5.95,
+ "price_distribution": {
+ "session": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 5.95,
+ "price_distribution": {
+ "session": 1.0
+ },
+ "blocking_fee_start": null,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "ae2e27d5-b74f-418c-b345-b53ac40c241f"
+ }
+ }
+ }
+ },
+ {
+ "id": "8f221fdb-af5a-4b83-a54a-a176c3f969a4",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Maingau Energie",
+ "tariff_name": "EinfachStromLaden (Business)",
+ "url": "https://www.maingau-energie.de/e-mobilit%C3%A4t/autostrom-tarif",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "alert",
+ "text": "Higher prices might apply if used often!",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 10.559999999999999,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 10.559999999999999,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "82314aed-b2bc-406f-9343-5b0ce9c9ad03"
+ }
+ }
+ },
+ "links": {
+ "open_app_at_station": "https://api.chargeprice.app/v1/companies/f7acbbc7-3247-4298-b3ed-9c61c4860a13/deeplinks?filter[longitude]=9.57108&filter[latitude]=54.5116"
+ }
+ },
+ {
+ "id": "60691e0f-1e71-4027-a431-de7543bb2464",
+ "type": "charge_price",
+ "attributes": {
+ "provider": "Maingau Energie",
+ "tariff_name": "EinfachStromLaden (Privat)",
+ "url": "https://www.maingau-energie.de/e-mobilit%C3%A4t/autostrom-tarif",
+ "monthly_min_sales": 0.0,
+ "total_monthly_fee": 0.0,
+ "flat_rate": false,
+ "direct_payment": false,
+ "provider_customer_tariff": false,
+ "currency": "EUR",
+ "start_time": 720,
+ "tags": [
+ {
+ "kind": "alert",
+ "text": "Higher prices might apply if used often!",
+ "url": null
+ }
+ ],
+ "charge_point_prices": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "price": 8.36,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "price": 8.36,
+ "price_distribution": {
+ "kwh": 1.0,
+ "minute": 0.0
+ },
+ "blocking_fee_start": 240,
+ "no_price_reason": null
+ }
+ ]
+ },
+ "relationships": {
+ "tariff": {
+ "data": {
+ "type": "tariff",
+ "id": "8fdeb6b4-2d72-4cff-87d8-081fa0ac4688"
+ }
+ }
+ },
+ "links": {
+ "open_app_at_station": "https://api.chargeprice.app/v1/companies/f7acbbc7-3247-4298-b3ed-9c61c4860a13/deeplinks?filter[longitude]=9.57108&filter[latitude]=54.5116"
+ }
+ }
+ ],
+ "meta": {
+ "charge_points": [
+ {
+ "power": 22.0,
+ "plug": "Typ2",
+ "energy": 22,
+ "duration": 60
+ },
+ {
+ "power": 2.3,
+ "plug": "Schuko",
+ "energy": 22,
+ "duration": 60
+ }
+ ]
+ }
+}
\ No newline at end of file