From c9994de7262cded2b76a6b28cfd4974c5addc703 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:31:33 +0300 Subject: [PATCH] nice o code add manually support --- CHANGELOG.md | 3 +- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../subghz/helpers/subghz_feature_plugin.h | 2 +- .../main/subghz/helpers/subghz_gen_info.h | 2 + .../plugins/add_manually/subghz_gen_info.c | 13 ++ .../add_manually/subghz_scene_set_counter.c | 10 +- .../add_manually/subghz_scene_set_seed.c | 41 +++++- .../add_manually/subghz_scene_set_type.c | 7 +- .../subghz_txrx_create_protocol_key.c | 19 ++- .../subghz_txrx_create_protocol_key.h | 4 +- applications/system/nice_o_code/nice_o_code.c | 50 ++++++- documentation/SubGHzRemoteProg.md | 28 +++- lib/subghz/protocols/nice_flor_s.c | 124 ++++++++++++++---- lib/subghz/protocols/public_api.h | 42 ++++++ targets/f7/api_symbols.csv | 4 +- 15 files changed, 290 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc036c3b2..af0443626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## Main changes -- Current API: 88.17 +- Current API: 88.18 - iButton: **Choose which blanks a write may target** - new Settings -> Write Blanks list in the iButton app, where RW1990.1, RW1990.2, TM2004 and TM01x can each be switched on or off; a write then only tries the enabled ones instead of every blank type in turn, and the write screen names the type it is currently attempting rather than showing an unlabelled "Writing ID" throughout. Each attempt that is not the blank in front of you costs real time (RW1990.1/.2 about 900 ms each, TM01x about 335 ms), so narrowing the list is the difference between a write that lands right away and one that walks the whole sequence first. Writing with nothing enabled, or with nothing enabled that the loaded key can actually be written to, says so instead of blinking forever. The choice is saved and is honoured by the ibutton CLI as well (by @mishamyte | PR #1153 | Closes #1152) - RFID: **Add Manually by facility code and card number** - picking a format under Add Manually now asks whether to enter the card as FC/ID or as hex. FC/ID takes the numbers printed on the card, one number input per field, and builds the data itself: EM4100 (all clocks), HID H10301 and the HID formats below, ioProx XSF (with its version), AWID and Pyramid 26-bit, and Gallagher (with region code and issue level). Formats without such a layout go straight to hex as before (by @matthewb-worthe | PR #1149) - RFID: **HID H10302, H10304, H10306, AMAG S10401 and Corporate 1000 35-bit now read** - a Generic HIDProx card of one of these lengths whose parity checks out shows its FC and card number on the read and saved-card screens, one line per format the frame fits (a 37-bit card can fit more than one). The formats are listed under Add Manually too, entered by FC/ID and saved as HIDProx. The "Data:" line of HIDProx cards is gone: it repeated the hex shown above it and lost bits after the first digit on 37-, 35- and 26-bit frames (by @matthewb-worthe | PR #1149) @@ -13,6 +13,7 @@ - SubGHz: **Nord ICE remotes send the full pair, and their other buttons** - a press puts two frames on air that differ in one bit, and only one of them was ever resent; both are rebuilt from a single key, so files saved by an earlier build transmit the pair unchanged, and the bit and gap timings now match the remote. The arrows on the send screen reach the remote's other three buttons, the button editor works, and saving keeps whichever button was sent. **Nord ICE 433MHz** is in Add Manually too, generated on button 4 - SubGHz: **HomeGate remotes now work** - new KeeLoq manufacturer key, so these remotes read and send properly. **HomeGate 433MHz** is in Add Manually too - SubGHz: **Prastel 433MHz** is new in Add Manually, generated on button 1. The arrows on the send screen now reach the remote's other three buttons and the button editor works - **the button map is unverified**, it follows the other four button remotes +- SubGHz: **Nice O-Code 433MHz** is new in Add Manually - Advanced asks for the installer code (Enter IC) so a remote can be made for a gate you already have. The `Nice O-Code` app, which recovers that code from a remote, now has **New** next to Save: it writes a second remote with the same code and button but its own serial and a fresh counter, so it works alongside your original instead of fighting it - SubGHz: **New app - Seed Capturer** - collects the Fix + Hop pairs an offline seed recovery needs from a FAAC SLH, Genius or Erreka remote, under Apps -> Sub-GHz. Pick the manufacturer and 433.92 or 868.35 MHz AM650, then press the remote's button over and over: it locks onto the first Fix it hears and keeps every distinct Hop under it, up to 10. Two hops are enough to save, more narrow the search. Captures go to /ext/apps_data/subghz_seed_captures for the qUnleashed app to recover the seed from - Main menu: **Two new menu styles - Macintosh and 3D** - pick them in Settings -> Desktop -> Menu Style. Macintosh is a classic Mac OS desktop - title bar with the Flipper's name, icon grid, paged with arrows; 3D is a carousel that flies the icons along a path with the selected one zoomed in front. Both are reimplementations of MIT-licensed Arduboy/Arduino originals: Ardutosh by [@jhhoward](https://github.com/jhhoward/Ardutosh) and the 3D OLED menu by [@upiir](https://github.com/upiir/arduino_3d_menu_oled) (by @apfxtech | PR #1155) - SubGHz: **New protocol Doorbell32** - unbranded 433MHz doorbells. KeyFinder, KeyFinder2 and Doorbell32 are all in the Sensors ignore group diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 404092aa0..d9bc7ea74 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -138,6 +138,7 @@ typedef enum { SetTypeNiceSmilo_433_92, SetTypeNiceFlorS_433_92, SetTypeNiceOne_433_92, + SetTypeNiceOCode_433_92, SetTypeNiceFlo12bit, SetTypeNiceFlo24bit, SetTypeCAME12bit, diff --git a/applications/main/subghz/helpers/subghz_feature_plugin.h b/applications/main/subghz/helpers/subghz_feature_plugin.h index 6860eb4f4..9efc0a083 100644 --- a/applications/main/subghz/helpers/subghz_feature_plugin.h +++ b/applications/main/subghz/helpers/subghz_feature_plugin.h @@ -23,7 +23,7 @@ extern "C" { * the signature of an exported symbol - the API hash covers the name only, so a changed signature * still resolves and then miscalls. Dropping an export needs no bump; that one fails to resolve. */ -#define SUBGHZ_FEATURE_PLUGIN_API_VERSION 2 +#define SUBGHZ_FEATURE_PLUGIN_API_VERSION 3 /** Where the feature plugins live - fbt's default folder for this app's plugins, shared with the * radio device drivers, whose registry only loads radio_device_*.fal and skips the rest. */ diff --git a/applications/main/subghz/helpers/subghz_gen_info.h b/applications/main/subghz/helpers/subghz_gen_info.h index 27232d83a..b0062f279 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.h +++ b/applications/main/subghz/helpers/subghz_gen_info.h @@ -91,7 +91,9 @@ typedef struct { uint32_t serial; uint8_t btn; uint16_t cnt; + uint16_t ic; bool nice_one; + bool o_code; } nice_flor_s; struct { uint32_t serial; diff --git a/applications/main/subghz/plugins/add_manually/subghz_gen_info.c b/applications/main/subghz/plugins/add_manually/subghz_gen_info.c index 85c612aa7..546cc40e0 100644 --- a/applications/main/subghz/plugins/add_manually/subghz_gen_info.c +++ b/applications/main/subghz/plugins/add_manually/subghz_gen_info.c @@ -719,6 +719,19 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .nice_flor_s.cnt = 0x03, .nice_flor_s.nice_one = true}; break; + case SetTypeNiceOCode_433_92: + gen_info = (GenInfo){ + .type = GenNiceFlorS, + .mod = "AM650", + .freq = 433920000, + .nice_flor_s.serial = key & 0x0FFFFFFF, + .nice_flor_s.btn = 0x01, + .nice_flor_s.cnt = 0x03, + // a made up installer code makes a remote of its own system; to join a gate + // that is already installed, enter its code under Add Manually -> Advanced + .nice_flor_s.ic = (uint16_t)rand(), + .nice_flor_s.o_code = true}; + break; case SetTypeNiceSmilo_433_92: gen_info = (GenInfo){ .type = GenKeeloq, diff --git a/applications/main/subghz/plugins/add_manually/subghz_scene_set_counter.c b/applications/main/subghz/plugins/add_manually/subghz_scene_set_counter.c index 5d4577243..c238c4f49 100644 --- a/applications/main/subghz/plugins/add_manually/subghz_scene_set_counter.c +++ b/applications/main/subghz/plugins/add_manually/subghz_scene_set_counter.c @@ -260,6 +260,12 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) { subghz->gen_info->ditec_gol4.cnt); break; case GenNiceFlorS: + // O-Code has one more per-remote value, the installer code, and the seed + // scene is where a protocol asks for its extra field + if(subghz->gen_info->nice_flor_s.o_code) { + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetSeed); + return true; + } generated_protocol = subghz_txrx_gen_nice_flor_s_protocol( subghz->txrx, subghz->gen_info->mod, @@ -267,7 +273,9 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) { subghz->gen_info->nice_flor_s.serial, subghz->gen_info->nice_flor_s.btn, subghz->gen_info->nice_flor_s.cnt, - subghz->gen_info->nice_flor_s.nice_one); + subghz->gen_info->nice_flor_s.nice_one, + false, + 0); break; case GenSecPlus2: generated_protocol = subghz_txrx_gen_secplus_v2_protocol( diff --git a/applications/main/subghz/plugins/add_manually/subghz_scene_set_seed.c b/applications/main/subghz/plugins/add_manually/subghz_scene_set_seed.c index 8cdf6082c..dd9c4a8f1 100644 --- a/applications/main/subghz/plugins/add_manually/subghz_scene_set_seed.c +++ b/applications/main/subghz/plugins/add_manually/subghz_scene_set_seed.c @@ -26,6 +26,11 @@ void subghz_scene_set_seed_on_enter(void* context) { byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_seed.seed; byte_count = sizeof(subghz->gen_info->keeloq_seed.seed); break; + case GenNiceFlorS: + // Nice O-Code only - plain Flor-S and Nice One never reach this scene + byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.ic; + byte_count = sizeof(subghz->gen_info->nice_flor_s.ic); + break; // Not needed for these types case GenKeeloq: case GenAlutechAt4n: @@ -35,7 +40,6 @@ void subghz_scene_set_seed_on_enter(void* context) { case GenBenincaARC: case GenJarolift: case GenDitecGOL4: - case GenNiceFlorS: case GenSecPlus2: case GenPhoenixV2: case GenPrastel: @@ -50,14 +54,25 @@ void subghz_scene_set_seed_on_enter(void* context) { furi_assert(byte_ptr); furi_assert(byte_count > 0); - *((uint32_t*)byte_ptr) = __bswap32(*((uint32_t*)byte_ptr)); // Convert + if(byte_count == 2) { + *((uint16_t*)byte_ptr) = __bswap16(*((uint16_t*)byte_ptr)); // Convert + } else { + *((uint32_t*)byte_ptr) = __bswap32(*((uint32_t*)byte_ptr)); // Convert + } // Setup view ByteInput* byte_input = subghz->byte_input; - // Monarch reuses seed for its hop discriminator, so ask for it by the right name - bool is_disc = (subghz->gen_info->type == GenKeeloqSeed) && - (strcmp(subghz->gen_info->keeloq_seed.manuf, "Monarch") == 0); - byte_input_set_header_text(byte_input, is_disc ? "Enter DISC. in hex" : "Enter SEED in hex"); + // Monarch reuses seed for its hop discriminator and O-Code asks for its installer + // code here, so ask for each by the right name + const char* header = "Enter SEED in hex"; + if(subghz->gen_info->type == GenNiceFlorS) { + header = "Enter IC in hex"; + } else if( + (subghz->gen_info->type == GenKeeloqSeed) && + (strcmp(subghz->gen_info->keeloq_seed.manuf, "Monarch") == 0)) { + header = "Enter DISC. in hex"; + } + byte_input_set_header_text(byte_input, header); byte_input_set_result_callback( byte_input, subghz_scene_set_seed_byte_input_callback, NULL, subghz, byte_ptr, byte_count); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); @@ -94,6 +109,19 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) { subghz->gen_info->keeloq_seed.seed, subghz->gen_info->keeloq_seed.manuf); break; + case GenNiceFlorS: + subghz->gen_info->nice_flor_s.ic = __bswap16(subghz->gen_info->nice_flor_s.ic); + generated_protocol = subghz_txrx_gen_nice_flor_s_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->nice_flor_s.serial, + subghz->gen_info->nice_flor_s.btn, + subghz->gen_info->nice_flor_s.cnt, + subghz->gen_info->nice_flor_s.nice_one, + subghz->gen_info->nice_flor_s.o_code, + subghz->gen_info->nice_flor_s.ic); + break; // Not needed for these types case GenKeeloq: case GenAlutechAt4n: @@ -103,7 +131,6 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) { case GenBenincaARC: case GenJarolift: case GenDitecGOL4: - case GenNiceFlorS: case GenSecPlus2: case GenPhoenixV2: case GenPrastel: diff --git a/applications/main/subghz/plugins/add_manually/subghz_scene_set_type.c b/applications/main/subghz/plugins/add_manually/subghz_scene_set_type.c index 64066256d..cc0eadc48 100644 --- a/applications/main/subghz/plugins/add_manually/subghz_scene_set_type.c +++ b/applications/main/subghz/plugins/add_manually/subghz_scene_set_type.c @@ -84,6 +84,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeNiceSmilo_433_92] = "KL: Nice Smilo 433MHz", [SetTypeNiceFlorS_433_92] = "Nice FloR-S 433MHz", [SetTypeNiceOne_433_92] = "Nice One 433MHz", + [SetTypeNiceOCode_433_92] = "Nice O-Code 433MHz", [SetTypeNiceFlo12bit] = "Nice Flo 12bit 433MHz", [SetTypeNiceFlo24bit] = "Nice Flo 24bit 433MHz", [SetTypeCAME12bit] = "CAME 12bit 433MHz", @@ -268,7 +269,9 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) { gen_info.nice_flor_s.serial, gen_info.nice_flor_s.btn, gen_info.nice_flor_s.cnt, - gen_info.nice_flor_s.nice_one); + gen_info.nice_flor_s.nice_one, + gen_info.nice_flor_s.o_code, + gen_info.nice_flor_s.ic); break; case GenSecPlus1: generated_protocol = @@ -353,7 +356,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { case GenBenincaARC: // Serial (u32), Button (u8), Counter (u32) case GenJarolift: // Serial (u32), Button (u4), Counter (u16) case GenDitecGOL4: // Serial (u32), Button (u4), Counter (u16) - case GenNiceFlorS: // Serial (u32), Button (u8), Counter (u16) + case GenNiceFlorS: // Serial (u32), Button (u8), Counter (u16), IC (u16, O-Code) case GenSecPlus2: // Serial (u32), Button (u8), Counter (u32) case GenPhoenixV2: // Serial (u32), Counter (u16) case GenPrastel: // Serial (u32), Button (u8), Counter (u16) diff --git a/applications/main/subghz/plugins/add_manually/subghz_txrx_create_protocol_key.c b/applications/main/subghz/plugins/add_manually/subghz_txrx_create_protocol_key.c index da7c66116..095dd7da9 100644 --- a/applications/main/subghz/plugins/add_manually/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/plugins/add_manually/subghz_txrx_create_protocol_key.c @@ -177,7 +177,9 @@ bool subghz_txrx_gen_nice_flor_s_protocol( uint32_t serial, uint8_t btn, uint16_t cnt, - bool nice_one) { + bool nice_one, + bool o_code, + uint16_t ic) { SubGhzTxRx* txrx = context; bool res = false; @@ -186,15 +188,12 @@ bool subghz_txrx_gen_nice_flor_s_protocol( subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME); subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0); - if(txrx->transmitter && subghz_protocol_nice_flor_s_create_data( - subghz_transmitter_get_protocol_instance(txrx->transmitter), - txrx->fff_data, - serial, - btn, - cnt, - txrx->preset, - nice_one)) { - res = true; + if(txrx->transmitter) { + void* protocol = subghz_transmitter_get_protocol_instance(txrx->transmitter); + res = o_code ? subghz_protocol_nice_o_create_data( + protocol, txrx->fff_data, serial, btn, cnt, ic, txrx->preset) : + subghz_protocol_nice_flor_s_create_data( + protocol, txrx->fff_data, serial, btn, cnt, txrx->preset, nice_one); } subghz_transmitter_free(txrx->transmitter); diff --git a/applications/main/subghz/plugins/add_manually/subghz_txrx_create_protocol_key.h b/applications/main/subghz/plugins/add_manually/subghz_txrx_create_protocol_key.h index 4544ea646..a47917c72 100644 --- a/applications/main/subghz/plugins/add_manually/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/plugins/add_manually/subghz_txrx_create_protocol_key.h @@ -80,7 +80,9 @@ bool subghz_txrx_gen_nice_flor_s_protocol( uint32_t serial, uint8_t btn, uint16_t cnt, - bool nice_one); + bool nice_one, + bool o_code, + uint16_t ic); bool subghz_txrx_gen_faac_slh_protocol( void* context, diff --git a/applications/system/nice_o_code/nice_o_code.c b/applications/system/nice_o_code/nice_o_code.c index 0fbde0234..912d4d35e 100644 --- a/applications/system/nice_o_code/nice_o_code.c +++ b/applications/system/nice_o_code/nice_o_code.c @@ -7,6 +7,10 @@ * same remote are: only the correct installer code makes every parcel decode to the same * serial. This app listens for parcels, brute forces the code, and writes a .sub with * the recovered "IC" key so the sub-ghz app can decode and replay that remote. + * + * It can also write a .sub for a remote that does not exist yet: the same installer code + * and button with a serial of its own, which the gate accepts once it is paired the way a + * new remote is. */ #include @@ -42,6 +46,11 @@ #define NICE_O_MIN_KEYS 4 #define NICE_O_FREQUENCY 433920000 +/* What a brand new remote looks like: a counter near zero, and the parcel index Add + * Manually writes, so a generated file reads back like any single captured frame. */ +#define NICE_O_NEW_CNT 0x0003 +#define NICE_O_NEW_PARCEL 0x3 + #define NICE_O_SWEEP_TOTAL 0x10000u /* candidates per main loop pass: small enough that the screen keeps redrawing, * large enough that the whole sweep is over in well under a second */ @@ -166,6 +175,7 @@ static void nice_o_draw_callback(Canvas* canvas, void* ctx) { snprintf(buf, sizeof(buf), "Serial %07lX", app->serial); canvas_draw_str(canvas, 6, 42, buf); elements_button_center(canvas, "Save"); + elements_button_right(canvas, "New"); } else { elements_multiline_text_aligned( canvas, 64, 32, AlignCenter, AlignCenter, furi_string_get_cstr(app->status)); @@ -454,9 +464,10 @@ static void nice_o_set_status(NiceOCode* app, const char* text) { furi_mutex_release(app->mutex); } -static bool nice_o_save(NiceOCode* app) { +/* Writes one .sub holding @p key and the recovered installer code. */ +static bool nice_o_save_key(NiceOCode* app, uint64_t key, const char* prefix) { char name[NICE_O_NAME_LEN]; - name_generator_make_random_prefixed(name, sizeof(name), "NiceO"); + name_generator_make_random_prefixed(name, sizeof(name), prefix); if(!nice_o_ask_name(app, name, sizeof(name))) { nice_o_set_status(app, "Save cancelled"); return false; @@ -481,7 +492,7 @@ static bool nice_o_save(NiceOCode* app) { uint8_t key_data[8]; for(size_t i = 0; i < 8; i++) { - key_data[7 - i] = (app->keys[0] >> (i * 8)) & 0xFF; + key_data[7 - i] = (key >> (i * 8)) & 0xFF; } if(!flipper_format_write_hex(fff, "Key", key_data, sizeof(key_data))) break; @@ -498,6 +509,32 @@ static bool nice_o_save(NiceOCode* app) { return ok; } +static bool nice_o_save(NiceOCode* app) { + return nice_o_save_key(app, app->keys[0], "NiceO"); +} + +/* + * A second remote for the same gate. The installer code is what the receiver is paired + * to, so it stays, and so does the button; what makes it a remote of its own is a serial + * of its own, and a new remote leaves the factory with its counter near zero. The gate + * still has to be taught it, the same as any remote bought over the counter. + */ +static bool nice_o_save_new(NiceOCode* app) { + uint32_t serial = app->serial; + while(serial == app->serial) { + serial = furi_hal_random_get() & 0x0FFFFFFFu; + } + + const uint8_t btn = (uint8_t)((app->keys[0] >> 48) & 0x0Fu); + const uint64_t key = subghz_protocol_nice_o_make_key( + serial, NICE_O_NEW_CNT, btn, NICE_O_NEW_PARCEL, app->ic, NICE_O_KEYSTORE); + if(key == 0) { + nice_o_set_status(app, "No rainbow table"); + return false; + } + return nice_o_save_key(app, key, "NiceO_new"); +} + /* ---------------------------------------------------------------------- main */ int32_t nice_o_code_app(void* p) { @@ -597,9 +634,12 @@ int32_t nice_o_code_app(void* p) { case NiceOSceneResult: if((event.key == InputKeyBack) || (event.key == InputKeyLeft)) { app->scene = NiceOSceneMenu; - } else if((event.key == InputKeyOk) && app->solved) { + } else if( + ((event.key == InputKeyOk) || (event.key == InputKeyRight)) && + app->solved) { + const bool as_new = (event.key == InputKeyRight); furi_mutex_release(app->mutex); - const bool saved = nice_o_save(app); + const bool saved = as_new ? nice_o_save_new(app) : nice_o_save(app); furi_mutex_acquire(app->mutex, FuriWaitForever); notification_message( app->notifications, saved ? &sequence_success : &sequence_error); diff --git a/documentation/SubGHzRemoteProg.md b/documentation/SubGHzRemoteProg.md index ecda9a289..8e99cf88e 100644 --- a/documentation/SubGHzRemoteProg.md +++ b/documentation/SubGHzRemoteProg.md @@ -52,9 +52,10 @@ Which arrow is which depends on the protocol. Where a receiver wants a *hidden* | [DoorHan](#doorhan) | [Erreka IRIS](#erreka-iris-new) | [FAAC RC, XT](#faac-rc-xt) | | [FAAC SLH](#faac-slh) | [Genius (SLH)](#genius-slh) | [Genius TX4RC](#genius-tx4rc-bravo--echo) | | [Hormann EcoStar](#hormann-ecostar) | [Jarolift](#jarolift) | [KingGates Stylo 4k](#kinggates-stylo-4k) | -| [Mhouse](#mhouse) | [Nice Flor S](#nice-flor-s) | [Nice One](#nice-one) | -| [Nice Smilo](#nice-smilo) | [Security+ 1.0 / 2.0](#security-10--20-chamberlain-liftmaster-craftsman) | [Somfy Keytis](#somfy-keytis) | -| [Somfy Telis](#somfy-telis) | [Sommer](#sommer) | [V2 Phoenix (Phox)](#v2-phoenix-phox) | +| [Mhouse](#mhouse) | [Nice Flor S](#nice-flor-s) | [Nice O-Code](#nice-o-code) | +| [Nice One](#nice-one) | [Nice Smilo](#nice-smilo) | [Security+ 1.0 / 2.0](#security-10--20-chamberlain-liftmaster-craftsman) | +| [Somfy Keytis](#somfy-keytis) | [Somfy Telis](#somfy-telis) | [Sommer](#sommer) | +| [V2 Phoenix (Phox)](#v2-phoenix-phox) | | | Not listed? See [Programming / hidden button reference](#programming--hidden-button-reference) - it covers every other entry in the `Add Manually` menu. @@ -651,7 +652,24 @@ Nice One is the Nice FloR-S family with different keying, and the receiver side 2. Follow the [Nice Flor S](#nice-flor-s) steps > [!TIP] -> If your remote reads as FloR-S but the counter looks wrong, it may be **Nice O-Code** instead - that is FloR-S keyed with a 16 bit installer code. Use the `Nice O-Code` app (`Apps` -> `Sub-GHz`), it recovers the key from 4 or more captures. +> If your remote reads as FloR-S but the counter looks wrong, it may be **Nice O-Code** instead - see below. + +--- + +## Nice O-Code + +- **Create it:** `SubGHz` -> `Add Manually` -> `Nice O-Code 433MHz` +- **Create it on an installed system:** `SubGHz` -> `Add Manually` -> `Advanced` -> `Nice O-Code 433MHz`, which asks for the installer code as well + +O-Code is Nice FloR-S keyed with a 16 bit **installer code (IC)**, one value shared by every remote of an installation. The receiver side is identical to FloR-S, so **[both Nice Flor S procedures](#nice-flor-s) apply exactly as written** - but a remote only reaches a receiver that was set up with the same installer code. + +That leaves two ways round: + +- **For a system of your own**, the plain `Nice O-Code 433MHz` entry makes up an installer code along with the serial, and you register the remote with the receiver button like any other first remote. +- **For a system that is already installed**, the remote has to carry that installation's installer code. Recover it from a remote that already works with the **`Nice O-Code` app** (`Apps` -> `Sub-GHz`): press the remote's button 4 or more times, do not hold it, and the app brute forces the code out of the captures. Then either + - press **Right (`New`)** on the app's result screen, which writes a remote with that same installer code and button but a serial and counter of its own - the one to register with the receiver, and + - press `Save` for a plain clone of the remote you captured, useful for testing but it fights your original over the counter, or + - enter the code by hand under `Add Manually` -> `Advanced` if you already know it. --- @@ -924,7 +942,7 @@ Lots of receivers are programmed by pressing a button the original remote does n | `Somfy Telis 433MHz` | `0x8` | **Left Arrow** | `Prog` button | | `Somfy Keytis 433MHz` | `0x3` | **Up Arrow** | `Prog` pads on the back of the board | | `V2 Phoenix 433MHz` | `0x3` | Right Arrow | buttons 1+2 or 1+3 held *(unconfirmed)* | -| `Nice FloR-S` / `Nice One 433MHz` | `0x3` | Right Arrow | button 3 *(unconfirmed as a programming button)* | +| `Nice FloR-S` / `Nice One` / `Nice O-Code 433MHz` | `0x3` | Right Arrow | button 3 *(unconfirmed as a programming button)* | ### Everything else in the menu diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index e485d9e6f..038e38b32 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -33,14 +33,17 @@ static const uint8_t* subghz_protocol_nice_flor_s_table(const char* file_name); static uint8_t nice_flors_counter_mode = 0; -#define NICE_ONE_COUNT_BIT 72 -#define NICE_ONE_NAME "Nice One" -#define NICE_O_NAME "Nice O-Code" +#define NICE_ONE_COUNT_BIT 72 +#define NICE_ONE_NAME "Nice One" +#define NICE_O_NAME "Nice O-Code" /* Nice O-Code: the final permutation of the Flor-S cipher is XORed with a per-remote * 16 bit installer code instead of being inverted. IC == 0xFFFF is plain Nice Flor-S. */ -#define NICE_FLOR_S_IC_PLAIN 0xFFFFu -#define NICE_O_MIN_SAMPLES 4 -#define NICE_O_MAX_SAMPLES 16 +#define NICE_FLOR_S_IC_PLAIN 0xFFFFu +#define NICE_O_MIN_SAMPLES 4 +#define NICE_O_MAX_SAMPLES 16 +/* The parcel index a generated remote's stored frame carries. The encoder sends all 16 + * anyway, so this only decides which one the saved file looks like a capture of. */ +#define NICE_FLOR_S_CREATE_PARCEL 0x3 //variable used to bypass CounterMode settings if user just change Counter or Button static bool bypass = false; @@ -249,23 +252,20 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( static const uint64_t loops[16] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}; - uint8_t byte; - if(o_code) { // both the serial mask and the cipher key change with the parcel index - uint32_t mask_32 = 0; - uint16_t mask_16 = 0; - subghz_protocol_nice_o_mask((uint8_t)loops[i], &mask_32, &mask_16); - uint64_t plain = - ((uint64_t)((instance->generic.serial ^ mask_32) & 0x0FFFFFFFu) << 16) | - instance->generic.cnt; - enc_part = subghz_protocol_nice_flor_s_encrypt_ic( - plain, (uint16_t)(instance->ic ^ mask_16), file_name); + instance->generic.data = subghz_protocol_nice_o_make_key( + instance->generic.serial, + instance->generic.cnt, + btn, + (uint8_t)loops[i], + instance->ic, + file_name); + } else { + uint8_t byte = btn << 4 | (0xF ^ btn ^ loops[i]); + instance->generic.data = (uint64_t)byte << 44 | enc_part; } - byte = btn << 4 | (0xF ^ btn ^ loops[i]); - instance->generic.data = (uint64_t)byte << 44 | enc_part; - //Send header instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_nice_flor_s_const.te_short * 37); @@ -670,28 +670,68 @@ static bool subghz_protocol_nice_o_decrypt( return true; } -bool subghz_protocol_nice_flor_s_create_data( +uint64_t subghz_protocol_nice_o_make_key( + uint32_t serial, + uint16_t cnt, + uint8_t btn, + uint8_t parcel, + uint16_t ic, + const char* file_name) { + parcel &= 0x0Fu; + uint32_t mask_32 = 0; + uint16_t mask_16 = 0; + subghz_protocol_nice_o_mask(parcel, &mask_32, &mask_16); + + const uint64_t plain = ((uint64_t)((serial ^ mask_32) & 0x0FFFFFFFu) << 16) | cnt; + const uint64_t enc_part = + subghz_protocol_nice_flor_s_encrypt_ic(plain, (uint16_t)(ic ^ mask_16), file_name); + if(enc_part == SUBGHZ_NO_NICE_FLOR_S_RAINBOW_TABLE) return 0; + + const uint8_t byte = (uint8_t)((btn & 0x0Fu) << 4 | (0x0Fu ^ (btn & 0x0Fu) ^ parcel)); + return (uint64_t)byte << 44 | enc_part; +} + +static bool subghz_protocol_nice_flor_s_create_data_ic( void* context, FlipperFormat* flipper_format, uint32_t serial, uint8_t btn, uint16_t cnt, SubGhzRadioPreset* preset, - bool nice_one) { + bool nice_one, + bool o_code, + uint16_t ic) { furi_assert(context); SubGhzProtocolEncoderNiceFlorS* instance = context; instance->generic.serial = serial; instance->generic.cnt = cnt; + /* O-Code carries the installer code in the file instead of the 20 extra Nice One bits, + * so the two flavours are never both on */ + if(o_code) nice_one = false; if(nice_one) { instance->generic.data_count_bit = NICE_ONE_COUNT_BIT; } else { instance->generic.data_count_bit = 52; } - uint64_t decrypt = ((uint64_t)instance->generic.serial << 16) | instance->generic.cnt; - uint64_t enc_part = subghz_protocol_nice_flor_s_encrypt( - decrypt, instance->nice_flor_s_rainbow_table_file_name); - uint8_t byte = btn << 4 | (0xF ^ btn ^ 0x3); - instance->generic.data = (uint64_t)byte << 44 | enc_part; + instance->o_code = o_code; + instance->ic = o_code ? ic : NICE_FLOR_S_IC_PLAIN; + + if(o_code) { + instance->generic.data = subghz_protocol_nice_o_make_key( + serial, + cnt, + btn, + NICE_FLOR_S_CREATE_PARCEL, + ic, + instance->nice_flor_s_rainbow_table_file_name); + if(instance->generic.data == 0) return false; + } else { + uint64_t decrypt = ((uint64_t)instance->generic.serial << 16) | instance->generic.cnt; + uint64_t enc_part = subghz_protocol_nice_flor_s_encrypt( + decrypt, instance->nice_flor_s_rainbow_table_file_name); + uint8_t byte = btn << 4 | (0xF ^ btn ^ NICE_FLOR_S_CREATE_PARCEL); + instance->generic.data = (uint64_t)byte << 44 | enc_part; + } if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { uint8_t add_data[10] = {0}; @@ -708,10 +748,42 @@ bool subghz_protocol_nice_flor_s_create_data( SubGhzProtocolStatus res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if((res == SubGhzProtocolStatusOk) && o_code) { + uint8_t ic_data[2] = {(uint8_t)(ic >> 8), (uint8_t)ic}; + if(!flipper_format_rewind(flipper_format) || + !flipper_format_insert_or_update_hex(flipper_format, "IC", ic_data, sizeof(ic_data))) { + FURI_LOG_E(TAG, "Unable to add IC"); + res = SubGhzProtocolStatusErrorParserOthers; + } + } return res == SubGhzProtocolStatusOk; } +bool subghz_protocol_nice_flor_s_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset, + bool nice_one) { + return subghz_protocol_nice_flor_s_create_data_ic( + context, flipper_format, serial, btn, cnt, preset, nice_one, false, 0); +} + +bool subghz_protocol_nice_o_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + uint16_t ic, + SubGhzRadioPreset* preset) { + return subghz_protocol_nice_flor_s_create_data_ic( + context, flipper_format, serial, btn, cnt, preset, false, true, ic); +} + void* subghz_protocol_decoder_nice_flor_s_alloc(SubGhzEnvironment* environment) { SubGhzProtocolDecoderNiceFlorS* instance = subghz_protocol_decoder_common_alloc( sizeof(SubGhzProtocolDecoderNiceFlorS), &subghz_protocol_nice_flor_s); diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index 6e387b7a4..8cbe2fccc 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -159,6 +159,27 @@ bool subghz_protocol_nice_flor_s_create_data( SubGhzRadioPreset* preset, bool nice_one); +/** + * New Nice O-Code remote generation. Writes the installer code to the file as well, which + * is what tells the decoder an otherwise ordinary looking Flor-S frame is an O-Code one. + * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 28 bit + * @param btn Button number, 4 bit + * @param cnt Counter value, 16 bit + * @param ic Installer code, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_nice_o_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + uint16_t ic, + SubGhzRadioPreset* preset); + /** * Key generation from simple data. * @param context Pointer to a SubGhzProtocolEncoderSomfyTelis instance @@ -319,6 +340,27 @@ void subghz_protocol_nice_o_mask(uint8_t n, uint32_t* mask_32, uint16_t* mask_16 */ uint8_t subghz_protocol_nice_o_get_parcel(uint64_t data); +/** + * Build one Nice O-Code frame, the 52 bit key a .sub file holds. + * Everything a remote is made of goes in here: the same installer code with a different + * serial is a different remote on the same system, which is what cloning a remote onto a + * new one means. + * @param serial Serial number, 28 bit + * @param cnt Counter value, 16 bit + * @param btn Button number, 4 bit + * @param parcel Parcel index, 0..15 - the encoder sends all 16 regardless + * @param ic Installer code, 16 bit + * @param file_name Full path to the rainbow table + * @return the frame, or 0 if the rainbow table could not be read + */ +uint64_t subghz_protocol_nice_o_make_key( + uint32_t serial, + uint16_t cnt, + uint8_t btn, + uint8_t parcel, + uint16_t ic, + const char* file_name); + /** * Choose whether the Nice Flor-S decoder hides O-Code frames. * With this on, a 52 bit frame is reported only once a second frame of the same press diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 50c8fc0fa..ff017eebd 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,88.17,, +Version,+,88.18,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/applications.h,, Header,+,applications/services/bt/bt_service/bt.h,, @@ -3864,7 +3864,9 @@ Function,+,subghz_protocol_kinggates_stylo_4k_create_data,_Bool,"void*, FlipperF Function,+,subghz_protocol_marantec_crc8,uint8_t,"uint8_t*, size_t" Function,+,subghz_protocol_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*, _Bool" Function,+,subghz_protocol_nice_flor_s_decrypt_ic,uint64_t,"uint64_t, uint16_t, const char*" +Function,+,subghz_protocol_nice_o_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_nice_o_get_parcel,uint8_t,uint64_t +Function,+,subghz_protocol_nice_o_make_key,uint64_t,"uint32_t, uint16_t, uint8_t, uint8_t, uint16_t, const char*" Function,+,subghz_protocol_nice_o_mask,void,"uint8_t, uint32_t*, uint16_t*" Function,+,subghz_protocol_phoenix_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_prastel_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*"