App 2.12.0, server 2.5.0: enter settings on the phone

The ABRP credentials are long to type on the car. "Enter on phone"
(Live data tab, and Settings next to the charging price) shows a QR
code; it opens a form on the server where the phone sends the ABRP API
key and user token, charging price and usable capacity, confirmed with
the approval password. The car polls with its own token and collects
the values once, then saves them through the same checks as the fields
on screen.

Same device-flow shape as pairing. The server keeps the values in memory
only, at most ten minutes, hands them out once with no-store, and never
logs them or puts them back into the page; a paired token only reaches
its own installation's request. Pairing's QR dialog is now shared
(PhoneQrDialog), and the phone pages share one stylesheet.

The config file also takes charging_price and usable_kwh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
2026-10-18 12:59:03 +02:00
co-authored by Claude Opus 5.5
parent 9f184362f0
commit d711e0cfda
22 changed files with 918 additions and 100 deletions
@@ -96,4 +96,14 @@ public class ConfigImportTest {
assertFalse(c.isEmpty());
assertEquals("token-2", ConfigImport.parse("abrp_user_token = token-2").abrpToken);
}
@Test
public void chargingPriceAndUsableCapacityAreRead() {
ConfigImport c = ConfigImport.parse("charging_price = 0,39\nusable_kwh = 50.8\n");
assertEquals(0.39, c.chargingPrice, 1e-9);
assertEquals(50.8, c.usableKwh, 1e-9);
assertFalse(c.isEmpty());
assertNull(ConfigImport.parse("charging_price = cheap").chargingPrice);
assertEquals(0.5, ConfigImport.parse("price_per_kwh = 0.5").chargingPrice, 1e-9);
}
}
@@ -0,0 +1,29 @@
package com.evsuite.abrp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
/** Reading what the phone sent, as the server hands it out — pure JVM. */
public class PhoneSettingsClientTest {
@Test
public void everythingTheServerSends() throws Exception {
PhoneSettingsClient.Settings s = PhoneSettingsClient.Settings.parse(
"{\"abrpApiKey\":\"api-key-1234\",\"abrpToken\":\" user-token-5678 \",\"pricePerKwh\":0.39,\"usableKwh\":50.8}");
assertEquals("api-key-1234", s.abrpApiKey);
assertEquals("user-token-5678", s.abrpToken);
assertEquals(0.39, s.pricePerKwh, 1e-9);
assertEquals(50.8, s.usableKwh, 1e-9);
}
@Test
public void whatThePhoneLeftEmptyStaysUnset() throws Exception {
PhoneSettingsClient.Settings s = PhoneSettingsClient.Settings.parse("{\"abrpToken\":\"user-token-5678\"}");
assertNull(s.abrpApiKey);
assertNull(s.pricePerKwh);
assertNull(s.usableKwh);
assertEquals("user-token-5678", s.abrpToken);
}
}