Checklist v4 from contracts: step 31, each wheel by hand
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "z27ag-captures",
|
"id": "z27ag-captures",
|
||||||
"version": 3,
|
"version": 4,
|
||||||
"title": "Z27AG decoding captures",
|
"title": "Z27AG decoding captures",
|
||||||
"about": "docs/impl/plans/Z27AG-CAPTURES.md",
|
"about": "docs/impl/plans/Z27AG-CAPTURES.md",
|
||||||
"mark_format": "<step> <action>[ #<n>] start|end for a span, <step> <action>[ #<n>] for a point",
|
"mark_format": "<step> <action>[ #<n>] start|end for a span, <step> <action>[ #<n>] for a point",
|
||||||
@@ -445,6 +445,30 @@
|
|||||||
"hold_s": 10
|
"hold_s": 10
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "31",
|
||||||
|
"title": "One wheel at a time, by hand",
|
||||||
|
"do": "Ignition on, engine off, neutral. A front: hold the other front still (open differential: in neutral the turned wheel spins the gearbox, not its partner) and spin this one briskly by hand, forward, about one turn a second; start as it turns, end as you stop. A rear, if you raise it: rears turn freely, no holding needed. Fronts are enough if the rears are not up.",
|
||||||
|
"finds": "which word of 235 is which wheel, one at a time (the drives already point to bytes 0-1 front right, 2-3 front left, 4-5 rear right, 6-7 rear left); also the slowest speed the ABS reports",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"label": "front left by hand",
|
||||||
|
"hold_s": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "front right by hand",
|
||||||
|
"hold_s": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "rear left by hand",
|
||||||
|
"hold_s": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "rear right by hand",
|
||||||
|
"hold_s": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'link/boundary.dart';
|
import 'link/boundary.dart';
|
||||||
import 'link/net.dart';
|
import 'link/net.dart';
|
||||||
|
import 'link/reading.dart';
|
||||||
import 'pages/link_test.dart';
|
import 'pages/link_test.dart';
|
||||||
import 'shell.dart';
|
import 'shell.dart';
|
||||||
import 'shell_scope.dart';
|
import 'shell_scope.dart';
|
||||||
@@ -24,6 +25,14 @@ Future<void> main() async {
|
|||||||
final usb = UsbLink();
|
final usb = UsbLink();
|
||||||
await usb.bindFor(Uri.parse(defaultUrl));
|
await usb.bindFor(Uri.parse(defaultUrl));
|
||||||
final boundary = Boundary(defaultUrl)..start();
|
final boundary = Boundary(defaultUrl)..start();
|
||||||
|
// The sun, for auto dark/bright: the car's position while GNSS has one.
|
||||||
|
boundary.addListener(() {
|
||||||
|
final lat = Reading.of(boundary, 'gnss_lat');
|
||||||
|
final lon = Reading.of(boundary, 'gnss_lon');
|
||||||
|
if (!lat.dim && !lon.dim && lat.number != null && lon.number != null) {
|
||||||
|
themes.setPosition(lat.number!, lon.number!);
|
||||||
|
}
|
||||||
|
});
|
||||||
final linkTest = LinkTest(usb);
|
final linkTest = LinkTest(usb);
|
||||||
// Development hook: `am start ... --es linktest [dl:|bi:]<label>[@<host>]` runs the test
|
// Development hook: `am start ... --es linktest [dl:|bi:]<label>[@<host>]` runs the test
|
||||||
// (`dl:` the download series, `bi:` burst latency against idle gaps), on the gateway's host unless one is given.
|
// (`dl:` the download series, `bi:` burst latency against idle gaps), on the gateway's host unless one is given.
|
||||||
|
|||||||
+15
-8
@@ -9,6 +9,7 @@ import '../link/net.dart';
|
|||||||
import '../shell_scope.dart';
|
import '../shell_scope.dart';
|
||||||
import '../theme/light.dart';
|
import '../theme/light.dart';
|
||||||
import '../theme/palette.dart';
|
import '../theme/palette.dart';
|
||||||
|
import '../theme/sun.dart';
|
||||||
|
|
||||||
class SettingsPage extends StatelessWidget {
|
class SettingsPage extends StatelessWidget {
|
||||||
const SettingsPage({
|
const SettingsPage({
|
||||||
@@ -58,14 +59,20 @@ class SettingsPage extends StatelessWidget {
|
|||||||
title: const Text('Dark or bright'),
|
title: const Text('Dark or bright'),
|
||||||
subtitle: ValueListenableBuilder(
|
subtitle: ValueListenableBuilder(
|
||||||
valueListenable: themes.lux,
|
valueListenable: themes.lux,
|
||||||
builder: (context, lux, _) => Text(
|
builder: (context, lux, _) {
|
||||||
lux == null
|
final sun = themes.sunElevationNow;
|
||||||
? "Auto follows Android's dark theme and its schedule (no light sensor). "
|
final now = lux != null
|
||||||
'The status bar button holds one; a long press there is auto again.'
|
? 'now ${lux.toStringAsFixed(lux < 10 ? 1 : 0)} lux; dark at '
|
||||||
: 'Auto follows the light sensor: dark at ${LightSwitch.darkAt.round()} lux or less, '
|
'${LightSwitch.darkAt.round()} or less, bright from ${LightSwitch.brightAt.round()}'
|
||||||
'bright from ${LightSwitch.brightAt.round()}; now ${lux.toStringAsFixed(lux < 10 ? 1 : 0)} lux. '
|
: sun != null
|
||||||
'The status bar button holds one; a long press there is auto again.',
|
? 'now ${sun.round()}° above the horizon at the last GNSS position; '
|
||||||
),
|
'dark below ${darkBelowDeg.round()}°'
|
||||||
|
: 'no GNSS position yet';
|
||||||
|
return Text(
|
||||||
|
'Auto follows ${themes.autoSource} ($now). '
|
||||||
|
'The status bar button holds one; a long press there is auto again.',
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
+69
-6
@@ -24,6 +24,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'light.dart';
|
import 'light.dart';
|
||||||
|
import 'sun.dart';
|
||||||
|
|
||||||
/// The HUD face (smart-serow's DIN 1451). Opt-in: a widget names it in its own
|
/// The HUD face (smart-serow's DIN 1451). Opt-in: a widget names it in its own
|
||||||
/// TextStyle; everything else keeps the basic font, which covers CJK where DIN does not.
|
/// TextStyle; everything else keeps the basic font, which covers CJK where DIN does not.
|
||||||
@@ -120,21 +121,36 @@ class Palette {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which variant shows: auto (the light sensor, else Android's dark theme), or one held fixed.
|
/// Which variant shows: auto (a light sensor, else the sun, else Android's dark theme),
|
||||||
|
/// or one held fixed.
|
||||||
enum VariantMode { auto, dark, bright }
|
enum VariantMode { auto, dark, bright }
|
||||||
|
|
||||||
/// A theme: both variants, and which one is showing. Listen to it to follow a switch.
|
/// A theme: both variants, and which one is showing. Listen to it to follow a switch.
|
||||||
/// In auto it follows the tablet's light sensor ([followLight]); without one, Android's
|
/// In auto it follows a light sensor ([followLight]) where the device lets an app read
|
||||||
/// dark theme (by hand or on its schedule). The mode is kept across restarts.
|
/// one; else the sun at the car's last position ([setPosition]); else Android's dark
|
||||||
|
/// theme (by hand or on its schedule). The mode and the position are kept across restarts.
|
||||||
class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
||||||
Themes(this.name, this.dark, this.bright, [this._prefs]) {
|
Themes(
|
||||||
|
this.name,
|
||||||
|
this.dark,
|
||||||
|
this.bright, [
|
||||||
|
this._prefs,
|
||||||
|
this._clock = DateTime.now,
|
||||||
|
]) {
|
||||||
_mode =
|
_mode =
|
||||||
VariantMode.values.asNameMap()[_prefs?.getString(_key)] ??
|
VariantMode.values.asNameMap()[_prefs?.getString(_key)] ??
|
||||||
VariantMode.auto;
|
VariantMode.auto;
|
||||||
|
final (lat, lon) = (_prefs?.getDouble(_latKey), _prefs?.getDouble(_lonKey));
|
||||||
|
if (lat != null && lon != null) _position = (lat, lon);
|
||||||
|
_sunDark = _sunNow();
|
||||||
|
_sunTimer = Timer.periodic(const Duration(minutes: 1), (_) => _sunCheck());
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const _key = 'variant_mode';
|
static const _key = 'variant_mode';
|
||||||
|
static const _latKey = 'sun_lat';
|
||||||
|
static const _lonKey = 'sun_lon';
|
||||||
|
final DateTime Function() _clock;
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final Palette dark;
|
final Palette dark;
|
||||||
@@ -157,7 +173,7 @@ class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
|||||||
/// The last light level, in lux, while a sensor reports (for Settings, to tune by).
|
/// The last light level, in lux, while a sensor reports (for Settings, to tune by).
|
||||||
final lux = ValueNotifier<double?>(null);
|
final lux = ValueNotifier<double?>(null);
|
||||||
|
|
||||||
/// Follow a light sensor in auto. Errors (no sensor) leave auto on Android's dark theme.
|
/// Follow a light sensor in auto. Errors (no sensor, or refused) leave auto to the sun.
|
||||||
void followLight(Stream<double> levels) {
|
void followLight(Stream<double> levels) {
|
||||||
void changed(bool? before) {
|
void changed(bool? before) {
|
||||||
if (_light.isDark != before && _mode == VariantMode.auto) {
|
if (_light.isDark != before && _mode == VariantMode.auto) {
|
||||||
@@ -190,9 +206,51 @@ class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(double, double)? _position;
|
||||||
|
bool? _sunDark;
|
||||||
|
late final Timer _sunTimer;
|
||||||
|
|
||||||
|
/// The car's position, for the sun. Kept (to 0.1°, about 10 km: plenty for the sun)
|
||||||
|
/// so auto knows the sun before the first fix.
|
||||||
|
void setPosition(double lat, double lon) {
|
||||||
|
double r(double x) => (x * 10).round() / 10;
|
||||||
|
final p = (r(lat), r(lon));
|
||||||
|
if (p == _position) return;
|
||||||
|
_position = p;
|
||||||
|
_prefs?.setDouble(_latKey, p.$1);
|
||||||
|
_prefs?.setDouble(_lonKey, p.$2);
|
||||||
|
_sunCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sun's elevation now, in degrees, or null with no position yet.
|
||||||
|
double? get sunElevationNow {
|
||||||
|
final p = _position;
|
||||||
|
return p == null ? null : sunElevation(p.$1, p.$2, _clock());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? _sunNow() {
|
||||||
|
final e = sunElevationNow;
|
||||||
|
return e == null ? null : e < darkBelowDeg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sunCheck() {
|
||||||
|
final now = _sunNow();
|
||||||
|
if (now == _sunDark) return;
|
||||||
|
_sunDark = now;
|
||||||
|
if (_mode == VariantMode.auto && _light.isDark == null) notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What auto follows now, in words, for Settings.
|
||||||
|
String get autoSource => _light.isDark != null
|
||||||
|
? 'the light sensor'
|
||||||
|
: _sunDark != null
|
||||||
|
? 'the sun'
|
||||||
|
: "Android's dark theme";
|
||||||
|
|
||||||
bool get isDark => switch (_mode) {
|
bool get isDark => switch (_mode) {
|
||||||
VariantMode.auto =>
|
VariantMode.auto =>
|
||||||
_light.isDark ??
|
_light.isDark ??
|
||||||
|
_sunDark ??
|
||||||
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||||
Brightness.dark,
|
Brightness.dark,
|
||||||
VariantMode.dark => true,
|
VariantMode.dark => true,
|
||||||
@@ -206,11 +264,16 @@ class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangePlatformBrightness() {
|
void didChangePlatformBrightness() {
|
||||||
if (_mode == VariantMode.auto && _light.isDark == null) notifyListeners();
|
if (_mode == VariantMode.auto &&
|
||||||
|
_light.isDark == null &&
|
||||||
|
_sunDark == null) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_sunTimer.cancel();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_holdTimer?.cancel();
|
_holdTimer?.cancel();
|
||||||
lux.dispose();
|
lux.dispose();
|
||||||
|
|||||||
Reference in New Issue
Block a user