Skip to content
Shiny.NET
Shiny MAUI Shell v7 - App Links, App Shortcuts, & Navigation Interception!Shortcut me to it

OBD Releases

Fix
BleObdDeviceScanner hid adapters that advertise no name. The scan required a resolvable name before surfacing a device, and that is in practice a first-connection-of-the-process filter on iOS: the name is Peripheral.Name ?? AdvertisementData.LocalName, and CBPeripheral.Name stays null until CoreBluetooth has connected to that peripheral once and cached it — while plenty of ELM327 clones carry no local name in the advertisement at all. An adapter advertising from the OBD port was therefore invisible on exactly the attempt that mattered and became findable only once a connection had already succeeded by some other route, which reads as “pairing works, the first reconnect after a cold start fails, every reconnect after that is fine”. Unnamed adapters are now surfaced with Name as an empty string; identify them by Id, the BLE peripheral UUID, which is always present. Setting DeviceNameFilter still excludes them — a filter cannot match a name that isn’t there — so asking for a name remains available to callers that want it.
Fix
BLE adapters no longer connect on Android’s slow path. BleObdTransport passed no ConnectionConfig, so it inherited Shiny.BluetoothLE’s AutoConnect: true default — ConnectGatt(autoConnect: true), the background connection path, where the controller only attempts during widely spaced scan windows. An in-range adapter that a direct connect reaches in a few hundred milliseconds was taking tens of seconds. It also armed the platform’s own reconnect, which raced any caller supervising the session itself: each side’s teardown cancelled the other’s attempt in flight. The transport now passes an explicit config, and the new BleObdConfiguration.AutoConnect (default false) is how you ask for the old behaviour. ConnectTimeout is configurable alongside it.
Fix
Connecting reset the adapter twice. ObdConnection.Connect sent ATZ and waited a second before probing with ATI, and the profile it then selected opened with ATZ and another second — two full adapter resets per connection, two seconds of hardcoded delay, and the second reset discarding everything the first sequence had established. Auto-detection now probes with ATI alone; ATZ is sent once, by the profile.
Feature
Protocol pinning — ObdConnection.Protocol and RefreshNegotiatedProtocol(). ATSP0 does not choose a protocol, it defers the choice to the first command that needs the bus, and that command pays the whole ELM search — seconds of it, routinely longer than a command timeout, and ATZ discards the result so an adapter pays it again on every reconnect. Read the number a session settled on with RefreshNegotiatedProtocol(), persist it, and set Protocol on the next connection to skip the search entirely. A stale pin is safe: the protocol is verified with mode 01 during initialization and dropped for a search when nothing answers. Elm327AdapterProfile and ObdLinkAdapterProfile take the same number as a constructor argument.
Fix
ObdLinkAdapterProfile wiped its own configuration. STFAC restores factory defaults and was being sent after the ELM327 initialization, so every OBDLink session ran with echo on, no spacing, headers unset and an unpinned protocol — configured and un-configured in the same breath. It now runs before the base sequence.
Fix
The BLE transport assumed write-without-response. Commands were written with withResponse: false unconditionally. A clone whose TX characteristic is write-with-response only silently drops such a write: the command never reaches the adapter, nothing answers, and the caller sits out the full CommandTimeout for a reply that was never coming — a run of which is indistinguishable from a dead adapter. The transport now reads the characteristic’s advertised properties on connect and writes accordingly.
Feature
Shiny.Obd.Emulator — be an adapter instead of reading one. The OBD-II adapter emulator that shipped inside the sample app is now a package. It answers the ELM327 dialect over a real TCP socket, so the AT initialisation handshake, supported-PID discovery, multi-frame VIN reads, freeze frames and trouble codes all happen for real rather than being mocked — which also makes it useful for testing apps that have nothing to do with .NET. services.AddObdEmulator() registers the vehicle, the responder, the TCP front-end and the driving scenarios; ObdEmulatorHost.Start() begins listening. Every PID the library has a command for is settable in engineering units or as raw bytes, individually supported or not (an unsupported PID answers NO DATA and leaves the supported-PID bitmask), alongside trouble codes for modes 03/07/0A, MIL state, monitor readiness, freeze frames and the ATI string.
Feature
Shiny.Obd.Emulator.Ble — the same emulator as a Bluetooth LE peripheral. services.AddObdEmulatorBluetoothLE() adds a GATT front-end advertising the FFF0/FFF1/FFF2 triple as VEEPEAK — the BleObdConfiguration defaults, so a Shiny.Obd client finds it with no configuration on either side. Transports are additive and share one vehicle, so a value you set is answered identically over BLE and TCP.
Feature
Eight emulated vehicles whose VINs decode for real. VehicleCatalog ships a Civic, a Camry, a 330i, a Prius, a Golf TDI, a Cummins Ram 2500, a Leaf and a 1998 Cavalier — each with a real WMI and descriptor, a valid ISO 3779 check digit, and a decode verified against NHTSA vPIC, so IVinDecoder answers with a real make, model and year instead of null. Setting ObdEmulatorState.Vehicle rewrites the whole car: identity PIDs, which PIDs exist at all (the diesels drop the narrowband O2 sensors and the evaporative system, the Leaf drops a third of mode 01, the Cavalier answers no mode 09 and so has no VIN to read over the bus), the readiness monitor set, the seeded fault memory, and the mass, gearing and fuel chemistry a driving scenario is played through.
Feature
Driving scenarios. DrivingScenarioPlayer writes the mode 01 parameters five times a second from the selected vehicle’s own physical model, so a client polling for hours sees a vehicle being driven rather than fixed values — RPM follows the gear the speed implies, mass air flow follows the load and the engine’s displacement, fuel rate follows the air flow at that fuel’s density, and the odometer, fuel level and trip counters integrate across laps. DrivingScenarioCatalog ships warm idle, city driving, busy highway and a mixed commute; gear changes with load-dependent shift points, deceleration fuel cut and harsh braking are all modelled.
Feature
VIN check digit. VinNumber.CalculateCheckDigit(vin) returns the ISO 3779 position-9 character ('0''9' or 'X', or null when the input is implausible) and VinNumber.IsCheckDigitValid(vin) compares it to what is there. Position 9 weighs zero in the formula, so a VIN carrying the wrong check digit still computes the right one — which is what makes this usable for generating VINs a decoder will accept, not just for checking them. Deliberately kept out of IsPlausible: the check digit is only mandatory in North America, so plenty of legitimate European and Asian VINs fail it and it must never be used to reject a VIN read off a vehicle.

Initial Release