diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ea155cb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +# Repository Guidelines + +## Project Structure & Module Organization +The Flipper Zero TPMS app is written in C and keeps its entry point in `tpms_app.c`, with the public interface in `tpms_app_i.h`. Interaction flows are assembled in `scenes/`, rendering logic in `views/`, and protocol decoders in `protocols/`. Shared helpers for radio presets and event data sit in `helpers/`, while deployable assets live under `images/`. `application.fam` defines the build metadata (appid `tpms`, category Sub-GHz); keep it in sync whenever you expose new scenes or resources. + +## Build, Test, and Development Commands +- `./fbt fap_tpms` (run from a cloned `flipperzero-firmware` tree with this repo under `applications_user/tpms`) builds the FAP and drops it into `build/f7-firmware-D/apps`. +- `./fbt launch_app APPSRC=flipperzero-tpms` launches the debug build on a connected Flipper or emulator for quick iteration. +- `./fbt fap_dist APPSRC=flipperzero-tpms` creates a distributable `tpms.fap` archive in `dist/fap` for release builds. + +## Coding Style & Naming Conventions +- Follow the established 4-space indentation, K&R braces, and `snake_case` function/file names prefixed with `tpms_`; reserve `PascalCase` for structs and enums such as `TPMSApp`. +- Group system/Furi includes before project headers, keep them alphabetized, and prefer `static` helpers when the symbol is local to a file. +- Document non-obvious radio timing or preset choices with a short comment; favor existing logging patterns over new macros. + +## Testing Guidelines +There is no automated suite yet, so rely on hardware verification. After flashing via `./fbt fap_tpms` and loading the package with qFlipper, confirm menu navigation, sensor discovery, and relearn timing against supported sensors. Record manual smoke results in the pull request, including firmware hash and sensor IDs. When adding protocol decoders, attach concise RF logs or test notes that let reviewers understand the scenario you validated. + +For receiver validation, do not stop at the first successful decode. Confirm repeated reception of an already-known sensor ID and verify that `Auto` behaves correctly on hardware without an external CC1101 attached. + +## Commit & Pull Request Guidelines +Recent history favors concise, imperative subjects (e.g. `Update tpms for latest firmware version`); keep them ≤72 characters and scope each commit to one logical change. For pull requests, provide a short summary, testing notes, related issues/discussions, and screenshots or GIFs when UI states change. Request maintainer review prior to merge and ensure you build against the same `flipperzero-firmware` revision referenced in the PR description. + +## Configuration Tips +Add new sensors by extending `protocols/` and documenting any radio preset changes directly in the decoder code so testers can replicate results. Re-run `./fbt fap_tpms` after asset changes to verify the `tpms_10px.png` icon and other resources remain within Flipper size/format limits. + +## Path Hygiene +Prefer repo-relative paths in docs, scripts, comments, and answers unless an absolute path is required for a device-specific or host-specific operation. Avoid centering instructions around local workspace paths when a repository-relative path communicates the same thing. + +Do not leave `picocom`, `minicom`, or similar serial sessions open after a check. Close them before launches, uploads, or additional device verification so the USB CDC path does not get wedged by your own session. diff --git a/Readme.md b/Readme.md index dc1e4f7..9083749 100644 --- a/Readme.md +++ b/Readme.md @@ -3,10 +3,14 @@ ## Features - Read [TPMS](https://en.wikipedia.org/wiki/Tire-pressure_monitoring_system) sensors +- Decode Toyota PMV-C210/C215 sensors +- Filter received frames by minimum RSSI +- Select radio path explicitly: `Auto`, `Internal`, or `External` - Relearn by 125kHz signal #### Supported sensors * Schrader GG4 +* Toyota PMV-C210 / PMV-C215 * Abarth 124 (soon) ## How to use @@ -19,6 +23,11 @@ If sensor is supported and correct frequency and modulation was set, an item wit Pressing OK displays temperature and pressure. +Receiver configuration is available from the main receive screen: +- `Modulation` now includes the Toyota `PMV-C2x` preset +- `Min RSSI` can be used to ignore weak frames +- `Radio` can force `Internal` or `External` hardware instead of relying on `Auto` + ![input](tpms.gif) Feel free to contribute via PR or report issue diff --git a/application.fam b/application.fam index eab5eca..f8281cb 100644 --- a/application.fam +++ b/application.fam @@ -5,11 +5,12 @@ App( targets=["f7"], entry_point="tpms_app", requires=["gui"], + sources=["*.c", "!tools"], stack_size=4 * 1024, order=50, fap_icon="tpms_10px.png", fap_category="Sub-GHz", fap_icon_assets="images", - fap_version="0.1", + fap_version="0.3", fap_description="Receive data from vehicle Tyre Pressure sensors (TPMS)", ) diff --git a/helpers/radio_device_loader.c b/helpers/radio_device_loader.c index 0d99549..1a799a9 100644 --- a/helpers/radio_device_loader.c +++ b/helpers/radio_device_loader.c @@ -16,6 +16,18 @@ static void radio_device_loader_power_off() { if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); } +static bool radio_device_loader_begin_external(const SubGhzDevice* radio_device) { + furi_assert(radio_device); + + radio_device_loader_power_on(); + if(subghz_devices_begin(radio_device)) { + return true; + } + + radio_device_loader_power_off(); + return false; +} + bool radio_device_loader_is_connect_external(const char* name) { bool is_connect = false; bool is_otg_enabled = furi_hal_power_is_otg_enabled(); @@ -38,21 +50,39 @@ bool radio_device_loader_is_connect_external(const char* name) { const SubGhzDevice* radio_device_loader_set( const SubGhzDevice* current_radio_device, SubGhzRadioDeviceType radio_device_type) { - const SubGhzDevice* radio_device; + const SubGhzDevice* internal_radio_device = + subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + const SubGhzDevice* external_radio_device = + subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); - if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 && - radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) { - radio_device_loader_power_on(); - radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); - subghz_devices_begin(radio_device); - } else if(current_radio_device == NULL) { - radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); - } else { - radio_device_loader_end(current_radio_device); - radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + bool prefer_external = (radio_device_type == SubGhzRadioDeviceTypeExternalCC1101); + if(prefer_external) { + prefer_external = radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME); } - return radio_device; + const SubGhzDevice* target_radio_device = internal_radio_device; + bool use_external = false; + + if(prefer_external && external_radio_device) { + if(current_radio_device && current_radio_device != internal_radio_device) { + subghz_devices_end(current_radio_device); + current_radio_device = NULL; + } + + use_external = radio_device_loader_begin_external(external_radio_device); + if(use_external) { + target_radio_device = external_radio_device; + } + } + + if(!use_external) { + if(current_radio_device && current_radio_device != internal_radio_device) { + subghz_devices_end(current_radio_device); + } + radio_device_loader_power_off(); + } + + return target_radio_device; } bool radio_device_loader_is_external(const SubGhzDevice* radio_device) { @@ -66,4 +96,4 @@ void radio_device_loader_end(const SubGhzDevice* radio_device) { if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) { subghz_devices_end(radio_device); } -} \ No newline at end of file +} diff --git a/helpers/radio_device_loader.h b/helpers/radio_device_loader.h index bae4bac..b7e7e3c 100644 --- a/helpers/radio_device_loader.h +++ b/helpers/radio_device_loader.h @@ -4,6 +4,7 @@ /** SubGhzRadioDeviceType */ typedef enum { + SubGhzRadioDeviceTypeAuto, SubGhzRadioDeviceTypeInternal, SubGhzRadioDeviceTypeExternalCC1101, } SubGhzRadioDeviceType; @@ -14,4 +15,4 @@ const SubGhzDevice* radio_device_loader_set( bool radio_device_loader_is_external(const SubGhzDevice* radio_device); -void radio_device_loader_end(const SubGhzDevice* radio_device); \ No newline at end of file +void radio_device_loader_end(const SubGhzDevice* radio_device); diff --git a/helpers/tpms_preset.c b/helpers/tpms_preset.c new file mode 100644 index 0000000..a286d2e --- /dev/null +++ b/helpers/tpms_preset.c @@ -0,0 +1,118 @@ +#include "tpms_preset.h" + +#include + +static uint8_t tpms_preset_toyota_pmvc2x_data[] = { + 0x02, 0x0D, 0x0B, 0x06, 0x08, 0x32, 0x07, 0x04, 0x14, 0x00, 0x13, 0x02, + 0x12, 0x04, 0x11, 0x93, 0x10, 0x59, 0x15, 0x41, 0x18, 0x18, 0x19, 0x16, + 0x1D, 0x91, 0x1C, 0x00, 0x1B, 0x07, 0x20, 0xFB, 0x22, 0x10, 0x21, 0x56, + 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const TPMSPresetDefinition tpms_preset_definitions[] = { + { + .name = TPMS_PRESET_TOYOTA_PMVC2X_NAME, + .short_name = "FM", + .radio_preset = FuriHalSubGhzPresetCustom, + .custom_data = tpms_preset_toyota_pmvc2x_data, + .custom_data_size = sizeof(tpms_preset_toyota_pmvc2x_data), + }, + { + .name = "AM270", + .short_name = "AM", + .radio_preset = FuriHalSubGhzPresetOok270Async, + }, + { + .name = "AM650", + .short_name = "AM", + .radio_preset = FuriHalSubGhzPresetOok650Async, + }, + { + .name = "FM238", + .short_name = "FM", + .radio_preset = FuriHalSubGhzPreset2FSKDev238Async, + }, + { + .name = "FM12K", + .short_name = "FM", + .radio_preset = FuriHalSubGhzPreset2FSKDev12KAsync, + }, + { + .name = "FM476", + .short_name = "FM", + .radio_preset = FuriHalSubGhzPreset2FSKDev476Async, + }, +}; + +size_t tpms_preset_count(void) { + return COUNT_OF(tpms_preset_definitions); +} + +const TPMSPresetDefinition* tpms_preset_get_by_index(size_t index) { + if(index >= tpms_preset_count()) { + return NULL; + } + + return &tpms_preset_definitions[index]; +} + +const TPMSPresetDefinition* tpms_preset_get_by_name(const char* name) { + if(name == NULL) { + return NULL; + } + + for(size_t index = 0; index < tpms_preset_count(); index++) { + const TPMSPresetDefinition* definition = &tpms_preset_definitions[index]; + if(strcmp(definition->name, name) == 0) { + return definition; + } + } + + return NULL; +} + +const TPMSPresetDefinition* tpms_preset_get_default(void) { + return &tpms_preset_definitions[0]; +} + +const char* tpms_preset_get_short_name(const char* name) { + const TPMSPresetDefinition* definition = tpms_preset_get_by_name(name); + return definition ? definition->short_name : "CU"; +} + +FuriHalSubGhzPreset tpms_preset_get_radio_preset(const char* name) { + const TPMSPresetDefinition* definition = tpms_preset_get_by_name(name); + return definition ? definition->radio_preset : FuriHalSubGhzPresetCustom; +} + +uint32_t tpms_preset_get_default_frequency(void) { + return TPMS_PRESET_TOYOTA_PMVC2X_FREQUENCY; +} + +bool tpms_preset_init_radio( + SubGhzRadioPreset* preset, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size) { + furi_assert(preset); + furi_assert(preset_name); + + const TPMSPresetDefinition* definition = tpms_preset_get_by_name(preset_name); + + furi_string_set(preset->name, definition ? definition->name : preset_name); + preset->frequency = frequency; + + if(definition && definition->radio_preset == FuriHalSubGhzPresetCustom) { + preset->data = (uint8_t*)definition->custom_data; + preset->data_size = definition->custom_data_size; + } else if(definition) { + preset->data = NULL; + preset->data_size = 0; + } else { + preset->data = preset_data; + preset->data_size = preset_data_size; + } + + return true; +} diff --git a/helpers/tpms_preset.h b/helpers/tpms_preset.h new file mode 100644 index 0000000..8b279a4 --- /dev/null +++ b/helpers/tpms_preset.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +typedef struct { + const char* name; + const char* short_name; + FuriHalSubGhzPreset radio_preset; + const uint8_t* custom_data; + size_t custom_data_size; +} TPMSPresetDefinition; + +#define TPMS_PRESET_TOYOTA_PMVC2X_NAME "Toyota PMV-C2x" +#define TPMS_PRESET_TOYOTA_PMVC2X_FREQUENCY 433920000U + +size_t tpms_preset_count(void); +const TPMSPresetDefinition* tpms_preset_get_by_index(size_t index); +const TPMSPresetDefinition* tpms_preset_get_by_name(const char* name); +const TPMSPresetDefinition* tpms_preset_get_default(void); +const char* tpms_preset_get_short_name(const char* name); +FuriHalSubGhzPreset tpms_preset_get_radio_preset(const char* name); +uint32_t tpms_preset_get_default_frequency(void); +bool tpms_preset_init_radio( + SubGhzRadioPreset* preset, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); diff --git a/helpers/tpms_types.h b/helpers/tpms_types.h index 23fb663..ed9e63a 100644 --- a/helpers/tpms_types.h +++ b/helpers/tpms_types.h @@ -2,8 +2,9 @@ #include #include +#include -#define TPMS_VERSION_APP "0.1" +#define TPMS_VERSION_APP "0.3" #define TPMS_DEVELOPED "wosk" #define TPMS_GITHUB "https://github.com/beewosk/flipperzero-tpms" @@ -55,4 +56,6 @@ typedef enum { typedef enum { TPMSRelearnTypeCommon, //TPMSRelearnAnotherOEM, -} TPMSRelearnType; \ No newline at end of file +} TPMSRelearnType; + +#define TPMS_RSSI_FILTER_OFF INT16_MIN diff --git a/protocols/protocol_items.c b/protocols/protocol_items.c index ad3340b..752a636 100644 --- a/protocols/protocol_items.c +++ b/protocols/protocol_items.c @@ -2,6 +2,7 @@ const SubGhzProtocol* tpms_protocol_registry_items[] = { &tpms_protocol_schrader_gg4, + &tpms_protocol_toyota_pmvc2x, }; const SubGhzProtocolRegistry tpms_protocol_registry = { diff --git a/protocols/protocol_items.h b/protocols/protocol_items.h index 2c87f29..36d4209 100644 --- a/protocols/protocol_items.h +++ b/protocols/protocol_items.h @@ -2,5 +2,6 @@ #include "../tpms_app_i.h" #include "schrader_gg4.h" +#include "toyota_pmvc2x.h" extern const SubGhzProtocolRegistry tpms_protocol_registry; diff --git a/protocols/toyota_pmvc2x.c b/protocols/toyota_pmvc2x.c new file mode 100644 index 0000000..2f8ce64 --- /dev/null +++ b/protocols/toyota_pmvc2x.c @@ -0,0 +1,172 @@ +#include "toyota_pmvc2x.h" + +#include +#include + +#define TAG "Toyota" + +#define TPMS_TOYOTA_PSI_TO_BAR 0.0689476f + +static const SubGhzBlockConst tpms_protocol_toyota_pmvc2x_const = { + .te_short = 48U, + .te_long = 96U, + .te_delta = 18U, + .min_count_bit_for_found = 64U, +}; + +struct TPMSProtocolDecoderToyotaPMVC2X { + SubGhzProtocolDecoderBase base; + SubGhzBlockDecoder decoder; + TPMSBlockGeneric generic; + ToyotaPmvc2xCore core; + uint8_t last_status; +}; + +struct TPMSProtocolEncoderToyotaPMVC2X { + SubGhzProtocolEncoderBase base; + SubGhzProtocolBlockEncoder encoder; + TPMSBlockGeneric generic; +}; + +static void tpms_protocol_toyota_pmvc2x_apply_frame( + TPMSProtocolDecoderToyotaPMVC2X* instance, + const ToyotaPmvc2xFrame* frame) { + float pressure_psi = ((float)frame->pressure_raw * 0.25f) - 7.0f; + float pressure_bar = pressure_psi * TPMS_TOYOTA_PSI_TO_BAR; + float temperature_c = (float)frame->temperature_raw - 40.0f; + + uint64_t data = 0; + for(size_t index = 0; index < 8U; index++) { + data = (data << 8U) | frame->payload[index]; + } + + instance->decoder.decode_data = data; + instance->decoder.decode_count_bit = 64U; + + instance->generic.data = data; + instance->generic.data_count_bit = 64U; + instance->generic.id = frame->id; + instance->generic.battery_low = TPMS_NO_BATT; + instance->generic.temperature = temperature_c; + instance->generic.pressure = pressure_bar; + instance->last_status = frame->status; + + FURI_LOG_D( + TAG, + "id=%08lX status=0x%02X psi=%.2f temp=%.1f", + (unsigned long)frame->id, + frame->status, + (double)pressure_psi, + (double)temperature_c); +} + +const SubGhzProtocolDecoder tpms_protocol_toyota_pmvc2x_decoder = { + .alloc = tpms_protocol_decoder_toyota_pmvc2x_alloc, + .free = tpms_protocol_decoder_toyota_pmvc2x_free, + .feed = tpms_protocol_decoder_toyota_pmvc2x_feed, + .reset = tpms_protocol_decoder_toyota_pmvc2x_reset, + .get_hash_data = tpms_protocol_decoder_toyota_pmvc2x_get_hash_data, + .serialize = tpms_protocol_decoder_toyota_pmvc2x_serialize, + .deserialize = tpms_protocol_decoder_toyota_pmvc2x_deserialize, + .get_string = tpms_protocol_decoder_toyota_pmvc2x_get_string, +}; + +const SubGhzProtocolEncoder tpms_protocol_toyota_pmvc2x_encoder = { + .alloc = NULL, + .free = NULL, + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol tpms_protocol_toyota_pmvc2x = { + .name = TPMS_PROTOCOL_TOYOTA_PMVC2X_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_FM | + SubGhzProtocolFlag_Decodable, + .decoder = &tpms_protocol_toyota_pmvc2x_decoder, + .encoder = &tpms_protocol_toyota_pmvc2x_encoder, +}; + +void* tpms_protocol_decoder_toyota_pmvc2x_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + TPMSProtocolDecoderToyotaPMVC2X* instance = malloc(sizeof(TPMSProtocolDecoderToyotaPMVC2X)); + instance->base.protocol = &tpms_protocol_toyota_pmvc2x; + instance->generic.protocol_name = instance->base.protocol->name; + tpms_protocol_decoder_toyota_pmvc2x_reset(instance); + return instance; +} + +void tpms_protocol_decoder_toyota_pmvc2x_free(void* context) { + furi_assert(context); + TPMSProtocolDecoderToyotaPMVC2X* instance = context; + free(instance); +} + +void tpms_protocol_decoder_toyota_pmvc2x_reset(void* context) { + furi_assert(context); + TPMSProtocolDecoderToyotaPMVC2X* instance = context; + instance->decoder.parser_step = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0; + instance->decoder.te_last = 0; + instance->last_status = 0; + toyota_pmvc2x_core_reset(&instance->core); +} + +void tpms_protocol_decoder_toyota_pmvc2x_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + TPMSProtocolDecoderToyotaPMVC2X* instance = context; + + if(toyota_pmvc2x_core_feed(&instance->core, level, duration)) { + ToyotaPmvc2xFrame frame = {0}; + if(toyota_pmvc2x_core_take_frame(&instance->core, &frame)) { + tpms_protocol_toyota_pmvc2x_apply_frame(instance, &frame); + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + } + } +} + +uint8_t tpms_protocol_decoder_toyota_pmvc2x_get_hash_data(void* context) { + furi_assert(context); + TPMSProtocolDecoderToyotaPMVC2X* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8U) + 1U); +} + +SubGhzProtocolStatus tpms_protocol_decoder_toyota_pmvc2x_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + TPMSProtocolDecoderToyotaPMVC2X* instance = context; + return tpms_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus tpms_protocol_decoder_toyota_pmvc2x_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + TPMSProtocolDecoderToyotaPMVC2X* instance = context; + return tpms_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + tpms_protocol_toyota_pmvc2x_const.min_count_bit_for_found); +} + +void tpms_protocol_decoder_toyota_pmvc2x_get_string(void* context, FuriString* output) { + furi_assert(context); + TPMSProtocolDecoderToyotaPMVC2X* instance = context; + furi_string_printf( + output, + "%s\r\nId:0x%08lX\r\nStatus:0x%02X\r\nBat:%d\r\nTemp:%2.0f C Bar:%2.1f", + instance->generic.protocol_name, + (unsigned long)instance->generic.id, + instance->last_status, + instance->generic.battery_low, + (double)instance->generic.temperature, + (double)instance->generic.pressure); +} diff --git a/protocols/toyota_pmvc2x.h b/protocols/toyota_pmvc2x.h new file mode 100644 index 0000000..b47ebdb --- /dev/null +++ b/protocols/toyota_pmvc2x.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +#include "toyota_pmvc2x_core.h" +#include "tpms_generic.h" + +#define TPMS_PROTOCOL_TOYOTA_PMVC2X_NAME "Toyota PMV-C2x" + +typedef struct TPMSProtocolDecoderToyotaPMVC2X TPMSProtocolDecoderToyotaPMVC2X; +typedef struct TPMSProtocolEncoderToyotaPMVC2X TPMSProtocolEncoderToyotaPMVC2X; + +extern const SubGhzProtocolDecoder tpms_protocol_toyota_pmvc2x_decoder; +extern const SubGhzProtocolEncoder tpms_protocol_toyota_pmvc2x_encoder; +extern const SubGhzProtocol tpms_protocol_toyota_pmvc2x; + +void* tpms_protocol_decoder_toyota_pmvc2x_alloc(SubGhzEnvironment* environment); +void tpms_protocol_decoder_toyota_pmvc2x_free(void* context); +void tpms_protocol_decoder_toyota_pmvc2x_reset(void* context); +void tpms_protocol_decoder_toyota_pmvc2x_feed(void* context, bool level, uint32_t duration); +uint8_t tpms_protocol_decoder_toyota_pmvc2x_get_hash_data(void* context); +SubGhzProtocolStatus tpms_protocol_decoder_toyota_pmvc2x_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); +SubGhzProtocolStatus tpms_protocol_decoder_toyota_pmvc2x_deserialize( + void* context, + FlipperFormat* flipper_format); +void tpms_protocol_decoder_toyota_pmvc2x_get_string(void* context, FuriString* output); diff --git a/protocols/toyota_pmvc2x_core.c b/protocols/toyota_pmvc2x_core.c new file mode 100644 index 0000000..08b2839 --- /dev/null +++ b/protocols/toyota_pmvc2x_core.c @@ -0,0 +1,361 @@ +#include "toyota_pmvc2x_core.h" + +#include + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +#define TOYOTA_PMVC2X_HALF_BIT_US 48U +#define TOYOTA_PMVC2X_MAX_UNITS_PER_PULSE 6U +#define TOYOTA_PMVC2X_PATTERN_MIN_LEN 8U +#define TOYOTA_PMVC2X_PAYLOAD_BITS 72U +#define TOYOTA_PMVC2X_ENCODED_BITS (TOYOTA_PMVC2X_PAYLOAD_BITS * 2U) +#define TOYOTA_PMVC2X_MIN_FRAME_BITS (TOYOTA_PMVC2X_PATTERN_MIN_LEN + TOYOTA_PMVC2X_ENCODED_BITS) +#define TOYOTA_PMVC2X_GAP_THRESHOLD_US 320U +#define TOYOTA_PMVC2X_TOLERANCE_US 18U +#define TOYOTA_PMVC2X_TOLERANCE_EXTRA_US 12U + +typedef struct { + const uint8_t* bits; + uint8_t length; +} ToyotaPmvc2xSyncPattern; + +static const uint8_t toyota_pmvc2x_sync_pattern_full[] = {1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0}; +static const uint8_t toyota_pmvc2x_sync_pattern_short0[] = {0, 0, 1, 1, 1, 1, 0, 0}; +static const uint8_t toyota_pmvc2x_sync_pattern_short1[] = {0, 0, 1, 1, 1, 1, 1, 0, 0}; +static const uint8_t toyota_pmvc2x_sync_pattern_short2[] = {0, 0, 1, 1, 1, 1, 0, 1}; +static const uint8_t toyota_pmvc2x_sync_pattern_short3[] = {0, 0, 1, 1, 1, 1, 1, 0, 1}; + +static const ToyotaPmvc2xSyncPattern toyota_pmvc2x_sync_patterns[] = { + {toyota_pmvc2x_sync_pattern_full, COUNT_OF(toyota_pmvc2x_sync_pattern_full)}, + {toyota_pmvc2x_sync_pattern_short0, COUNT_OF(toyota_pmvc2x_sync_pattern_short0)}, + {toyota_pmvc2x_sync_pattern_short1, COUNT_OF(toyota_pmvc2x_sync_pattern_short1)}, + {toyota_pmvc2x_sync_pattern_short2, COUNT_OF(toyota_pmvc2x_sync_pattern_short2)}, + {toyota_pmvc2x_sync_pattern_short3, COUNT_OF(toyota_pmvc2x_sync_pattern_short3)}, +}; + +static uint32_t toyota_pmvc2x_core_diff_u32(uint32_t a, uint32_t b) { + return (a > b) ? (a - b) : (b - a); +} + +static void toyota_pmvc2x_core_reset_buffer(ToyotaPmvc2xCore* instance) { + instance->raw_count = 0; +} + +static uint8_t toyota_pmvc2x_core_bit_at( + const ToyotaPmvc2xCore* instance, + size_t index, + bool invert) { + uint8_t bit = instance->raw_bits[index]; + return invert ? (bit ^ 1U) : bit; +} + +static bool toyota_pmvc2x_core_store_bit(ToyotaPmvc2xCore* instance, uint8_t bit) { + if(instance->raw_count >= COUNT_OF(instance->raw_bits)) { + return false; + } + + instance->raw_bits[instance->raw_count++] = bit ? 1U : 0U; + return true; +} + +static bool toyota_pmvc2x_core_append_level( + ToyotaPmvc2xCore* instance, + bool level, + uint32_t duration_us) { + if(duration_us < (TOYOTA_PMVC2X_HALF_BIT_US / 2U)) { + return true; + } + + uint32_t count = (duration_us + (TOYOTA_PMVC2X_HALF_BIT_US / 2U)) / TOYOTA_PMVC2X_HALF_BIT_US; + if(count == 0U) { + count = 1U; + } + + if(count > TOYOTA_PMVC2X_MAX_UNITS_PER_PULSE) { + return false; + } + + uint32_t expected = count * TOYOTA_PMVC2X_HALF_BIT_US; + uint32_t tolerance = (TOYOTA_PMVC2X_TOLERANCE_US * count) + TOYOTA_PMVC2X_TOLERANCE_EXTRA_US; + if(toyota_pmvc2x_core_diff_u32(duration_us, expected) > tolerance) { + return false; + } + + for(uint32_t index = 0; index < count; index++) { + if(!toyota_pmvc2x_core_store_bit(instance, level ? 1U : 0U)) { + return false; + } + } + + return true; +} + +static bool toyota_pmvc2x_core_match_pattern( + const ToyotaPmvc2xCore* instance, + size_t offset, + bool invert, + const ToyotaPmvc2xSyncPattern* pattern) { + if(offset + pattern->length > instance->raw_count) { + return false; + } + + for(size_t index = 0; index < pattern->length; index++) { + if(toyota_pmvc2x_core_bit_at(instance, offset + index, invert) != pattern->bits[index]) { + return false; + } + } + + return true; +} + +static void toyota_pmvc2x_core_append_payload_bit(uint8_t* buffer, size_t* bit_count, uint8_t bit) { + if(*bit_count >= TOYOTA_PMVC2X_PAYLOAD_BITS) { + return; + } + + size_t byte_index = *bit_count / 8U; + size_t bit_index = *bit_count % 8U; + if(bit_index == 0U) { + buffer[byte_index] = 0; + } + + buffer[byte_index] = (buffer[byte_index] << 1U) | (bit & 1U); + (*bit_count)++; +} + +static bool toyota_pmvc2x_core_diff_manchester( + const ToyotaPmvc2xCore* instance, + size_t start, + bool invert, + uint8_t* payload) { + size_t len = instance->raw_count; + size_t pos = start; + uint8_t bit1 = 0; + uint8_t bit2 = 0; + size_t out_bits = 0; + + while((pos + 1U) < len && out_bits < TOYOTA_PMVC2X_PAYLOAD_BITS) { + bit1 = toyota_pmvc2x_core_bit_at(instance, pos++, invert); + if(pos >= len) { + return false; + } + + bit2 = toyota_pmvc2x_core_bit_at(instance, pos++, invert); + if(pos >= len) { + return false; + } + + uint8_t bit3 = toyota_pmvc2x_core_bit_at(instance, pos, invert); + + if(bit1 != bit2) { + if(bit2 != bit3) { + toyota_pmvc2x_core_append_payload_bit(payload, &out_bits, 0U); + } else { + bit2 = bit1; + pos -= 1U; + break; + } + } else { + bit2 = 1U - bit1; + if(pos < 2U) { + return false; + } + pos -= 2U; + break; + } + } + + while((pos + 1U) < len && out_bits < TOYOTA_PMVC2X_PAYLOAD_BITS) { + bit1 = toyota_pmvc2x_core_bit_at(instance, pos++, invert); + if(bit1 == bit2) { + break; + } + + if(pos >= len) { + return false; + } + + bit2 = toyota_pmvc2x_core_bit_at(instance, pos++, invert); + toyota_pmvc2x_core_append_payload_bit(payload, &out_bits, (bit1 == bit2) ? 1U : 0U); + } + + return out_bits >= TOYOTA_PMVC2X_PAYLOAD_BITS; +} + +static uint8_t toyota_pmvc2x_core_crc8( + const uint8_t* message, + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init; + + for(size_t byte = 0; byte < size; byte++) { + remainder ^= message[byte]; + for(uint8_t bit = 0; bit < 8U; bit++) { + if(remainder & 0x80U) { + remainder = (uint8_t)((remainder << 1U) ^ polynomial); + } else { + remainder = (uint8_t)(remainder << 1U); + } + } + } + + return remainder; +} + +static void toyota_pmvc2x_core_store_frame(ToyotaPmvc2xCore* instance, const uint8_t* payload) { + ToyotaPmvc2xFrame* frame = &instance->frame; + memcpy(frame->payload, payload, TOYOTA_PMVC2X_PAYLOAD_BYTES); + frame->id = ((uint32_t)payload[0] << 24U) | ((uint32_t)payload[1] << 16U) | + ((uint32_t)payload[2] << 8U) | (uint32_t)payload[3]; + frame->status = (uint8_t)((payload[4] & 0x80U) | (payload[6] & 0x7FU)); + frame->pressure_raw = (uint8_t)(((payload[4] & 0x7FU) << 1U) | (payload[5] >> 7U)); + frame->temperature_raw = (uint8_t)(((payload[5] & 0x7FU) << 1U) | (payload[6] >> 7U)); + frame->crc_ok = true; + instance->frame_pending = true; +} + +static bool toyota_pmvc2x_core_decode_from( + ToyotaPmvc2xCore* instance, + size_t start, + bool invert) { + if(start + TOYOTA_PMVC2X_ENCODED_BITS > instance->raw_count) { + return false; + } + + uint8_t payload[TOYOTA_PMVC2X_PAYLOAD_BYTES] = {0}; + if(!toyota_pmvc2x_core_diff_manchester(instance, start, invert, payload)) { + return false; + } + + if(toyota_pmvc2x_core_crc8(payload, 8U, 0x07U, 0x80U) != payload[8]) { + return false; + } + + uint8_t pressure_raw = (uint8_t)(((payload[4] & 0x7FU) << 1U) | (payload[5] >> 7U)); + uint8_t pressure_check = (uint8_t)(payload[7] ^ 0xFFU); + if(pressure_raw != pressure_check) { + return false; + } + + toyota_pmvc2x_core_store_frame(instance, payload); + return true; +} + +static bool toyota_pmvc2x_core_try_decode(ToyotaPmvc2xCore* instance) { + if(instance->raw_count < TOYOTA_PMVC2X_MIN_FRAME_BITS) { + return false; + } + + for(size_t pattern_index = 0; pattern_index < COUNT_OF(toyota_pmvc2x_sync_patterns); + pattern_index++) { + const ToyotaPmvc2xSyncPattern* pattern = &toyota_pmvc2x_sync_patterns[pattern_index]; + if(instance->raw_count < (pattern->length + TOYOTA_PMVC2X_ENCODED_BITS)) { + continue; + } + + size_t limit = instance->raw_count - (pattern->length + TOYOTA_PMVC2X_ENCODED_BITS) + 1U; + for(size_t offset = 0; offset < limit; offset++) { + bool matched = toyota_pmvc2x_core_match_pattern(instance, offset, false, pattern); + bool matched_invert = toyota_pmvc2x_core_match_pattern(instance, offset, true, pattern); + if(!(matched || matched_invert)) { + continue; + } + + bool invert_options[2] = {false, true}; + size_t option_count = (matched && matched_invert) ? 2U : 1U; + if(!matched) { + invert_options[0] = true; + } + + size_t base_start = offset + pattern->length - 1U; + for(size_t option = 0; option < option_count; option++) { + bool invert = invert_options[option]; + if((invert && !matched_invert) || (!invert && !matched)) { + continue; + } + + for(int shift = -1; shift <= 2; shift++) { + int adjusted = (int)base_start + shift; + if(adjusted < 0) { + continue; + } + + if(toyota_pmvc2x_core_decode_from(instance, (size_t)adjusted, invert)) { + return true; + } + } + } + } + } + + return false; +} + +void toyota_pmvc2x_core_reset(ToyotaPmvc2xCore* instance) { + if(instance == NULL) { + return; + } + + memset(&instance->frame, 0, sizeof(instance->frame)); + instance->frame_pending = false; + toyota_pmvc2x_core_reset_buffer(instance); +} + +bool toyota_pmvc2x_core_feed(ToyotaPmvc2xCore* instance, bool level, uint32_t duration_us) { + if(instance == NULL) { + return false; + } + + if(duration_us > TOYOTA_PMVC2X_GAP_THRESHOLD_US) { + if(toyota_pmvc2x_core_try_decode(instance)) { + toyota_pmvc2x_core_reset_buffer(instance); + return true; + } else if(instance->raw_count > TOYOTA_PMVC2X_PATTERN_MIN_LEN) { + size_t keep = TOYOTA_PMVC2X_PATTERN_MIN_LEN / 2U; + if(keep > instance->raw_count) { + keep = instance->raw_count; + } + if(keep > 0U) { + memmove( + instance->raw_bits, + instance->raw_bits + (instance->raw_count - keep), + keep); + } + instance->raw_count = keep; + } else { + toyota_pmvc2x_core_reset_buffer(instance); + } + return false; + } + + if(!toyota_pmvc2x_core_append_level(instance, level, duration_us)) { + toyota_pmvc2x_core_reset_buffer(instance); + return false; + } + + if(instance->raw_count >= TOYOTA_PMVC2X_MIN_FRAME_BITS && + toyota_pmvc2x_core_try_decode(instance)) { + toyota_pmvc2x_core_reset_buffer(instance); + return true; + } + + if(instance->raw_count >= COUNT_OF(instance->raw_bits)) { + toyota_pmvc2x_core_reset_buffer(instance); + } + + return false; +} + +bool toyota_pmvc2x_core_take_frame(ToyotaPmvc2xCore* instance, ToyotaPmvc2xFrame* frame) { + if((instance == NULL) || !instance->frame_pending) { + return false; + } + + if(frame != NULL) { + *frame = instance->frame; + } + + instance->frame_pending = false; + return true; +} diff --git a/protocols/toyota_pmvc2x_core.h b/protocols/toyota_pmvc2x_core.h new file mode 100644 index 0000000..6c9d014 --- /dev/null +++ b/protocols/toyota_pmvc2x_core.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#define TOYOTA_PMVC2X_PAYLOAD_BYTES 9U + +typedef struct { + uint32_t id; + uint8_t status; + uint8_t pressure_raw; + uint8_t temperature_raw; + bool crc_ok; + uint8_t payload[TOYOTA_PMVC2X_PAYLOAD_BYTES]; +} ToyotaPmvc2xFrame; + +typedef struct { + uint8_t raw_bits[256]; + size_t raw_count; + bool frame_pending; + ToyotaPmvc2xFrame frame; +} ToyotaPmvc2xCore; + +void toyota_pmvc2x_core_reset(ToyotaPmvc2xCore* instance); +bool toyota_pmvc2x_core_feed(ToyotaPmvc2xCore* instance, bool level, uint32_t duration_us); +bool toyota_pmvc2x_core_take_frame(ToyotaPmvc2xCore* instance, ToyotaPmvc2xFrame* frame); diff --git a/protocols/tpms_generic.c b/protocols/tpms_generic.c index 68c352f..082bc48 100644 --- a/protocols/tpms_generic.c +++ b/protocols/tpms_generic.c @@ -13,6 +13,8 @@ void tpms_block_generic_get_preset_name(const char* preset_name, FuriString* pre preset_name_temp = "FuriHalSubGhzPresetOok650Async"; } else if(!strcmp(preset_name, "FM238")) { preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; + } else if(!strcmp(preset_name, "FM12K")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev12KAsync"; } else if(!strcmp(preset_name, "FM476")) { preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; } else { @@ -225,4 +227,4 @@ SubGhzProtocolStatus tpms_block_generic_deserialize_check_count_bit( } } while(false); return ret; -} \ No newline at end of file +} diff --git a/scenes/tpms_scene_receiver.c b/scenes/tpms_scene_receiver.c index ec6dfd8..56c2b12 100644 --- a/scenes/tpms_scene_receiver.c +++ b/scenes/tpms_scene_receiver.c @@ -31,6 +31,19 @@ static const NotificationSequence subghz_sequence_rx_locked = { NULL, }; +static void tpms_scene_receiver_reload_history(TPMSApp* app) { + furi_assert(app); + + tpms_view_receiver_exit(app->tpms_receiver); + for(uint8_t i = 0; i < tpms_history_get_item(app->txrx->history); i++) { + tpms_view_receiver_add_item_to_menu( + app->tpms_receiver, + tpms_history_get_type_label(app->txrx->history, i), + tpms_history_get_id_label(app->txrx->history, i), + tpms_history_get_type_protocol(app->txrx->history, i)); + } +} + static void tpms_scene_receiver_update_statusbar(void* context) { TPMSApp* app = context; FuriString* history_stat_str; @@ -76,21 +89,18 @@ static void tpms_scene_receiver_add_to_history_callback( void* context) { furi_assert(context); TPMSApp* app = context; - FuriString* str_buff; - str_buff = furi_string_alloc(); - - if(tpms_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == - TPMSHistoryStateAddKeyNewDada) { - furi_string_reset(str_buff); - - tpms_history_get_text_item_menu( - app->txrx->history, str_buff, tpms_history_get_item(app->txrx->history) - 1); - tpms_view_receiver_add_item_to_menu( - app->tpms_receiver, - furi_string_get_cstr(str_buff), - tpms_history_get_type_protocol( - app->txrx->history, tpms_history_get_item(app->txrx->history) - 1)); + float rssi = subghz_devices_get_rssi(app->txrx->radio_device); + if((app->txrx->rssi_filter_dbm != TPMS_RSSI_FILTER_OFF) && + (rssi < (float)app->txrx->rssi_filter_dbm)) { + subghz_receiver_reset(receiver); + return; + } + TPMSHistoryStateAddKey history_state = + tpms_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset); + if((history_state == TPMSHistoryStateAddKeyNewDada) || + (history_state == TPMSHistoryStateAddKeyUpdateData)) { + tpms_scene_receiver_reload_history(app); tpms_scene_receiver_update_statusbar(app); notification_message(app->notifications, &sequence_blink_green_10); if(app->lock != TPMSLockOn) { @@ -100,19 +110,13 @@ static void tpms_scene_receiver_add_to_history_callback( } } subghz_receiver_reset(receiver); - furi_string_free(str_buff); app->txrx->rx_key_state = TPMSRxKeyStateAddKey; } void tpms_scene_receiver_on_enter(void* context) { TPMSApp* app = context; - FuriString* str_buff; - str_buff = furi_string_alloc(); - if(app->txrx->rx_key_state == TPMSRxKeyStateIDLE) { - tpms_preset_init( - app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); tpms_history_reset(app->txrx->history); app->txrx->rx_key_state = TPMSRxKeyStateStart; } @@ -120,35 +124,17 @@ void tpms_scene_receiver_on_enter(void* context) { tpms_view_receiver_set_lock(app->tpms_receiver, app->lock); //Load history to receiver - tpms_view_receiver_exit(app->tpms_receiver); - for(uint8_t i = 0; i < tpms_history_get_item(app->txrx->history); i++) { - furi_string_reset(str_buff); - tpms_history_get_text_item_menu(app->txrx->history, str_buff, i); - tpms_view_receiver_add_item_to_menu( - app->tpms_receiver, - furi_string_get_cstr(str_buff), - tpms_history_get_type_protocol(app->txrx->history, i)); + tpms_scene_receiver_reload_history(app); + if(tpms_history_get_item(app->txrx->history)) { app->txrx->rx_key_state = TPMSRxKeyStateAddKey; } - furi_string_free(str_buff); tpms_scene_receiver_update_statusbar(app); tpms_view_receiver_set_callback(app->tpms_receiver, tpms_scene_receiver_callback, app); subghz_receiver_set_rx_callback( app->txrx->receiver, tpms_scene_receiver_add_to_history_callback, app); - if(app->txrx->txrx_state == TPMSTxRxStateRx) { - tpms_rx_end(app); - }; - if((app->txrx->txrx_state == TPMSTxRxStateIDLE) || - (app->txrx->txrx_state == TPMSTxRxStateSleep)) { - tpms_begin( - app, - subghz_setting_get_preset_data_by_name( - app->setting, furi_string_get_cstr(app->txrx->preset->name))); - - tpms_rx(app, app->txrx->preset->frequency); - } + tpms_receiver_restart(app); tpms_view_receiver_set_idx_menu(app->tpms_receiver, app->txrx->idx_menu_chosen); view_dispatcher_switch_to_view(app->view_dispatcher, TPMSViewReceiver); @@ -170,8 +156,6 @@ bool tpms_scene_receiver_on_event(void* context, SceneManagerEvent event) { subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app); app->txrx->rx_key_state = TPMSRxKeyStateIDLE; - tpms_preset_init( - app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); if(scene_manager_has_previous_scene(app->scene_manager, TPMSSceneStart)) { consumed = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, TPMSSceneStart); @@ -206,7 +190,7 @@ bool tpms_scene_receiver_on_event(void* context, SceneManagerEvent event) { tpms_scene_receiver_update_statusbar(app); } // Get current RSSI - float rssi = furi_hal_subghz_get_rssi(); + float rssi = subghz_devices_get_rssi(app->txrx->radio_device); tpms_view_receiver_set_rssi(app->tpms_receiver, rssi); if(app->txrx->txrx_state == TPMSTxRxStateRx) { @@ -217,5 +201,11 @@ bool tpms_scene_receiver_on_event(void* context, SceneManagerEvent event) { } void tpms_scene_receiver_on_exit(void* context) { - UNUSED(context); + TPMSApp* app = context; + subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app); + + if(app->txrx->txrx_state == TPMSTxRxStateRx) { + tpms_rx_end(app); + tpms_sleep(app); + } } diff --git a/scenes/tpms_scene_receiver_config.c b/scenes/tpms_scene_receiver_config.c index df51cd0..b06630b 100644 --- a/scenes/tpms_scene_receiver_config.c +++ b/scenes/tpms_scene_receiver_config.c @@ -4,6 +4,8 @@ enum TPMSSettingIndex { TPMSSettingIndexFrequency, TPMSSettingIndexHopping, TPMSSettingIndexModulation, + TPMSSettingIndexRadio, + TPMSSettingIndexMinRssi, TPMSSettingIndexLock, }; @@ -17,34 +19,102 @@ const uint32_t hopping_value[HOPPING_COUNT] = { TPMSHopperStateRunnig, }; +#define RADIO_COUNT 3 +const char* const radio_text[RADIO_COUNT] = { + "Auto", + "Internal", + "External", +}; +const SubGhzRadioDeviceType radio_value[RADIO_COUNT] = { + SubGhzRadioDeviceTypeAuto, + SubGhzRadioDeviceTypeInternal, + SubGhzRadioDeviceTypeExternalCC1101, +}; + +#define RSSI_FILTER_COUNT 7 +const char* const rssi_filter_text[RSSI_FILTER_COUNT] = { + "Off", + "-100 dBm", + "-95 dBm", + "-90 dBm", + "-85 dBm", + "-80 dBm", + "-75 dBm", +}; +const int16_t rssi_filter_value[RSSI_FILTER_COUNT] = { + TPMS_RSSI_FILTER_OFF, + -100, + -95, + -90, + -85, + -80, + -75, +}; + +static void tpms_scene_receiver_config_frequency_to_string( + char* text_buf, + size_t text_buf_size, + uint32_t frequency) { + snprintf( + text_buf, + text_buf_size, + "%03lu.%02lu", + (unsigned long)(frequency / 1000000U), + (unsigned long)((frequency % 1000000U) / 10000U)); +} + uint8_t tpms_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); TPMSApp* app = context; - uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { if(value == subghz_setting_get_frequency(app->setting, i)) { - index = i; - break; - } else { - index = subghz_setting_get_frequency_default_index(app->setting); + return i; } } - return index; + + if(value != tpms_preset_get_default_frequency()) { + for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { + if(tpms_preset_get_default_frequency() == subghz_setting_get_frequency(app->setting, i)) { + return i; + } + } + } + + return subghz_setting_get_frequency_default_index(app->setting); } uint8_t tpms_scene_receiver_config_next_preset(const char* preset_name, void* context) { furi_assert(context); - TPMSApp* app = context; - uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { - if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { - index = i; - break; - } else { - // index = subghz_setting_get_frequency_default_index(app ->setting); + UNUSED(context); + for(uint8_t i = 0; i < tpms_preset_count(); i++) { + const TPMSPresetDefinition* definition = tpms_preset_get_by_index(i); + if(definition && !strcmp(definition->name, preset_name)) { + return i; + } + } + + return 0; +} + +static uint8_t tpms_scene_receiver_config_radio_value_index(SubGhzRadioDeviceType value) { + for(uint8_t i = 0; i < RADIO_COUNT; i++) { + if(radio_value[i] == value) { + return i; } } - return index; + + return 0; +} + +static uint8_t tpms_scene_receiver_config_rssi_filter_value_index(int16_t value) { + for(uint8_t i = 0; i < RSSI_FILTER_COUNT; i++) { + if(rssi_filter_value[i] == value) { + return i; + } + } + + return 0; } uint8_t tpms_scene_receiver_config_hopper_value_index( @@ -73,12 +143,8 @@ static void tpms_scene_receiver_config_set_frequency(VariableItem* item) { if(app->txrx->hopper_state == TPMSHopperStateOFF) { char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_frequency(app->setting, index) / 1000000, - (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); + tpms_scene_receiver_config_frequency_to_string( + text_buf, sizeof(text_buf), subghz_setting_get_frequency(app->setting, index)); variable_item_set_current_value_text(item, text_buf); app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); } else { @@ -90,14 +156,27 @@ static void tpms_scene_receiver_config_set_frequency(VariableItem* item) { static void tpms_scene_receiver_config_set_preset(VariableItem* item) { TPMSApp* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(app->setting, index)); - tpms_preset_init( - app, - subghz_setting_get_preset_name(app->setting, index), - app->txrx->preset->frequency, - subghz_setting_get_preset_data(app->setting, index), - subghz_setting_get_preset_data_size(app->setting, index)); + const TPMSPresetDefinition* definition = tpms_preset_get_by_index(index); + furi_assert(definition); + + variable_item_set_current_value_text(item, definition->name); + tpms_preset_init(app, definition->name, app->txrx->preset->frequency, NULL, 0); +} + +static void tpms_scene_receiver_config_set_radio(VariableItem* item) { + TPMSApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, radio_text[index]); + app->txrx->radio_device_type = radio_value[index]; +} + +static void tpms_scene_receiver_config_set_rssi_filter(VariableItem* item) { + TPMSApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, rssi_filter_text[index]); + app->txrx->rssi_filter_dbm = rssi_filter_value[index]; } static void tpms_scene_receiver_config_set_hopping_running(VariableItem* item) { @@ -107,21 +186,17 @@ static void tpms_scene_receiver_config_set_hopping_running(VariableItem* item) { variable_item_set_current_value_text(item, hopping_text[index]); if(hopping_value[index] == TPMSHopperStateOFF) { char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_default_frequency(app->setting) / 1000000, - (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); + tpms_scene_receiver_config_frequency_to_string( + text_buf, sizeof(text_buf), tpms_preset_get_default_frequency()); variable_item_set_current_value_text( (VariableItem*)scene_manager_get_scene_state( app->scene_manager, TPMSSceneReceiverConfig), text_buf); - app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); + app->txrx->preset->frequency = tpms_preset_get_default_frequency(); variable_item_set_current_value_index( (VariableItem*)scene_manager_get_scene_state( app->scene_manager, TPMSSceneReceiverConfig), - subghz_setting_get_frequency_default_index(app->setting)); + tpms_scene_receiver_config_next_frequency(tpms_preset_get_default_frequency(), app)); } else { variable_item_set_current_value_text( (VariableItem*)scene_manager_get_scene_state( @@ -159,12 +234,8 @@ void tpms_scene_receiver_config_on_enter(void* context) { scene_manager_set_scene_state(app->scene_manager, TPMSSceneReceiverConfig, (uint32_t)item); variable_item_set_current_value_index(item, value_index); char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_frequency(app->setting, value_index) / 1000000, - (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); + tpms_scene_receiver_config_frequency_to_string( + text_buf, sizeof(text_buf), subghz_setting_get_frequency(app->setting, value_index)); variable_item_set_current_value_text(item, text_buf); item = variable_item_list_add( @@ -181,14 +252,29 @@ void tpms_scene_receiver_config_on_enter(void* context) { item = variable_item_list_add( app->variable_item_list, "Modulation:", - subghz_setting_get_preset_count(app->setting), + tpms_preset_count(), tpms_scene_receiver_config_set_preset, app); value_index = tpms_scene_receiver_config_next_preset(furi_string_get_cstr(app->txrx->preset->name), app); variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(app->setting, value_index)); + variable_item_set_current_value_text(item, tpms_preset_get_by_index(value_index)->name); + + item = variable_item_list_add( + app->variable_item_list, "Radio:", RADIO_COUNT, tpms_scene_receiver_config_set_radio, app); + value_index = tpms_scene_receiver_config_radio_value_index(app->txrx->radio_device_type); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, radio_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, + "Min RSSI:", + RSSI_FILTER_COUNT, + tpms_scene_receiver_config_set_rssi_filter, + app); + value_index = tpms_scene_receiver_config_rssi_filter_value_index(app->txrx->rssi_filter_dbm); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rssi_filter_text[value_index]); variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); variable_item_list_set_enter_callback( diff --git a/tpms_app.c b/tpms_app.c index 87036c7..395a66d 100644 --- a/tpms_app.c +++ b/tpms_app.c @@ -85,9 +85,17 @@ TPMSApp* tpms_app_alloc() { app->txrx = malloc(sizeof(TPMSTxRx)); app->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); app->txrx->preset->name = furi_string_alloc(); - tpms_preset_init(app, "AM650", subghz_setting_get_default_frequency(app->setting), NULL, 0); + app->txrx->radio_device = NULL; + app->txrx->radio_device_type = SubGhzRadioDeviceTypeAuto; + tpms_preset_init( + app, + TPMS_PRESET_TOYOTA_PMVC2X_NAME, + tpms_preset_get_default_frequency(), + NULL, + 0); app->txrx->hopper_state = TPMSHopperStateOFF; + app->txrx->rssi_filter_dbm = TPMS_RSSI_FILTER_OFF; app->txrx->history = tpms_history_alloc(); app->txrx->worker = subghz_worker_alloc(); app->txrx->environment = subghz_environment_alloc(); @@ -97,8 +105,7 @@ TPMSApp* tpms_app_alloc() { subghz_devices_init(); - app->txrx->radio_device = - radio_device_loader_set(app->txrx->radio_device, SubGhzRadioDeviceTypeExternalCC1101); + tpms_apply_radio_device(app); subghz_devices_reset(app->txrx->radio_device); subghz_devices_idle(app->txrx->radio_device); diff --git a/tpms_app_i.c b/tpms_app_i.c index b03b3e8..4b225b8 100644 --- a/tpms_app_i.c +++ b/tpms_app_i.c @@ -11,27 +11,31 @@ void tpms_preset_init( size_t preset_data_size) { furi_assert(context); TPMSApp* app = context; - furi_string_set(app->txrx->preset->name, preset_name); - app->txrx->preset->frequency = frequency; - app->txrx->preset->data = preset_data; - app->txrx->preset->data_size = preset_data_size; + tpms_preset_init_radio( + app->txrx->preset, preset_name, frequency, preset_data, preset_data_size); } bool tpms_set_preset(TPMSApp* app, const char* preset) { + const char* preset_name = NULL; + if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) { - furi_string_set(app->txrx->preset->name, "AM270"); + preset_name = "AM270"; } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) { - furi_string_set(app->txrx->preset->name, "AM650"); + preset_name = "AM650"; } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) { - furi_string_set(app->txrx->preset->name, "FM238"); + preset_name = "FM238"; + } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev12KAsync")) { + preset_name = "FM12K"; } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) { - furi_string_set(app->txrx->preset->name, "FM476"); + preset_name = "FM476"; } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) { - furi_string_set(app->txrx->preset->name, "CUSTOM"); + preset_name = TPMS_PRESET_TOYOTA_PMVC2X_NAME; } else { FURI_LOG_E(TAG, "Unknown preset"); return false; } + + tpms_preset_init_radio(app->txrx->preset, preset_name, app->txrx->preset->frequency, NULL, 0); return true; } @@ -45,35 +49,58 @@ void tpms_get_frequency_modulation(TPMSApp* app, FuriString* frequency, FuriStri app->txrx->preset->frequency / 10000 % 100); } if(modulation != NULL) { - furi_string_printf(modulation, "%.2s", furi_string_get_cstr(app->txrx->preset->name)); + furi_string_printf( + modulation, "%s", tpms_preset_get_short_name(furi_string_get_cstr(app->txrx->preset->name))); } } void tpms_begin(TPMSApp* app, uint8_t* preset_data) { furi_assert(app); - UNUSED(preset_data); - furi_hal_subghz_reset(); - furi_hal_subghz_idle(); - furi_hal_subghz_load_custom_preset(preset_data); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + FuriHalSubGhzPreset radio_preset = + tpms_preset_get_radio_preset(furi_string_get_cstr(app->txrx->preset->name)); + + subghz_devices_reset(app->txrx->radio_device); + subghz_devices_idle(app->txrx->radio_device); + subghz_devices_load_preset( + app->txrx->radio_device, + radio_preset, + (radio_preset == FuriHalSubGhzPresetCustom) ? preset_data : NULL); app->txrx->txrx_state = TPMSTxRxStateIDLE; } +void tpms_apply_radio_device(TPMSApp* app) { + furi_assert(app); + app->txrx->radio_device = + radio_device_loader_set(app->txrx->radio_device, app->txrx->radio_device_type); +} + +void tpms_receiver_restart(TPMSApp* app) { + furi_assert(app); + + if(app->txrx->txrx_state == TPMSTxRxStateRx) { + tpms_rx_end(app); + } + + subghz_receiver_reset(app->txrx->receiver); + tpms_apply_radio_device(app); + tpms_begin(app, app->txrx->preset->data); + tpms_rx(app, app->txrx->preset->frequency); +} + uint32_t tpms_rx(TPMSApp* app, uint32_t frequency) { furi_assert(app); - if(!furi_hal_subghz_is_frequency_valid(frequency)) { + if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) { furi_crash("TPMS: Incorrect RX frequency."); } furi_assert( app->txrx->txrx_state != TPMSTxRxStateRx && app->txrx->txrx_state != TPMSTxRxStateSleep); - furi_hal_subghz_idle(); - uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); - furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); - furi_hal_subghz_flush_rx(); - furi_hal_subghz_rx(); + subghz_devices_idle(app->txrx->radio_device); + uint32_t value = subghz_devices_set_frequency(app->txrx->radio_device, frequency); + subghz_devices_flush_rx(app->txrx->radio_device); - furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker); + subghz_devices_start_async_rx( + app->txrx->radio_device, subghz_worker_rx_callback, app->txrx->worker); subghz_worker_start(app->txrx->worker); app->txrx->txrx_state = TPMSTxRxStateRx; return value; @@ -82,7 +109,7 @@ uint32_t tpms_rx(TPMSApp* app, uint32_t frequency) { void tpms_idle(TPMSApp* app) { furi_assert(app); furi_assert(app->txrx->txrx_state != TPMSTxRxStateSleep); - furi_hal_subghz_idle(); + subghz_devices_idle(app->txrx->radio_device); app->txrx->txrx_state = TPMSTxRxStateIDLE; } @@ -91,15 +118,15 @@ void tpms_rx_end(TPMSApp* app) { furi_assert(app->txrx->txrx_state == TPMSTxRxStateRx); if(subghz_worker_is_running(app->txrx->worker)) { subghz_worker_stop(app->txrx->worker); - furi_hal_subghz_stop_async_rx(); + subghz_devices_stop_async_rx(app->txrx->radio_device); } - furi_hal_subghz_idle(); + subghz_devices_idle(app->txrx->radio_device); app->txrx->txrx_state = TPMSTxRxStateIDLE; } void tpms_sleep(TPMSApp* app) { furi_assert(app); - furi_hal_subghz_sleep(); + subghz_devices_sleep(app->txrx->radio_device); app->txrx->txrx_state = TPMSTxRxStateSleep; } @@ -122,7 +149,7 @@ void tpms_hopper_update(TPMSApp* app) { float rssi = -127.0f; if(app->txrx->hopper_state != TPMSHopperStateRSSITimeOut) { // See RSSI Calculation timings in CC1101 17.3 RSSI - rssi = furi_hal_subghz_get_rssi(); + rssi = subghz_devices_get_rssi(app->txrx->radio_device); // Stay if RSSI is high enough if(rssi > -90.0f) { diff --git a/tpms_app_i.h b/tpms_app_i.h index 427f1f4..95ceb77 100644 --- a/tpms_app_i.h +++ b/tpms_app_i.h @@ -21,6 +21,7 @@ #include #include "helpers/radio_device_loader.h" +#include "helpers/tpms_preset.h" typedef struct TPMSApp TPMSApp; @@ -28,6 +29,7 @@ struct TPMSTxRx { SubGhzWorker* worker; const SubGhzDevice* radio_device; + SubGhzRadioDeviceType radio_device_type; SubGhzEnvironment* environment; SubGhzReceiver* receiver; SubGhzRadioPreset* preset; @@ -35,6 +37,7 @@ struct TPMSTxRx { uint16_t idx_menu_chosen; TPMSTxRxState txrx_state; TPMSHopperState hopper_state; + int16_t rssi_filter_dbm; uint8_t hopper_timeout; uint8_t hopper_idx_frequency; TPMSRxKeyState rx_key_state; @@ -68,6 +71,8 @@ void tpms_preset_init( bool tpms_set_preset(TPMSApp* app, const char* preset); void tpms_get_frequency_modulation(TPMSApp* app, FuriString* frequency, FuriString* modulation); void tpms_begin(TPMSApp* app, uint8_t* preset_data); +void tpms_apply_radio_device(TPMSApp* app); +void tpms_receiver_restart(TPMSApp* app); uint32_t tpms_rx(TPMSApp* app, uint32_t frequency); void tpms_idle(TPMSApp* app); void tpms_rx_end(TPMSApp* app); diff --git a/tpms_history.c b/tpms_history.c index 2b69003..e9b74c0 100644 --- a/tpms_history.c +++ b/tpms_history.c @@ -3,14 +3,18 @@ #include #include #include "protocols/tpms_generic.h" +#include "protocols/toyota_pmvc2x.h" #include +#include #define TPMS_HISTORY_MAX 50 #define TAG "TPMSHistory" typedef struct { FuriString* item_str; + FuriString* type_label; + FuriString* id_label; FlipperFormat* flipper_string; uint8_t type; uint32_t id; @@ -33,6 +37,44 @@ struct TPMSHistory { TPMSHistoryStruct* history; }; +static void tpms_history_build_labels(TPMSHistory* instance, TPMSHistoryItem* item) { + furi_assert(instance); + furi_assert(item); + + furi_string_reset(instance->tmp_string); + if(flipper_format_rewind(item->flipper_string) && + flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { + if(strcmp(furi_string_get_cstr(instance->tmp_string), TPMS_PROTOCOL_TOYOTA_PMVC2X_NAME) == 0) { + furi_string_set_str(item->type_label, "Toyota-C2x"); + } else { + furi_string_set(item->type_label, instance->tmp_string); + } + } else { + furi_string_set_str(item->type_label, "TPMS"); + } + + furi_string_printf(item->id_label, "%08lX", (unsigned long)item->id); + furi_string_printf( + item->item_str, + "%s %s", + furi_string_get_cstr(item->type_label), + furi_string_get_cstr(item->id_label)); +} + +static void tpms_history_move_item_to_front(TPMSHistory* instance, size_t index) { + furi_assert(instance); + if(index == 0 || index >= TPMSHistoryItemArray_size(instance->history->data)) { + return; + } + + TPMSHistoryItem item = *TPMSHistoryItemArray_get(instance->history->data, index); + for(size_t i = index; i > 0; i--) { + *TPMSHistoryItemArray_get(instance->history->data, i) = + *TPMSHistoryItemArray_get(instance->history->data, i - 1); + } + *TPMSHistoryItemArray_get(instance->history->data, 0) = item; +} + TPMSHistory* tpms_history_alloc(void) { TPMSHistory* instance = malloc(sizeof(TPMSHistory)); instance->tmp_string = furi_string_alloc(); @@ -47,6 +89,8 @@ void tpms_history_free(TPMSHistory* instance) { for M_EACH(item, instance->history->data, TPMSHistoryItemArray_t) { furi_string_free(item->item_str); + furi_string_free(item->type_label); + furi_string_free(item->id_label); furi_string_free(item->preset->name); free(item->preset); flipper_format_free(item->flipper_string); @@ -81,6 +125,8 @@ void tpms_history_reset(TPMSHistory* instance) { for M_EACH(item, instance->history->data, TPMSHistoryItemArray_t) { furi_string_free(item->item_str); + furi_string_free(item->type_label); + furi_string_free(item->id_label); furi_string_free(item->preset->name); free(item->preset); flipper_format_free(item->flipper_string); @@ -138,6 +184,18 @@ void tpms_history_get_text_item_menu(TPMSHistory* instance, FuriString* output, furi_string_set(output, item->item_str); } +const char* tpms_history_get_type_label(TPMSHistory* instance, uint16_t idx) { + furi_assert(instance); + TPMSHistoryItem* item = TPMSHistoryItemArray_get(instance->history->data, idx); + return furi_string_get_cstr(item->type_label); +} + +const char* tpms_history_get_id_label(TPMSHistory* instance, uint16_t idx) { + furi_assert(instance); + TPMSHistoryItem* item = TPMSHistoryItemArray_get(instance->history->data, idx); + return furi_string_get_cstr(item->id_label); +} + TPMSHistoryStateAddKey tpms_history_add_to_history(TPMSHistory* instance, void* context, SubGhzRadioPreset* preset) { furi_assert(instance); @@ -181,6 +239,12 @@ TPMSHistoryStateAddKey Stream* flipper_string_stream = flipper_format_get_raw_stream(item->flipper_string); stream_clean(flipper_string_stream); subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); + item->preset->frequency = preset->frequency; + furi_string_set(item->preset->name, preset->name); + item->preset->data = preset->data; + item->preset->data_size = preset->data_size; + tpms_history_build_labels(instance, item); + tpms_history_move_item_to_front(instance, i); return TPMSHistoryStateAddKeyUpdateData; } } @@ -198,33 +262,13 @@ TPMSHistoryStateAddKey item->id = id; item->item_str = furi_string_alloc(); + item->type_label = furi_string_alloc(); + item->id_label = furi_string_alloc(); item->flipper_string = flipper_format_string_alloc(); subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); - - do { - if(!flipper_format_rewind(item->flipper_string)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - if(!flipper_format_read_string( - item->flipper_string, "Protocol", instance->tmp_string)) { - FURI_LOG_E(TAG, "Missing Protocol"); - break; - } - - if(!flipper_format_rewind(item->flipper_string)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - uint32_t id = 0; - if(!flipper_format_read_uint32(item->flipper_string, "Id", &id, 1)) { - FURI_LOG_E(TAG, "Missing Id"); - break; - } - furi_string_cat_printf(instance->tmp_string, " %lX", id); - - furi_string_set(item->item_str, instance->tmp_string); - } while(false); + tpms_history_build_labels(instance, item); + tpms_history_move_item_to_front( + instance, TPMSHistoryItemArray_size(instance->history->data) - 1); instance->last_index_write++; return TPMSHistoryStateAddKeyNewDada; } diff --git a/tpms_history.h b/tpms_history.h index d791038..929c64e 100644 --- a/tpms_history.h +++ b/tpms_history.h @@ -84,6 +84,8 @@ const char* tpms_history_get_protocol_name(TPMSHistory* instance, uint16_t idx); * @param idx - record index */ void tpms_history_get_text_item_menu(TPMSHistory* instance, FuriString* output, uint16_t idx); +const char* tpms_history_get_type_label(TPMSHistory* instance, uint16_t idx); +const char* tpms_history_get_id_label(TPMSHistory* instance, uint16_t idx); /** Get string the remaining number of records to history * diff --git a/views/tpms_receiver.c b/views/tpms_receiver.c index 0dc31c1..9683789 100644 --- a/views/tpms_receiver.c +++ b/views/tpms_receiver.c @@ -10,13 +10,14 @@ #define TAG "TPMSReceiver" #define FRAME_HEIGHT 12 -#define MAX_LEN_PX 112 +#define TYPE_LEN_PX 60 #define MENU_ITEMS 4u #define UNLOCK_CNT 3 #define SUBGHZ_RAW_THRESHOLD_MIN -90.0f typedef struct { - FuriString* item_str; + FuriString* type_label; + FuriString* id_label; uint8_t type; } TPMSReceiverMenuItem; @@ -137,7 +138,8 @@ static void tpms_view_receiver_update_offset(TPMSReceiver* tpms_receiver) { void tpms_view_receiver_add_item_to_menu( TPMSReceiver* tpms_receiver, - const char* name, + const char* type_label, + const char* id_label, uint8_t type) { furi_assert(tpms_receiver); with_view_model( @@ -146,7 +148,8 @@ void tpms_view_receiver_add_item_to_menu( { TPMSReceiverMenuItem* item_menu = TPMSReceiverMenuItemArray_push_raw(model->history->data); - item_menu->item_str = furi_string_alloc_set(name); + item_menu->type_label = furi_string_alloc_set(type_label); + item_menu->id_label = furi_string_alloc_set(id_label); item_menu->type = type; if((model->idx == model->history_item - 1)) { model->history_item++; @@ -210,29 +213,31 @@ void tpms_view_receiver_draw(Canvas* canvas, TPMSReceiverModel* model) { elements_button_left(canvas, "Config"); bool scrollbar = model->history_item > 4; - FuriString* str_buff; - str_buff = furi_string_alloc(); - TPMSReceiverMenuItem* item_menu; for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); item_menu = TPMSReceiverMenuItemArray_get(model->history->data, idx); - furi_string_set(str_buff, item_menu->item_str); - elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); if(model->idx == idx) { tpms_view_receiver_draw_frame(canvas, i, scrollbar); } else { canvas_set_color(canvas, ColorBlack); } - // canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); - canvas_draw_str(canvas, 4, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); - furi_string_reset(str_buff); + + FuriString* type_str = furi_string_alloc_set(item_menu->type_label); + elements_string_fit_width(canvas, type_str, TYPE_LEN_PX); + canvas_draw_str(canvas, 4, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(type_str)); + furi_string_free(type_str); + + const char* id_str = furi_string_get_cstr(item_menu->id_label); + uint16_t id_width = canvas_string_width(canvas, id_str); + uint16_t right_edge = scrollbar ? 118 : 123; + uint16_t id_x = (id_width < right_edge) ? (right_edge - id_width) : 64; + canvas_draw_str(canvas, id_x, 9 + i * FRAME_HEIGHT, id_str); } if(scrollbar) { elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); } - furi_string_free(str_buff); canvas_set_color(canvas, ColorBlack); @@ -409,7 +414,8 @@ void tpms_view_receiver_exit(void* context) { furi_string_reset(model->history_stat_str); for M_EACH(item_menu, model->history->data, TPMSReceiverMenuItemArray_t) { - furi_string_free(item_menu->item_str); + furi_string_free(item_menu->type_label); + furi_string_free(item_menu->id_label); item_menu->type = 0; } TPMSReceiverMenuItemArray_reset(model->history->data); @@ -469,7 +475,8 @@ void tpms_view_receiver_free(TPMSReceiver* tpms_receiver) { furi_string_free(model->history_stat_str); for M_EACH(item_menu, model->history->data, TPMSReceiverMenuItemArray_t) { - furi_string_free(item_menu->item_str); + furi_string_free(item_menu->type_label); + furi_string_free(item_menu->id_label); item_menu->type = 0; } TPMSReceiverMenuItemArray_clear(model->history->data); diff --git a/views/tpms_receiver.h b/views/tpms_receiver.h index c93d73c..8a01a35 100644 --- a/views/tpms_receiver.h +++ b/views/tpms_receiver.h @@ -32,7 +32,8 @@ void tpms_view_receiver_add_data_statusbar( void tpms_view_receiver_add_item_to_menu( TPMSReceiver* tpms_receiver, - const char* name, + const char* type_label, + const char* id_label, uint8_t type); uint16_t tpms_view_receiver_get_idx_menu(TPMSReceiver* tpms_receiver);