From 4e347b207ca8136b69a76d18c1ab01fec13ca2ee Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 27 Dec 2022 10:35:57 +0300 Subject: [PATCH 1/7] Fix amap on forks (#2200) --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ca60aee6..4f7233e46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,12 +87,14 @@ jobs: cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" - name: 'Copy map analyser files' + if: ${{ !github.event.pull_request.head.repo.fork }} run: | cp build/f7-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map cp build/f7-firmware-*/firmware.elf map_analyser_files/firmware.elf cp ${{ github.event_path }} map_analyser_files/event.json - name: 'Upload map analyser files to storage' + if: ${{ !github.event.pull_request.head.repo.fork }} uses: keithweaver/aws-s3-github-action@v1.0.0 with: source: map_analyser_files/ @@ -103,6 +105,7 @@ jobs: flags: --recursive - name: 'Trigger map file reporter' + if: ${{ !github.event.pull_request.head.repo.fork }} uses: peter-evans/repository-dispatch@v2 with: repository: flipperdevices/flipper-map-reporter From 8a279758fd9334adbc18dc1dcaca7485e66ed13d Mon Sep 17 00:00:00 2001 From: Daniel Carvallo Date: Tue, 27 Dec 2022 01:55:25 -0600 Subject: [PATCH 2/7] Fix quoted error for macOS bad-usb (#2155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add stderr redirect to null device * Remove stderr redirect and replace <`> with <'> Co-authored-by: あく --- assets/resources/badusb/demo_macos.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/assets/resources/badusb/demo_macos.txt b/assets/resources/badusb/demo_macos.txt index 6da305905..3c21a4df8 100644 --- a/assets/resources/badusb/demo_macos.txt +++ b/assets/resources/badusb/demo_macos.txt @@ -34,43 +34,43 @@ ENTER STRING _.-------.._ -, ENTER HOME -STRING .-"```"--..,,_/ /`-, -, \ +STRING .-"'''"--..,,_/ /'-, -, \ ENTER HOME -STRING .:" /:/ /'\ \ ,_..., `. | | +STRING .:" /:/ /'\ \ ,_..., '. | | ENTER HOME -STRING / ,----/:/ /`\ _\~`_-"` _; +STRING / ,----/:/ /'\ _\~'_-"' _; ENTER HOME -STRING ' / /`"""'\ \ \.~`_-' ,-"'/ +STRING ' / /'"""'\ \ \.~'_-' ,-"'/ ENTER HOME -STRING | | | 0 | | .-' ,/` / +STRING | | | 0 | | .-' ,/' / ENTER HOME -STRING | ,..\ \ ,.-"` ,/` / +STRING | ,..\ \ ,.-"' ,/' / ENTER HOME -STRING ; : `/`""\` ,/--==,/-----, +STRING ; : '/'""\' ,/--==,/-----, ENTER HOME -STRING | `-...| -.___-Z:_______J...---; +STRING | '-...| -.___-Z:_______J...---; ENTER HOME -STRING : ` _-' +STRING : ' _-' ENTER HOME -STRING _L_ _ ___ ___ ___ ___ ____--"` +STRING _L_ _ ___ ___ ___ ___ ____--"' ENTER HOME -STRING | __|| | |_ _|| _ \| _ \| __|| _ \ +STRING | __|| | |_ _|| _ \| _ \| __|| _ \ ENTER HOME -STRING | _| | |__ | | | _/| _/| _| | / +STRING | _| | |__ | | | _/| _/| _| | / ENTER HOME -STRING |_| |____||___||_| |_| |___||_|_\ +STRING |_| |____||___||_| |_| |___||_|_\ ENTER HOME ENTER From f43b76efc21bd7cb248d7dc14d232fb6b2617a95 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 27 Dec 2022 11:03:56 +0300 Subject: [PATCH 3/7] [FL-3021] USB/BLE HID Remote icon fix (#2179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../hid_app/assets/Ble_disconnected_15x15.png | Bin 3632 -> 657 bytes applications/plugins/hid_app/hid.c | 14 +++++++++----- .../plugins/hid_app/views/hid_keyboard.c | 9 ++++++++- .../plugins/hid_app/views/hid_keynote.c | 15 +++++++++++---- .../plugins/hid_app/views/hid_media.c | 15 +++++++++++---- .../plugins/hid_app/views/hid_mouse.c | 15 +++++++++++---- .../plugins/hid_app/views/hid_mouse_jiggler.c | 18 ++++++++++++++---- .../plugins/hid_app/views/hid_tiktok.c | 15 +++++++++++---- 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/applications/plugins/hid_app/assets/Ble_disconnected_15x15.png b/applications/plugins/hid_app/assets/Ble_disconnected_15x15.png index 0858bb93f43d7cfc81e3f6f3b7540bd1a5e17ebf..bd54646d891976d838ecdf1acb1d05ee1f3ba40c 100644 GIT binary patch delta 392 zcmdlWGm&+IvK<3kfKQ0)|2hUBV0ZLc3#5utBRtc5eHpZXYz_t{MneWBAWIR5)!H)| zSb#Jm5PJYI!{kIB1rs36ynvB`8AvmNq!uv274a`%MzBE|T2IA$1C^Cbp3Nma`3;wj zaD;Dwf^&XRs)CuGfu4bq9hZW_ zN&}yC+C4=^_C}p+7pp(c*RPG+WFRMY!+)9eNuJG762JFz%im^jo_*=zKTt?`y85}S Ib4q9e01jJVn*aa+ delta 3367 zcmZ{iXH?T!x5ob&klv(<2os7lAtCe0D?cPPS0dvV^Gdq3PYXPpn{JZt~Xe)c{)^-T4J6iF(Z5dn$3Y=%UF zeaR$G!Yv#C1WwZI@o4*1LEXLC9fU<(_(Kblod65i4v`SfpDHac&dOsRCtffiVB2!m z$cRy-D?g4QDJeWrz*d3dKHE6+lK7LPsHbuFdk*G9o_dcq?tSSyel@1IQn5{|9bj%| z;m9ymu~iG_C`E{!rGAd;Z10#~f*3@wN%;aiEVUlu{zsA^U_T5Fza-Jj{1yNO4zsfW z%~rY1N*7{JxW6H?uYtnvfto3)nieeK{D47l5~2t&Fb0LwvZU>Sd`7^v*WGOqxU2}c z%22-y1L3s&><|#({7_r~l%EQK`EYS4z)J_H7`SquY6M`E0De!4b}e9B5l}L7^fU#k z>wxBd9`+i5g#%Esjf<887()QpE=kEi;9fSsZ@lFQ|Km~>&x-tMud-|4^-@|!5zZ`% ze$0-J5HZ<)GoefT>eyefIR=o{plo5~2vzRS+aCZRKULuD=cw(Yz<#c({(ki&I@b+F z>bG}HClYRMi^t0Y<-P_0Fi!~^JXV0#o)6by3MU>vxwygTb)7x`_FjT#4Y$E7Ab)kz zaohK2cZ>>B>L(_amzKuO+Y!##9>>sQeCv&7$D><+hQSZ_c0a#ekcv=ti7;W@|J?dv z$FlIkzysEMZqr|qO%ChWPYy+gCECrXZuJ;>{zY3}za&&n;c0RDOhfwV}vP%Q=N+rVA}+rd!w#UDfX)q}p2%JX8dmNB9M@ zt6W7SLN=zaT2$T@`pH04wb|AyCe4=%((3S+^g%2w}83%H^ zkC9Ab5z3C<7jH3Tn7^4`Uil zU=Jf|Y;`|zyh7HOXq>-iOnt)s(SriZNfyk{{YZX!UN=#_VC7@E7x!ec9!mFluI1XAD4qv0{d%7)y-x zxM>Oaf|TmnNmdP3hI_WHG7@Pe7B8jWoc*)?oSQN%7GtU)Im(;N2c`0a#39=?qET_&to2Xa=^&b_zPHr8*_CW%v>jTA%tX%;_ke$*Ik<%?A0* zHvh$q#l{XWySs30pn|wJXL-!+2(@w1=$4fhXdzY1RUB*WwjX-x zGcRWO?LVSev#!V%XKshxpXJ|_TaXheQnjpy;jVd?Wn^Yx%1z5{%T3mr)T`EuX+=#& z4NeCUT!x+IGS{dwCivw3Gqe@^3HzC1wqijmUG3c}NSSw`NI?TC;GIwLMpoY2>*0Bp zq~h72rj;KTLZL_KBjS(ZT@wxrQ!R%S(-cSlIrw`uKN4IN+Bx-yEz+gU09lAl$7aso zORXP-KOHgt&6qop&~a^1YLVDM?0728BrPs&VfD>wBZq2dRbM1mq+_XL+1dA@?@8Zi zwpy+(Y)pA2cO`c$cdpL$YT#7`dI|kC&a7tXYLU|hz0oK7s+LnB#^I_+-x&d|UM&^lo}-opAN?IMY-Wok$PG;@yPsb?>X%Hqx5XLf?e`cBIS9 z89TvME=s*B8s`6!%&&b#dp4*4(T0BL`<;wcC>}GwGcc~QNayR7YbVg`bB9gvLil?B zsO`9fc#FthNTwbnSEOU6t7*xcvg_wEA3U^6%dO3AT`!q>zM;NB*od8e{6Y1xT%MDP z|Hrm2TIaXa)7*Mu{-4xRMu19{nsJ95$ zm{p#2y757yxvs0O*QSk>cO!ZZNtHbDI0-%(1d78ig76IS5rz@YzZ~ob-xYtCq^~`N z`(87#ZkU)~Zzy1x99bE;+)^p-)%ANyrTE@TR)1S|Sx8BYju5ZUK&=%MiLxi%RT`s;s1^ul%(ZzzL5MBlC3@t4gbaSPHzP+y5{)3PeL73ES%6VJ^VA3Q z!WaujMN!O`riz1tHM{LU19W$%KnxuoRyd$o+^hCD2 z0(q~oJFqQBhptVJt-IJ@piXnIus>pGB6P4_}s+ zx7T|Ncn*24ewkIwbLPq@%lwkb)f6gr}pHX#E^=4=HXxOh~h<|HxW5oImWs6IS0;e&Tni!=nam3 zn+-lRnfjIckt*CN$b1{!O_*Gb98SY;4J6XiX`x||Kc`)%p3p;9-23Nla+5aS@KEN5 zj7}0(bQ*Q~uWh}iY}HIH^EpyqJLWvszCrv(`365u8@B0ec+`+nRe#O9ej~W)Rfy-_ zsz{zj|IAKP&>w;)qa%4*x-(7N!KA(Sd%JUjGht+zqyFB8i2#|6%0`>U8#@N;h7-t{ z*b#l_pNEt z)>{Im14K8U&T-o;-33!+PrUwov1|Xa_#jOCYP*w&5bVz&NFdSo6bAsfexNVb z-5W;%yW#KzA6<#fhIbNRf`_hzqneeHl`j(KMKBE}N3Up>!oq10e*0hqk7Y zAwpAA$q1%o41>XpP=;z6nuh8~1WXNyQr9##{D(!6+;02 zPgA3CWWpVshcTH%1pifmHsRmRApiBjzgdreH-q~BtmWU8Ku_&J$sV4kX8hgB1YZh) zRB?gJI%X(Cbl{|$D*!U)aSaE_ z@=3SGMTeU!Bb3gGk6#v(Oa$2`r5Ml|8sHDY__tq7B+si-_MYx@gPVb0vjId3kq`WH O;LMDyQB?@n$o~M7G41{U diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c index 1d2235e08..7f63f0cc6 100644 --- a/applications/plugins/hid_app/hid.c +++ b/applications/plugins/hid_app/hid.c @@ -42,10 +42,12 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con furi_assert(context); Hid* hid = context; bool connected = (status == BtStatusConnected); - if(connected) { - notification_internal_message(hid->notifications, &sequence_set_blue_255); - } else { - notification_internal_message(hid->notifications, &sequence_reset_blue); + if(hid->transport == HidTransportBle) { + if(connected) { + notification_internal_message(hid->notifications, &sequence_set_blue_255); + } else { + notification_internal_message(hid->notifications, &sequence_reset_blue); + } } hid_keynote_set_connected_status(hid->hid_keynote, connected); hid_keyboard_set_connected_status(hid->hid_keyboard, connected); @@ -186,7 +188,9 @@ void hid_free(Hid* app) { furi_assert(app); // Reset notification - notification_internal_message(app->notifications, &sequence_reset_blue); + if(app->transport == HidTransportBle) { + notification_internal_message(app->notifications, &sequence_reset_blue); + } // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); diff --git a/applications/plugins/hid_app/views/hid_keyboard.c b/applications/plugins/hid_app/views/hid_keyboard.c index 3e3b63284..8b12e8fd1 100644 --- a/applications/plugins/hid_app/views/hid_keyboard.c +++ b/applications/plugins/hid_app/views/hid_keyboard.c @@ -25,6 +25,7 @@ typedef struct { bool back_pressed; bool connected; char key_string[5]; + HidTransport transport; } HidKeyboardModel; typedef struct { @@ -207,7 +208,7 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { HidKeyboardModel* model = context; // Header - if(!model->connected) { + if((!model->connected) && (model->transport == HidTransportBle)) { canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard"); @@ -361,6 +362,12 @@ HidKeyboard* hid_keyboard_alloc(Hid* bt_hid) { view_set_draw_callback(hid_keyboard->view, hid_keyboard_draw_callback); view_set_input_callback(hid_keyboard->view, hid_keyboard_input_callback); + with_view_model( + hid_keyboard->view, + HidKeyboardModel * model, + { model->transport = bt_hid->transport; }, + true); + return hid_keyboard; } diff --git a/applications/plugins/hid_app/views/hid_keynote.c b/applications/plugins/hid_app/views/hid_keynote.c index c95f42780..5e5eeb790 100644 --- a/applications/plugins/hid_app/views/hid_keynote.c +++ b/applications/plugins/hid_app/views/hid_keynote.c @@ -19,6 +19,7 @@ typedef struct { bool ok_pressed; bool back_pressed; bool connected; + HidTransport transport; } HidKeynoteModel; static void hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -39,11 +40,14 @@ static void hid_keynote_draw_callback(Canvas* canvas, void* context) { HidKeynoteModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); @@ -186,6 +190,9 @@ HidKeynote* hid_keynote_alloc(Hid* hid) { view_set_draw_callback(hid_keynote->view, hid_keynote_draw_callback); view_set_input_callback(hid_keynote->view, hid_keynote_input_callback); + with_view_model( + hid_keynote->view, HidKeynoteModel * model, { model->transport = hid->transport; }, true); + return hid_keynote; } diff --git a/applications/plugins/hid_app/views/hid_media.c b/applications/plugins/hid_app/views/hid_media.c index 5d764f73a..468529d56 100644 --- a/applications/plugins/hid_app/views/hid_media.c +++ b/applications/plugins/hid_app/views/hid_media.c @@ -21,6 +21,7 @@ typedef struct { bool down_pressed; bool ok_pressed; bool connected; + HidTransport transport; } HidMediaModel; static void hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) { @@ -41,11 +42,14 @@ static void hid_media_draw_callback(Canvas* canvas, void* context) { HidMediaModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media"); canvas_set_font(canvas, FontSecondary); @@ -190,6 +194,9 @@ HidMedia* hid_media_alloc(Hid* hid) { view_set_draw_callback(hid_media->view, hid_media_draw_callback); view_set_input_callback(hid_media->view, hid_media_input_callback); + with_view_model( + hid_media->view, HidMediaModel * model, { model->transport = hid->transport; }, true); + return hid_media; } diff --git a/applications/plugins/hid_app/views/hid_mouse.c b/applications/plugins/hid_app/views/hid_mouse.c index d1d76c15a..30a9d9d06 100644 --- a/applications/plugins/hid_app/views/hid_mouse.c +++ b/applications/plugins/hid_app/views/hid_mouse.c @@ -20,6 +20,7 @@ typedef struct { bool left_mouse_held; bool right_mouse_pressed; bool connected; + HidTransport transport; } HidMouseModel; static void hid_mouse_draw_callback(Canvas* canvas, void* context) { @@ -27,11 +28,14 @@ static void hid_mouse_draw_callback(Canvas* canvas, void* context) { HidMouseModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse"); canvas_set_font(canvas, FontSecondary); @@ -198,6 +202,9 @@ HidMouse* hid_mouse_alloc(Hid* hid) { view_set_draw_callback(hid_mouse->view, hid_mouse_draw_callback); view_set_input_callback(hid_mouse->view, hid_mouse_input_callback); + with_view_model( + hid_mouse->view, HidMouseModel * model, { model->transport = hid->transport; }, true); + return hid_mouse; } diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.c b/applications/plugins/hid_app/views/hid_mouse_jiggler.c index a2b07c7a1..d8f1f8928 100644 --- a/applications/plugins/hid_app/views/hid_mouse_jiggler.c +++ b/applications/plugins/hid_app/views/hid_mouse_jiggler.c @@ -16,6 +16,7 @@ typedef struct { bool connected; bool running; uint8_t counter; + HidTransport transport; } HidMouseJigglerModel; static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { @@ -23,11 +24,14 @@ static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { HidMouseJigglerModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler"); @@ -120,6 +124,12 @@ HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) { hid_mouse_jiggler->timer = furi_timer_alloc( hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->transport = hid->transport; }, + true); + return hid_mouse_jiggler; } diff --git a/applications/plugins/hid_app/views/hid_tiktok.c b/applications/plugins/hid_app/views/hid_tiktok.c index 42abf4148..e1f9f4bed 100644 --- a/applications/plugins/hid_app/views/hid_tiktok.c +++ b/applications/plugins/hid_app/views/hid_tiktok.c @@ -19,6 +19,7 @@ typedef struct { bool ok_pressed; bool connected; bool is_cursor_set; + HidTransport transport; } HidTikTokModel; static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { @@ -26,11 +27,14 @@ static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { HidTikTokModel* model = context; // Header - if(model->connected) { - canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + if(model->transport == HidTransportBle) { + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } } + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "TikTok"); canvas_set_font(canvas, FontSecondary); @@ -207,6 +211,9 @@ HidTikTok* hid_tiktok_alloc(Hid* bt_hid) { view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback); view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback); + with_view_model( + hid_tiktok->view, HidTikTokModel * model, { model->transport = bt_hid->transport; }, true); + return hid_tiktok; } From 1390f10a6fc53aabd128820e563fb98fe6c8079f Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Tue, 27 Dec 2022 12:29:21 +0400 Subject: [PATCH 4/7] [FL-3068] SubGhz: add Holtek_ht12x protocol (#2187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: add Holtek_ht12x protocol * SubGhz: add unit_test holtek_ht12x * SubGhz: correct string formatting Co-authored-by: あく --- .../debug/unit_tests/subghz/subghz_test.c | 17 +- assets/unit_tests/subghz/holtek_ht12x.sub | 8 + assets/unit_tests/subghz/holtek_ht12x_raw.sub | 10 + assets/unit_tests/subghz/test_random_raw.sub | 5 + lib/subghz/protocols/holtek_ht12x.c | 400 ++++++++++++++++++ lib/subghz/protocols/holtek_ht12x.h | 107 +++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 8 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 assets/unit_tests/subghz/holtek_ht12x.sub create mode 100644 assets/unit_tests/subghz/holtek_ht12x_raw.sub create mode 100644 lib/subghz/protocols/holtek_ht12x.c create mode 100644 lib/subghz/protocols/holtek_ht12x.h diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index cb89e1f02..1dee1d59e 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 253 +#define TEST_RANDOM_COUNT_PARSE 273 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -597,6 +597,13 @@ MU_TEST(subghz_decoder_smc5326_test) { "Test decoder " SUBGHZ_PROTOCOL_SMC5326_NAME " error\r\n"); } +MU_TEST(subghz_decoder_holtek_ht12x_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/holtek_ht12x_raw.sub"), SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME), + "Test decoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -730,6 +737,12 @@ MU_TEST(subghz_encoder_smc5326_test) { "Test encoder " SUBGHZ_PROTOCOL_SMC5326_NAME " error\r\n"); } +MU_TEST(subghz_encoder_holtek_ht12x_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/holtek_ht12x.sub")), + "Test encoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -774,6 +787,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_clemsa_test); MU_RUN_TEST(subghz_decoder_ansonic_test); MU_RUN_TEST(subghz_decoder_smc5326_test); + MU_RUN_TEST(subghz_decoder_holtek_ht12x_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -797,6 +811,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_clemsa_test); MU_RUN_TEST(subghz_encoder_ansonic_test); MU_RUN_TEST(subghz_encoder_smc5326_test); + MU_RUN_TEST(subghz_encoder_holtek_ht12x_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/assets/unit_tests/subghz/holtek_ht12x.sub b/assets/unit_tests/subghz/holtek_ht12x.sub new file mode 100644 index 000000000..09e20b134 --- /dev/null +++ b/assets/unit_tests/subghz/holtek_ht12x.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: Holtek_HT12X +Bit: 12 +Key: 00 00 00 00 00 00 0F FB +TE: 205 diff --git a/assets/unit_tests/subghz/holtek_ht12x_raw.sub b/assets/unit_tests/subghz/holtek_ht12x_raw.sub new file mode 100644 index 000000000..1aeedac38 --- /dev/null +++ b/assets/unit_tests/subghz/holtek_ht12x_raw.sub @@ -0,0 +1,10 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 97 -264 65 -1890 231 -366 165 -232 99 -166 297 -68 401 -100 97 -100 759 -264 823 -132 919 -98 65 -230 367 -98 1055 -52 51 -104 101 -126 173 -148 151 -100 153 -222 77 -288 53 -280 331 -212 81 -108 246 -156 267 -268 183 -1260 53 -622 51 -468 183 -264 105 -990 77 -421 75 -52 53 -214 185 -504 75 -563 131 -338 165 -82 133 -80 53 -236 231 -208 282 -836 187 -406 81 -102 181 -54 107 -415 81 -54 109 -298 55 -406 323 -136 299 -136 437 -52 133 -294 53 -110 133 -420 333 -402 269 -574 79 -270 163 -594 124 -52 51 -320 79 -324 109 -78 105 -236 79 -292 53 -432 181 -110 187 -268 107 -78 79 -188 79 -78 75 -130 75 -128 103 -628 79 -322 79 -136 81 -196 85 -296 51 -349 159 -325 131 -80 131 -80 402 -110 55 -194 109 -108 53 -108 135 -224 81 -162 57 -228 161 -357 105 -54 103 -347 279 -294 422 -152 51 -104 99 -146 147 -205 77 -216 107 -52 181 -52 107 -158 105 -52 133 -134 139 -418 291 -102 103 -106 105 -584 103 -540 53 -52 153 -208 227 -128 307 -184 105 -328 109 -862 129 -196 55 -244 51 -1501 55 -82 81 -326 79 -500 103 -406 207 -130 75 -126 155 -202 53 -376 135 -158 53 -428 107 -467 167 -138 81 -106 79 -346 157 -84 143 -144 85 -136 53 -308 189 -248 79 -711 109 -592 107 -220 107 -104 77 -583 131 -158 53 -110 135 -191 107 -748 105 -478 77 -398 107 -138 83 -410 127 -477 103 -162 119 -72 103 -442 129 -237 107 -664 175 -74 147 -406 53 -78 127 -178 51 -102 101 -259 125 -202 257 -210 77 -52 77 -130 251 -230 77 -76 77 -178 155 -98 97 -100 153 -54 185 -82 79 -82 135 -398 241 -130 77 -76 311 -210 53 -238 181 -404 241 -352 189 -270 265 -164 215 -998 157 -82 77 -106 51 -54 75 -100 328 -178 103 -224 253 -341 147 -334 446 -76 125 -100 75 -126 173 -690 75 -250 51 -554 81 -300 53 -188 53 -106 265 -1024 105 -646 221 -82 191 -362 75 -234 131 -128 83 -224 55 -240 77 -376 157 -78 187 -358 389 -76 103 -128 199 -513 83 -492 129 -618 123 -52 129 -52 79 -376 101 -688 105 -110 55 -84 179 -152 51 -486 358 -276 99 -74 73 -679 51 -210 113 -196 365 -104 223 -104 53 -430 55 -140 55 -84 153 -202 181 -358 149 -820 103 -80 215 -108 309 -368 105 -538 154 -732 233 -588 53 -253 155 -288 55 -136 55715 -102 407 -100 259 -78 157 -130 206 -52 258 -78 441 -104 77 -80 492 -104 182 -104 79 -155 +RAW_Data: 287 -78 807 -104 1588 -52 2952 -78 1018 -240 341 -262 361 -258 335 -280 309 -100 509 -280 307 -302 307 -6968 321 -290 293 -298 323 -294 289 -314 285 -334 285 -330 257 -330 281 -328 293 -310 281 -124 479 -330 281 -326 267 -6994 295 -324 267 -320 287 -340 259 -332 257 -358 257 -354 257 -328 257 -352 257 -352 255 -150 455 -360 255 -326 255 -7024 253 -352 255 -352 255 -354 267 -326 269 -352 239 -346 261 -362 233 -358 257 -356 255 -152 455 -334 255 -354 255 -7006 267 -342 267 -352 241 -350 263 -342 259 -362 233 -356 257 -356 231 -378 231 -380 231 -150 455 -358 231 -378 229 -7050 229 -378 229 -352 253 -352 239 -372 239 -380 243 -348 235 -368 231 -388 231 -356 233 -176 431 -384 229 -380 231 -7028 239 -368 241 -372 241 -350 239 -372 233 -366 233 -384 231 -358 231 -380 231 -378 231 -176 429 -384 205 -402 205 -7064 213 -380 243 -378 215 -374 233 -368 233 -386 231 -382 205 -382 231 -380 229 -380 231 -176 429 -386 203 -378 231 -7062 229 -378 229 -378 229 -378 229 -378 213 -396 203 -404 203 -404 203 -404 203 -404 203 -198 431 -376 215 -376 239 -7064 235 -364 231 -386 231 -384 205 -408 205 -380 231 -380 231 -380 231 -378 207 -404 205 -200 431 -384 205 -404 205 -7118 205 -408 205 -406 205 -408 205 -408 205 -408 205 -436 179 -440 283 -460 75 -1486 105 -54 103 -784 107 -218 113 -824 125 -2140 85 -583 51 -1291 53 -722 113 -622 107 -1176 51 -52 75 -152 113 -56 111 -784 105 -436 81 -112 85 -272 53 -406 81 -1345 105 -3008 53 -1418 105 -254 107 -874 131 -818 53 -595 77 -130 123 -1088 213 -724 131 -793 99 -428 75 -288 51 -334 101 -1471 81 -274 53 -164 107 -2618 129 -532 55 -366 297 -160 83 -166 184 -208 57 -196 328 -1640 235 -1743 79 -594 51 -76 77 -270 163 -728 51 -408 55 -354 101 -126 153 -184 133 -392 53 -604 77 -804 127 -965 51 -78 103 -668 262 -210 237 -439 75 -340 77 -106 105 -1775 79 -284 51 -3100 157 -433 55 -1355 107 -198 165 -82 79 -770 79 -1587 81 -638 79 -530 103 -703 51 -396 71 -956 151 -248 53 -553 103 -154 75 -806 75 -660 53 -698 127 -1210 53 -3175 79 -608 245 -1590 207 -164 107 -232 51 -1094 99 -695 135 -955 53 -804 217 -587 55 -1452 163 -1232 79 -968 79 -720 81 -1110 129 -1194 105 -1736 79 -386 79 -184 77 -652 75 -2442 103 -555 129 -125 75 -1115 81 -108 135 -980 53 -808 105 -1001 155 -1068 131 -850 99 -268 51 -1106 159 -2408 79 -612 51 -1191 +RAW_Data: 135 -212 77 -2816 137 -980 51 -1921 79 -1554 53 -906 83 -1042 99 -5161 51 -290 75 -558 51 -260 77 -738 99 -2128 105 -1384 53 -108 79 -188 55 -830 77 -5690 109 -2652 51 -1374 53 -2469 79 -2163 203 -152 71 -1459 79 -2741 105 -262 75 -1194 75 -228 153 -210 107 -3236 107 -1500 77 -3562 79 -170 55 -540 53 -1270 51 -304 51 -2071 137 -134 105 -266 101 -834 51 -8117 73 -3292 81 -732 181 -1539 53 -132 109 -1375 77 -132 81 -1061 77 -1620 79 -1373 105 -643 183 -190 53 -620 77 -1633 55 -931 51 -398 135 -924 111 -1122 51 -128 125 -2361 97 -2571 51 -3563 51 -794 77 -844 81 -788 81 -2040 77 -232 77 -1000 131 -322 73 -848 55 -336 107 -1546 81 -602 79 -954 107 -1824 73 -1131 240 -1048 197 -2442 73 -720 127 -78 77 -574 79 -624 101 -5754 103 -1829 149 -719 77 -982 53 -250 53 -634 161 -2201 75 -3483 149 -356 73 -98 123 -176 75 -702 231 -1633 121 -704 53 -1163 51 -1227 73 -368 123 -258 103 -530 279 -628 53 -884 55 -82 101 -102 135 -162 107 -1264 159 -3370 77 -3538 131 -130 81 -2460 129 -588 51 -514 77 -376 53 -616 53 -956 51 -1508 53 -1044 53 -1160 261 -442 53 -640 83 -300 79 -1723 75 -124 125 -532 103 -500 79 -764 53 -1220 279 -458 77 -548 159 -450 223 -718 55 -716 51 -716 203 -732 79 -6974 53 -1086 51 -1794 79 -882 53 -4221 53 -1070 77 -1126 75 -2307 155 -1233 131 -156 111 -1352 51 -478 51 -154 75 -724 51 -635 103 -652 77 -294 75 -268 81 -1046 177 -1778 51 -298 109 -336 77 -80 53 -192 133 -556 99 -394 81 -654 51 -106 79 -1893 55 -240 55 -136 55 -2680 77 -136 81 -164 53 -576 135 -3304 131 -654 103 -788 53 -224 187 -354 79 -1872 81 -290 207 -410 77 -1132 55 -112 57 -512 51 -2736 123 -404 239 -1283 51 -1526 213 -720 133 -354 109 -396 77 -1518 105 -632 53 -164 53 -1090 55 -730 79 -1705 75 -370 75 -378 79 -2022 81 -2480 103 -1108 79 -106 53 -1784 105 -512 219 -669 179 -795 105 -376 105 -514 53 -402 53 -282 83 -554 133 -448 105 -972 79 -490 81 -1184 77 -216 109 -132 51 -164 135 -1570 51 -1329 79 -1218 53 -2450 75 -1246 51 -3825 77 -1114 53 -1718 51 -846 79 -826 165 -306 75 -3821 51 -606 55 -880 107 -56 55 -5505 53 -504 79 -1356 77 -569 51 -2268 149 -358 53 -1979 51 -106 103 -176 221 -508 51 -110 53 -3896 127 -234 79 -1012 79 -84 243 -2860 53 -1010 73 -286 133 -2198 151 -370 129 -1942 101 -154 109 -702 79 -696 51 -302 +RAW_Data: 51 -744 83 -496 79 -672 77 -170 111 -264 103 -472 51 -524 75 -1172 51 -240 51963 -254 333 -278 333 -276 333 -278 305 -302 307 -76 531 -280 331 -276 331 -6938 347 -268 319 -272 319 -292 311 -312 283 -306 309 -304 283 -328 293 -310 281 -326 293 -110 479 -322 295 -322 291 -6964 283 -334 283 -304 283 -328 283 -328 281 -328 255 -354 265 -338 255 -352 255 -352 255 -124 481 -338 277 -328 279 -6984 269 -350 265 -320 287 -340 257 -332 257 -358 255 -356 255 -328 257 -354 257 -352 255 -150 455 -360 229 -376 231 -7040 239 -348 267 -350 239 -346 261 -364 231 -360 257 -356 231 -380 231 -378 231 -352 257 -150 453 -360 229 -378 229 -7032 239 -372 227 -378 227 -380 241 -352 241 -374 233 -366 233 -384 233 -356 231 -380 231 -176 431 -384 229 -354 255 -7030 231 -376 231 -376 229 -378 229 -378 229 -352 255 -352 255 -352 229 -378 229 -378 229 -174 427 -384 229 -376 241 -7040 241 -350 235 -368 233 -388 231 -358 233 -380 231 -380 229 -380 231 -380 229 -380 211 -192 421 -366 227 -380 227 -7068 239 -378 215 -402 209 -394 207 -388 231 -386 231 -384 205 -408 205 -408 205 -408 205 -202 409 -412 207 -1400 105 -1002 51 -9495 53 -250 57 -1093 155 -124 73 -344 75 -2759 83 -468 53 -738 77 -134 53 -1581 51 -106 127 -1209 121 -956 51 -918 83 -276 85 -1696 125 -618 81 -1666 51 -152 101 -1324 107 -54 141 -586 75 -784 55 -1828 51 -4052 81 -480 53 -218 141 -1346 105 -1152 127 -776 53 -426 135 -390 105 -939 81 -887 71 -492 107 -1311 105 -1844 101 -1340 77 -2586 51 -2637 51 -1626 105 -54 53 -1672 151 -2830 57 -3143 51 -1859 79 -929 179 -78 77 -890 73 -894 79 -80 79 -1184 53 -323 53 -1344 79 -636 53 -1808 55 -3048 79 -2287 53 -572 51 -822 51 -608 77 -1772 75 -2521 79 -162 81 -664 163 -110 83 -524 53 -930 53 -1816 79 -1305 51 -816 53 -1358 55 -822 55 -594 81 -2230 55 -234 77 -600 201 -3174 151 -2534 71 -122 51 -1370 81 -3130 127 -236 79 -728 101 -1472 53 -800 127 -528 51 -802 77 -52 99 -3144 77 -6346 51 -1090 81 -588 79 -292 169 -2345 107 -370 187 -1218 81 -296 75 -696 51 -516 77 -2154 75 -558 75 -816 103 -2200 125 -2766 229 -376 151 -3375 79 -1466 53 -535 167 -524 217 -54 131 -3408 51 -54 109 -1886 77 -732 83 -536 99 -3128 103 -168 57 -1852 51 -574 79 -296 155 -844 99 -767 147 -2406 99 -1014 81 -4460 175 -226 195 -454 73 -2236 53 -818 155 -352 83 -752 79 -5083 51 -1716 +RAW_Data: 77 -1925 51 -1760 81 -162 53 -1469 79 -362 53 -471 103 -750 53 -562 127 -238 79 -250 107 -52 53 -210 83 -504 79 -1566 107 -82 79 -968 53 -458 131 -1944 301 -594 79 -382 51 -1196 79 -3963 183 -670 111 -360 105 -1990 53 -1084 79 -658 73 -301 73 -264 75 -1335 79 -1110 81 -1856 159 -3986 75 -1262 53 -1189 53 -158 53 -903 81 -1135 133 -350 107 -178 151 -1096 73 -2734 81 -1280 107 -950 79 -52 53 -5912 53 -1653 111 -1601 79 -742 51 -1104 55 -1254 51 -867 53 -136 109 -813 79 -1082 53 -395 53 -54 109 -504 79 -218 109 -114 57 -1620 157 -3003 79 -656 53 -510 209 -1933 107 -1197 159 -5508 79 -1164 77 -1466 77 -742 101 -124 71 -2049 85 -144 109 -1304 105 -2310 55 -381 83 -114 113 -944 103 -184 83 -558 55 -2064 109 -760 75 -1036 77 -574 51 -134 131 -224 57 -104 53 -440 53 -1262 183 -2454 51 -1966 73 -1950 125 -1095 51 -480 121 -1994 57 -1930 103 -786 79 -2272 105 -3312 51 -746 127 -144 133 -1608 77 -692 51 -1136 53 -164 55 -573 55 -3110 53 -1558 105 -6248 53 -1051 111 -886 105 -2234 103 -106 53 -1256 101 -1446 111 -974 79 -851 81 -136 193 -3392 83 -582 103 -1197 111 -196 55 -906 51 -742 77 -2038 71 -686 53 -1943 51 -134 51 -852 51 -1658 133 -2050 161 -388 77 -326 81 -412 55 -1137 81 -3256 55 -1516 53 -1414 117 -372 51 -1144 199 -3087 51 -430 75 -1856 151 -128 95 -192 398 -1207 77 -280 51 -2716 51 -808 53 -78 77 -1524 109 -54 79 -410 79 -132 53 -770 109 -2066 185 -368 131 -2102 125 -1037 75 -780 127 -128 51 -176 53 -1982 77 -140 111 -1046 109 -3166 169 -1956 77 -4040 79 -2778 73 -204 129 -1546 82945 -150 359 -252 333 -76 533 -280 319 -286 305 -6960 319 -300 323 -270 317 -290 313 -308 283 -306 309 -304 283 -328 291 -310 281 -326 281 -124 481 -334 279 -302 305 -6970 293 -318 301 -304 279 -328 279 -328 277 -330 277 -330 293 -298 295 -320 287 -314 283 -128 489 -334 255 -330 281 -7012 265 -340 265 -344 251 -354 253 -354 269 -322 293 -322 263 -344 259 -336 257 -360 257 -152 457 -358 231 -354 257 -7060 237 -364 255 -354 237 -366 255 -354 255 -354 267 -352 241 -352 265 -348 261 -342 259 -154 439 -388 231 -358 255 -7114 237 -370 235 -394 233 -362 233 -386 233 -386 233 -386 233 -386 231 -388 233 -414 233 -180 441 -392 233 -1832 53 -564 53 -370 289 -867 201 -78 103 -352 213 -586 103 -1226 165 -112 55 -300 105 -975 107 -358 77 -410 55 -1777 51 -973 51 -828 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index 900b26207..be635f04d 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -168,3 +168,8 @@ RAW_Data: 285 -96 627 -362 53 -84 201 -374 113 -202 115 -202 421 -316 85 -58 139 RAW_Data: 535 -142 57 -58 55 -116 115 -432 85 -172 259 -192 167 -120 117 -72 119 -240 334 -72 71 -267 285 -144 119 -374 85 -88 85 -114 143 -202 229 -58 143 -202 115 -202 171 -86 71 -144 87 -56 173 -373 143 -116 113 -462 169 -80 215 -148 115 -336 85 -230 163 -432 85 -374 639 -174 85 -58 57 -82 295 -352 269 -532 414 -322 95 -287 263 -268 115 -56 259 -76 85 -282 401 -305 516 -114 115 -202 171 -86 451 -110 85 -346 201 -274 149 -202 85 -364 366 -258 57 -114 259 -172 142 -144 85 -116 85 -480 171 -144 57 -352 115 -116 535 -404 315 -202 163 -158 517 -316 215 -98 85 -346 85 -144 87 -86 257 -82 167 -58 85 -116 113 -894 233 -186 77 -266 147 -72 71 -82 57 -86 171 -58 57 -86 201 -364 143 -202 115 -114 85 -88 113 -86 87 -230 57 -76 613 -72 85 -96 209 -346 458 -58 547 -490 201 -315 315 -116 75 -168 359 -335 95 -384 93 -120 71 -312 251 -366 233 -96 189 -240 263 -192 271 -58 115 -58 229 -346 459 -174 113 -144 173 -144 218 -224 57 -116 215 -72 103 -202 513 -210 433 -116 113 -174 650 -273 147 -450 375 -86 115 -172 536 -84 85 -230 85 -58 195 -468 287 -110 551 -214 167 -311 213 -250 85 -58 85 -355 113 -230 115 -144 117 -288 195 -202 57 -376 123 -144 236 -168 553 -284 119 -72 143 -188 161 -120 93 -312 335 -58 55 -260 105 -244 143 -120 381 -268 173 -268 635 -168 453 -318 71 -167 71 -406 191 -172 215 -408 119 -144 93 -120 97 -130 143 -192 308 -122 147 -550 313 -96 139 -162 167 -96 431 -80 83 -112 201 -86 287 -86 229 -116 57 -288 113 -174 143 -116 113 -144 115 -518 57 -230 57 -172 231 -86 113 -314 183 -144 119 -72 165 -446 81 -86 135 -190 143 -96 71 -72 411 -96 143 -120 69 -216 349 -72 95 -96 517 -646 163 -86 113 -116 171 -116 143 -116 113 -287 259 -114 517 -168 141 -116 105 -72 95 -96 311 -118 159 -310 191 -54 143 -258 115 -450 219 -54 339 -372 239 -72 167 -174 113 -58 57 -144 259 -172 143 -336 113 -174 85 -230 83 -668 85 -202 113 -144 57 -116 373 -316 719 -288 115 -58 75 -120 139 -144 229 -144 57 -144 171 -192 391 -202 403 -58 315 -188 259 -56 115 -144 85 -404 57 -58 105 -102 429 -406 81 -172 57 -144 287 -230 287 -220 317 -458 283 -58 113 -86 269 -72 281 -58 85 -202 113 -52 421 -58 229 -480 259 -58 143 -660 155 -638 123 -86 57 -86 143 -346 143 -144 57 -144 RAW_Data: 2442 -312 275 -972 949 -310 941 -322 923 -342 921 -352 923 -334 281 -954 945 -350 279 -958 907 -354 289 -980 909 -352 281 -962 907 -330 311 -964 913 -350 317 -930 933 -344 921 -352 893 -330 311 -954 943 -318 315 -958 909 -324 947 -7854 953 -322 289 -948 939 -354 927 -332 911 -324 943 -344 917 -318 317 -964 905 -344 303 -942 947 -312 319 -960 913 -348 281 -958 941 -322 295 -978 905 -350 279 -962 931 -328 947 -324 939 -346 267 -964 935 -348 283 -938 953 -318 931 -7868 935 -346 269 -968 953 -310 941 -322 921 -330 935 -342 931 -318 311 -962 939 -290 337 -950 909 -352 317 -924 943 -324 313 -938 941 -318 317 -932 939 -344 301 -938 933 -350 921 -322 959 -310 301 -942 933 -352 317 -926 957 -314 919 -7868 943 -314 317 -958 909 -322 951 -344 919 -352 921 -324 937 -326 281 -964 941 -318 317 -930 939 -344 301 -938 933 -352 281 -962 953 -314 317 -922 933 -330 315 -954 943 -318 921 -342 943 -320 291 -980 909 -354 281 -962 943 -296 967 -7836 943 -332 309 -950 935 -318 929 -340 943 -320 921 -344 921 -354 283 -960 943 -296 309 -964 945 -318 279 -964 941 -322 333 -944 939 -314 279 -992 903 -342 319 -932 933 -330 931 -340 929 -348 281 -964 935 -334 281 -970 927 -346 921 -7862 951 -314 319 -922 953 -320 923 -346 921 -320 965 -298 943 -324 313 -942 941 -320 317 -930 941 -344 303 -940 945 -312 321 -940 953 -314 303 -960 933 -348 287 -962 911 -352 917 -350 905 -324 333 -918 971 -322 317 -924 945 -324 937 -7872 919 -324 317 -942 941 -318 933 -330 943 -324 943 -310 951 -318 317 -930 939 -344 301 -938 933 -352 317 -926 953 -314 319 -924 939 -324 331 -950 907 -354 315 -926 945 -324 939 -312 953 -318 317 -930 937 -344 301 -940 947 -348 909 -7864 949 -310 319 -956 915 -350 919 -348 905 -322 963 -296 935 -348 317 -922 951 -322 295 -976 939 -314 281 -996 915 -326 307 -940 959 -310 301 -966 935 -346 285 -958 915 -348 921 -348 903 -354 303 -948 911 -350 315 -926 945 -324 941 -7874 943 -290 319 -942 973 -318 929 -314 937 -328 941 -324 939 -310 303 -962 933 -352 285 -962 949 -314 319 -924 951 -320 293 -948 941 -354 283 -962 943 -294 309 -966 943 -320 931 -328 943 -326 311 -940 939 -320 309 -958 933 -338 943 -7840 933 -352 277 -964 941 -322 923 -344 923 -350 931 -310 955 -320 291 -974 907 -350 281 -958 963 -298 313 -956 945 -314 311 -960 937 -312 311 -966 909 -324 319 -944 941 -354 929 -298 945 -324 315 -940 RAW_Data: 943 -354 281 -964 905 -330 933 -7868 951 -324 315 -938 943 -354 893 -330 943 -324 943 -344 919 -318 317 -962 903 -344 301 -974 903 -350 317 -932 931 -342 269 -972 949 -346 285 -938 955 -310 301 -964 935 -348 921 -320 921 -344 301 -940 935 -350 317 -930 929 -318 937 -7872 939 -344 301 -940 947 -346 917 -322 921 -344 923 -352 927 -334 281 -970 925 -334 277 -982 943 -318 317 -932 931 -344 301 -936 935 -350 281 -960 957 -312 303 -960 935 -346 907 -322 929 -344 301 -942 935 -350 317 -924 955 -312 951 -7858 919 -342 309 -940 949 -348 909 -322 923 -344 923 -352 923 -336 317 -924 945 -312 311 -966 921 -340 317 -924 947 -350 281 -958 941 -322 291 -976 905 -350 279 -960 935 -342 943 -320 919 -330 311 -958 943 -320 315 -932 935 -344 919 -7866 957 -312 303 -964 917 -342 945 -320 923 -344 923 -354 929 -298 315 -956 941 -318 315 -960 911 -324 317 -942 939 -354 281 -964 941 -294 311 -968 943 -318 317 -932 937 -330 931 -350 919 -348 283 -960 917 -350 317 -922 939 -322 965 -7864 921 -324 329 -950 909 -354 923 -336 913 -322 947 -344 919 -354 281 -962 941 -294 311 -960 935 -354 281 -962 939 -294 311 -964 937 -354 281 -964 941 -296 309 -964 939 -318 931 -330 945 -324 315 -940 939 -354 281 -964 909 -344 921 -7862 963 -304 307 -976 933 -320 929 -328 941 -324 939 -348 915 -320 317 -930 939 -344 301 -940 965 -320 319 -926 953 -312 303 -960 933 -312 321 -960 913 -348 319 -924 943 -320 959 -310 921 -354 319 -924 943 -324 311 -938 941 -318 957 -7862 943 -318 317 -932 933 -344 925 -352 897 -332 943 -324 943 -346 267 -966 951 -310 321 -960 911 -350 281 -958 949 -320 291 -978 937 -316 279 -964 949 -326 309 -944 943 -314 959 -318 933 -336 317 -934 933 -344 267 -964 937 -350 905 -7896 943 -318 319 -926 955 -314 919 -350 935 -324 941 -294 967 -312 303 -962 933 -348 285 -960 917 -348 317 -922 941 -322 329 -950 907 -354 315 -926 943 -326 313 -940 941 -352 893 -332 949 -324 315 -938 941 -352 283 -962 943 -310 925 -7890 931 -344 269 -968 949 -310 943 -320 923 -350 937 -310 955 -318 317 -930 935 -344 301 -942 947 -346 285 -958 915 -346 317 -924 951 -322 295 -982 905 -352 317 -924 945 -324 941 -346 917 -318 317 -962 905 -330 311 -956 937 -352 897 -7878 939 -354 283 -960 941 -294 965 -312 953 -318 385 -201512 165 -198 265 -526 229 -298 755 -164 61687 -17310 131 -1056 99 -296 195 -296 65 -66 1617 +RAW_Data: 77 -76 131 -244 81 -210 55 -1428 53 -344 53 -238 51 -448 51 -804 125 -1490 51 -452 79 -1816 51 -176 197 -700 133 -563 51 -386 79 -474 109 -626 55 -266 103 -616 283 -1932 51 -1034 51 -2809 75 -244 83 -5339 77 -260 105 -839 107 -1806 53 -1408 81 -810 135 -488 187 -1469 73 -2596 75 -74 51 -726 113 -136 83 -406 55 -194 133 -606 55 -1018 55 -1774 51 -1954 75 -910 51 -944 137 -1337 51 -1606 101 -566 75 -584 51 -1470 133 -242 159 -2798 51 -1568 97 -100 71 -556 77 -1234 53 -320 53 -274 68337 -252 333 -278 333 -74 533 -280 307 -276 331 -6948 347 -262 329 -278 329 -278 329 -278 303 -304 303 -302 303 -304 303 -278 329 -278 329 -74 533 -294 325 -270 317 -6944 335 -282 309 -304 309 -304 281 -328 293 -310 281 -326 281 -326 281 -326 279 -302 305 -100 503 -306 305 -302 293 -6992 295 -294 291 -314 285 -336 283 -334 257 -328 283 -328 291 -310 281 -328 279 -328 279 -124 479 -332 279 -328 255 -7012 295 -324 271 -330 267 -346 261 -342 259 -334 257 -358 255 -356 257 -354 231 -354 255 -152 453 -356 257 -352 255 -7024 255 -352 257 -352 255 -354 255 -326 257 -352 255 -352 255 -354 255 -352 253 -352 255 -150 453 -356 255 -354 253 -7030 255 -352 267 -344 251 -354 253 -354 253 -354 267 -322 267 -350 261 -344 257 -364 231 -154 459 -360 257 -354 231 -7062 231 -380 231 -380 231 -352 257 -352 257 -352 257 -352 257 -352 257 -352 255 -354 231 -174 457 -360 229 -378 229 -7084 239 -364 239 -366 229 -380 229 -378 229 -380 229 -378 255 -354 255 -354 255 -352 255 -150 457 -364 253 -354 255 -9542 101 -3126 53 -814 109 -406 51 -162 109 -2219 183 -496 103 -1369 81 -603 99 -2172 79 -1103 75 -676 77 -560 103 -378 51 -654 95 -888 155 -1322 111 -1626 53 -182 51 -166 83 -52 181 -182 71 -2132 77 -2839 103 -4022 79 -362 81 -466 75 -970 203 -998 51 -2085 51 -1853 99 -328 75 -346 55 -1949 79 -2648 79 -434 75 -6757 51 -1920 109 -306 51 -612 101 -996 77 -764 81 -790 125 -1489 99 -430 77 -4142 165 -372 101 -198 71 -1688 51 -1636 99 -434 81 -794 135 -1973 79 -188 109 -2678 81 -196 109 -2099 51 -504 77 -1854 51 -910 107 -948 75 -122 131 -78 79 -1781 103 -3344 111 -406 79 -184 51 -408 103 -54 79 -1474 127 -1789 213 -683 131 -348 161 -5237 53 -2675 101 -52 105 -474 103 -1336 99 -3548 105 -1724 161 -2180 107 -2514 97 -3784 51 -910 77 -505 71 -494 131 -1154 79 -2295 75 -350 161 -274 81 -222 +RAW_Data: 107 -1501 77 -1518 53 -704 113 -390 107 -650 73 -932 51 -3641 169 -704 187 -574 79 -332 51 -3765 51 -1042 75 -1413 103 -2163 75 -218 73 -4118 73 -716 51 -1720 51 -176 145 -817 79 -602 55 -1270 53 -2290 81 -346 79 -1840 53 -596 97 -1135 155 -1672 157 -1150 53 -52 101 -2753 153 -1546 158 -698 79 -2962 215 -490 161 -766 51 -2170 55 -811 51 -694 51 -1461 103 -2590 149 -3785 130 -54 103 -1108 103 -3978 51 -1626 81 -1825 109 -452 129 -1000 79 -1651 157 -276 53 -104 81 -2440 81 -1780 53 -1554 51 -512 131 -2508 99 -176 73 -914 51 -76 81 -202 111 -690 109 -790 109 -584 53 -244 79 -706 73 -550 129 -142 127 -546 77 -296 53 -874 105 -2623 51 -1004 77 -3131 79 -552 81 -2008 187 -1168 55 -1173 51 -2146 99 -700 51 -1580 101 -252 75 -474 77 -569 73 -5817 77 -614 77 -712 149 -228 73 -562 201 -274 127 -648 77 -578 75 -1810 109 -2106 171 -5996 81 -366 159 -274 55 -1228 77 -3386 53 -106 81 -1024 133 -2331 53 -2636 53 -780 149 -842 79 -2288 53 -2807 107 -1410 51 -620 75 -428 71 -272 75 -1140 103 -4912 55 -2261 53 -716 53 -3093 109 -502 111 -1492 53 -4317 51 -500 83 -338 129 -698 105 -1565 103 -1874 105 -344 77 -546 79 -2826 105 -260 75 -616 103 -1254 113 -2687 77 -977 73 -246 97 -1054 109 -4681 67101 -252 357 -252 333 -276 333 -252 355 -254 333 -276 331 -6932 347 -272 353 -242 343 -268 339 -286 309 -282 309 -306 307 -304 283 -328 281 -328 281 -102 505 -308 291 -314 279 -6996 281 -326 279 -328 277 -328 277 -328 279 -328 277 -328 279 -328 279 -328 277 -330 277 -124 483 -322 297 -298 293 -6972 283 -336 281 -332 257 -330 281 -328 283 -328 281 -330 265 -338 255 -352 253 -352 255 -150 455 -336 279 -328 279 -6992 295 -322 265 -348 261 -342 259 -334 259 -356 257 -356 255 -330 257 -354 257 -352 255 -152 455 -358 239 -368 253 -7026 243 -352 243 -350 265 -344 261 -362 231 -358 255 -356 257 -354 231 -380 229 -354 255 -150 455 -360 231 -376 231 -7050 229 -378 229 -378 229 -378 229 -378 229 -378 229 -378 229 -352 255 -352 253 -352 255 -150 455 -362 229 -378 227 -7050 253 -354 229 -378 229 -378 229 -378 227 -378 229 -378 229 -378 229 -378 229 -376 229 -174 431 -374 243 -378 241 -7032 261 -370 233 -362 233 -384 233 -356 233 -380 231 -380 231 -380 231 -380 231 -378 231 -176 431 -384 231 -380 211 -7114 231 -384 231 -384 205 -408 207 -408 207 -408 231 -384 231 -412 207 -412 181 -530 77 -1144 +RAW_Data: 79 -4798 53 -918 83 -4847 51 -755 103 -732 81 -388 55 -1026 77 -1506 101 -242 107 -469 51 -2026 79 -686 77 -348 51 -104 131 -860 129 -148 73 -446 75 -440 97 -306 99 -600 51 -626 105 -1350 95 -674 83 -230 119 -1714 135 -396 155 -1111 109 -652 111 -482 51 -506 55 -1715 103 -968 207 -1156 81 -164 57 -404 99 -508 205 -126 75 -1417 51 -186 77 -588 53 -54 103 -2854 73 -1010 53 -800 51 -2494 53 -106 105 -52 51 -104 79 -1116 51 -654 103 -220 77 -162 71 -5385 137 -2232 79 -1159 79 -250 57 -108 79 -164 107 -1660 79 -3927 129 -992 73 -1913 51 -1430 51 -1498 55 -514 103 -586 81 -386 53 -2402 175 -1994 85 -3431 53 -3209 99 -372 79 -78 53 -1338 75 -682 97 -680 51 -206 101 -1708 101 -452 131 -1397 161 -2272 53 -456 77 -1413 193 -270 109 -466 53 -2432 77 -222 189 -474 107 -774 171 -192 79 -1327 75 -2141 51 -908 135 -3866 75 -804 129 -468 101 -1040 79 -1470 55 -869 77 -1448 105 -160 55 -1916 240 -588 79 -1587 53 -922 79 -2292 181 -1448 51 -552 77 -2189 75 -2545 77 -384 300 -2478 101 -1092 73 -558 79 -132 105 -884 103 -1177 109 -880 79 -2431 109 -1006 105 -468 53 -1378 235 -684 75 -285 73 -604 129 -528 77 -1582 51 -1240 105 -2750 75 -252 51 -1024 95 -1891 51 -864 107 -326 83 -887 159 -1058 163 -322 105 -722 83 -388 81 -936 155 -880 55 -220 83 -2123 135 -2100 73 -1926 103 -1633 149 -526 51 -324 51 -1538 103 -164 137 -964 81 -152 111 -781 225 -655 53 -2888 105 -151 131 -454 53 -4109 77 -1052 53 -178 163 -910 51 -733 207 -2070 53 -474 79 -54 53 -818 51 -1228 53 -2262 79 -788 79 -480 73 -2747 83 -316 183 -1880 105 -862 53 -662 53 -2287 153 -1630 51 -817 243 -806 55 -510 51 -1389 75 -986 135 -498 109 -532 131 -5521 99 -2948 209 -764 75 -1168 75 -886 83 -2065 53 -710 51 -596 77 -374 73 -628 99 -732 51 -202 73 -632 53 -222 55 -511 79 -4884 53 -1826 81 -1266 107 -356 55 -110 113 -280 83 -756 169 -252 81 -1854 51 -1556 157 -258 75 -748 53 -1438 291 -244 71 -1092 77 -1220 229 -1055 181 -1182 71 -1284 77 -864 79 -138 53 -160 53 -952 81 -80 127 -1272 51 -590 103 -502 77 -634 101 -74 51 -224 101 -912 77 -562 51 -164 83 -396 105 -4643 111 -3293 133 -1395 107 -3047 137 -2353 53 -298 83 -54 81 -80 53 -162 83 -392 105 -606 107 -787 53 -928 51 -2800 161 -1146 51 -182 103 -536 103 -994 81 -2044 83 -732 133 -1881 133 -2160 75 -178 +RAW_Data: 75 -1694 101 -122 73 -864 51 -250 129 -406 77 -630 77 -610 101 -781 125 -128 51 -5075 77 -1992 83 -1272 176 -2100 53 -2044 53 -1234 79 -1704 157 -519 99 -2374 101 -100 103 -202 51 -360 77 -1962 103 -2153 77 -1820 191 -164 167 -1320 77 -1718 127 -1374 81 -1047 53 -54 79 -632 53 -656 51 -128 81 -216 51 -755 79 -2692 103 -1478 125 -452 51 -896 157 -3679 135 -632 105 -134 55 -112 77 -588 79 -188 55 -1118 79 -1152 51 -1950 109 -1858 103 -1104 81 -580 131 -226 255 -2932 77 -1536 51 -1044 159 -2135 67667 -252 333 -278 333 -276 333 -74 533 -280 307 -276 345 -6930 331 -276 329 -278 329 -278 327 -278 349 -270 325 -270 317 -290 313 -308 283 -306 309 -100 509 -306 283 -328 281 -6972 307 -302 281 -326 281 -326 281 -328 279 -328 281 -326 279 -328 279 -326 281 -328 279 -124 481 -308 281 -326 279 -6998 281 -326 279 -328 279 -328 277 -330 277 -328 279 -328 279 -328 277 -304 303 -302 303 -100 503 -306 295 -322 293 -6968 287 -342 259 -336 283 -332 257 -356 255 -328 283 -328 257 -352 257 -352 257 -352 255 -150 455 -334 281 -326 281 -6996 265 -342 253 -354 253 -354 253 -354 253 -354 267 -326 269 -350 263 -342 257 -336 259 -152 459 -360 257 -354 231 -7038 267 -338 255 -352 253 -354 253 -354 239 -354 269 -352 239 -372 233 -366 233 -358 257 -154 457 -360 231 -378 231 -7050 231 -380 231 -378 231 -380 231 -378 231 -354 255 -352 257 -352 255 -354 231 -378 229 -176 453 -358 229 -378 231 -7076 231 -378 231 -380 231 -380 229 -354 255 -354 257 -352 257 -352 257 -352 257 -354 255 -150 455 -358 265 -364 229 -4941 101 -1058 153 -670 157 -532 124 -1396 133 -82 165 -162 153 -258 207 -156 131 -1582 85 -714 53 -774 103 -396 274 -110 131 -1965 55 -402 159 -1026 79 -590 77 -3531 57 -500 51 -4770 109 -722 77 -186 53 -298 79 -502 165 -808 77 -438 53 -382 101 -1914 75 -504 77 -1969 135 -5517 99 -576 51 -608 243 -684 53 -2058 315 -1384 79 -1079 77 -232 79 -212 155 -1500 137 -258 75 -975 204 -752 83 -2542 51 -484 103 -78 77 -210 53 -922 157 -1900 107 -2173 83 -384 101 -80 128 -814 183 -978 127 -772 105 -2073 51 -708 53 -300 83 -739 237 -884 131 -3412 157 -1752 81 -164 83 -3373 53 -1406 105 -3809 79 -432 51 -724 77 -548 53 -1955 79 -807 81 -2096 103 -490 105 -1196 109 -108 79 -394 71 -1159 129 -126 143 -340 107 -556 81 -2390 135 -106 133 -690 133 -4347 189 -290 51 -110 53 -78 103 -1101 51 -1362 +RAW_Data: 83 -320 81 -4648 101 -3726 173 -1418 85 -348 53 -2994 79 -1390 51 -1656 107 -764 53 -134 79 -1619 131 -932 55 -2810 107 -3218 79 -765 107 -654 103 -1498 77 -228 51 -134 247 -1526 51 -3903 103 -1495 179 -282 77 -392 53 -1756 105 -368 111 -486 51 -298 53 -216 113 -358 51 -266 187 -1059 81 -780 105 -238 51 -482 53 -791 109 -2169 77 -5304 53 -398 79 -650 51 -54 51 -1789 73 -198 101 -1580 101 -746 97 -4518 53 -744 51 -1064 101 -928 111 -392 185 -869 103 -320 133 -704 81 -244 53 -1628 75 -634 79 -666 183 -1276 83 -218 107 -1163 55 -1276 127 -1144 73 -1400 81 -266 77 -568 129 -806 121 -1420 103 -848 77 -982 103 -2132 81 -1610 101 -1218 55 -2208 75 -2735 53 -921 53 -724 51 -472 83 -3164 185 -400 77 -812 81 -306 215 -2167 53 -130 53 -272 81 -400 79 -1272 81 -418 51 -1381 73 -340 101 -2169 81 -2330 137 -2698 99 -2340 99 -126 51 -1714 55 -488 81 -3500 51 -404 77 -1422 77 -856 215 -80 51 -2308 53 -134 77 -2036 75 -5175 129 -946 239 -638 53 -244 55 -564 105 -826 71 -1632 77 -106 129 -246 135 -366 79 -724 79 -1535 57 -1085 113 -1320 79 -3111 127 -1578 75 -324 75 -102 173 -364 79 -1374 53 -1508 107 -622 51 -526 109 -584 187 -2648 51 -106 79 -380 103 -604 51 -1244 73 -5766 107 -1934 177 -702 51 -1277 53 -1643 79 -1446 81 -4098 75 -574 103 -432 189 -1436 107 -454 79 -132 105 -136 81 -112 113 -942 239 -1238 79 -952 157 -340 51 -314 191 -456 53 -3368 101 -150 99 -464 51 -718 73 -770 101 -150 73 -2132 75 -557 77 -680 81 -3512 151 -760 75 -332 75 -1212 131 -1468 79 -1955 101 -541 75 -344 79 -2146 53 -2299 97 -720 79 -2518 79 -3807 51 -1272 75 -352 77 -52 75 -586 53 -1142 79 -82 81 -2400 157 -324 81 -268 103 -1154 81 -1175 79 -1191 51 -1074 53 -2566 137 -854 75 -1497 51 -4533 51 -2290 51 -344 77 -348 55 -1182 77 -897 135 -874 51 -1064 51 -208 55 -140 55 -1334 133 -1238 157 -1669 113 -2128 75 -848 85 -510 83590 -126 333 -280 331 -252 331 -6946 331 -276 331 -276 329 -278 329 -276 331 -276 331 -276 331 -276 347 -238 351 -254 353 -268 323 -270 345 -6924 335 -282 307 -304 307 -304 281 -304 307 -302 307 -302 305 -302 307 -302 305 -302 281 -124 507 -282 305 -302 305 -6984 279 -328 277 -328 279 -328 277 -330 277 -304 303 -302 305 -302 305 -302 303 -304 303 -100 507 -314 295 -298 293 -6986 283 -334 281 -306 283 -328 283 -328 281 -328 283 -328 255 -352 diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c new file mode 100644 index 000000000..169387ded --- /dev/null +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -0,0 +1,400 @@ +#include "holtek_ht12x.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://www.holtek.com/documents/10179/116711/HT12A_Ev130.pdf + * + */ + +#define TAG "SubGhzProtocolHoltek_HT12X" + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define CNT_TO_DIP(dip) \ + (dip & 0x0080 ? '0' : '1'), (dip & 0x0040 ? '0' : '1'), (dip & 0x0020 ? '0' : '1'), \ + (dip & 0x0010 ? '0' : '1'), (dip & 0x0008 ? '0' : '1'), (dip & 0x0004 ? '0' : '1'), \ + (dip & 0x0002 ? '0' : '1'), (dip & 0x0001 ? '0' : '1') + +static const SubGhzBlockConst subghz_protocol_holtek_th12x_const = { + .te_short = 320, + .te_long = 640, + .te_delta = 200, + .min_count_bit_for_found = 12, +}; + +struct SubGhzProtocolDecoderHoltek_HT12X { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderHoltek_HT12X { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + Holtek_HT12XDecoderStepReset = 0, + Holtek_HT12XDecoderStepFoundStartBit, + Holtek_HT12XDecoderStepSaveDuration, + Holtek_HT12XDecoderStepCheckDuration, +} Holtek_HT12XDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_holtek_th12x_decoder = { + .alloc = subghz_protocol_decoder_holtek_th12x_alloc, + .free = subghz_protocol_decoder_holtek_th12x_free, + + .feed = subghz_protocol_decoder_holtek_th12x_feed, + .reset = subghz_protocol_decoder_holtek_th12x_reset, + + .get_hash_data = subghz_protocol_decoder_holtek_th12x_get_hash_data, + .serialize = subghz_protocol_decoder_holtek_th12x_serialize, + .deserialize = subghz_protocol_decoder_holtek_th12x_deserialize, + .get_string = subghz_protocol_decoder_holtek_th12x_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_holtek_th12x_encoder = { + .alloc = subghz_protocol_encoder_holtek_th12x_alloc, + .free = subghz_protocol_encoder_holtek_th12x_free, + + .deserialize = subghz_protocol_encoder_holtek_th12x_deserialize, + .stop = subghz_protocol_encoder_holtek_th12x_stop, + .yield = subghz_protocol_encoder_holtek_th12x_yield, +}; + +const SubGhzProtocol subghz_protocol_holtek_th12x = { + .name = SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_holtek_th12x_decoder, + .encoder = &subghz_protocol_holtek_th12x_encoder, +}; + +void* subghz_protocol_encoder_holtek_th12x_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderHoltek_HT12X* instance = + malloc(sizeof(SubGhzProtocolEncoderHoltek_HT12X)); + + instance->base.protocol = &subghz_protocol_holtek_th12x; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_holtek_th12x_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @return true On success + */ +static bool + subghz_protocol_encoder_holtek_th12x_get_upload(SubGhzProtocolEncoderHoltek_HT12X* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send header + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 36); + //Send start bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 2); + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 2); + } + } + return true; +} + +bool subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_holtek_th12x_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_holtek_th12x_stop(void* context) { + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_holtek_th12x_yield(void* context) { + SubGhzProtocolEncoderHoltek_HT12X* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_holtek_th12x_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderHoltek_HT12X* instance = + malloc(sizeof(SubGhzProtocolDecoderHoltek_HT12X)); + instance->base.protocol = &subghz_protocol_holtek_th12x; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_holtek_th12x_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + free(instance); +} + +void subghz_protocol_decoder_holtek_th12x_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; +} + +void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + + switch(instance->decoder.parser_step) { + case Holtek_HT12XDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short * 36) < + subghz_protocol_holtek_th12x_const.te_delta * 36)) { + //Found Preambula + instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit; + } + break; + case Holtek_HT12XDecoderStepFoundStartBit: + if((level) && (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta)) { + //Found StartBit + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = duration; + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + case Holtek_HT12XDecoderStepSaveDuration: + //save duration + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_holtek_th12x_const.te_short * 10 + + subghz_protocol_holtek_th12x_const.te_delta)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->te /= (instance->decoder.decode_count_bit * 3 + 1); + + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->last_data = instance->decoder.decode_data; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + instance->decoder.parser_step = Holtek_HT12XDecoderStepFoundStartBit; + break; + } else { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = Holtek_HT12XDecoderStepCheckDuration; + } + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + case Holtek_HT12XDecoderStepCheckDuration: + if(level) { + instance->te += duration; + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_holtek_th12x_const.te_long) < + subghz_protocol_holtek_th12x_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_holtek_th12x_const.te_short) < + subghz_protocol_holtek_th12x_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_holtek_th12x_const.te_long) < + subghz_protocol_holtek_th12x_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Holtek_HT12XDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + } else { + instance->decoder.parser_step = Holtek_HT12XDecoderStepReset; + } + break; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_holtek_th12x_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->btn = instance->data & 0x0F; + instance->cnt = (instance->data >> 4) & 0xFF; +} + +uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_holtek_th12x_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + bool ret = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_holtek_th12x_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + ret = true; + } while(false); + return ret; +} + +static void subghz_protocol_holtek_th12x_event_serialize(uint8_t event, FuriString* output) { + furi_string_cat_printf( + output, + "%s%s%s%s\r\n", + (((event >> 3) & 0x1) == 0x0 ? "B1 " : ""), + (((event >> 2) & 0x1) == 0x0 ? "B2 " : ""), + (((event >> 1) & 0x1) == 0x0 ? "B3 " : ""), + (((event >> 0) & 0x1) == 0x0 ? "B4 " : "")); +} + +void subghz_protocol_decoder_holtek_th12x_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderHoltek_HT12X* instance = context; + subghz_protocol_holtek_th12x_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%03lX\r\n" + "Btn: ", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFF)); + subghz_protocol_holtek_th12x_event_serialize(instance->generic.btn, output); + furi_string_cat_printf( + output, + "DIP:" DIP_PATTERN "\r\n" + "Te:%luus\r\n", + CNT_TO_DIP(instance->generic.cnt), + instance->te); +} diff --git a/lib/subghz/protocols/holtek_ht12x.h b/lib/subghz/protocols/holtek_ht12x.h new file mode 100644 index 000000000..7b5c31dd7 --- /dev/null +++ b/lib/subghz/protocols/holtek_ht12x.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME "Holtek_HT12X" + +typedef struct SubGhzProtocolDecoderHoltek_HT12X SubGhzProtocolDecoderHoltek_HT12X; +typedef struct SubGhzProtocolEncoderHoltek_HT12X SubGhzProtocolEncoderHoltek_HT12X; + +extern const SubGhzProtocolDecoder subghz_protocol_holtek_th12x_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_holtek_th12x_encoder; +extern const SubGhzProtocol subghz_protocol_holtek_th12x; + +/** + * Allocate SubGhzProtocolEncoderHoltek_HT12X. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderHoltek_HT12X* pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void* subghz_protocol_encoder_holtek_th12x_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void subghz_protocol_encoder_holtek_th12x_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + */ +void subghz_protocol_encoder_holtek_th12x_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderHoltek_HT12X instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_holtek_th12x_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderHoltek_HT12X. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderHoltek_HT12X* pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void* subghz_protocol_decoder_holtek_th12x_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void subghz_protocol_decoder_holtek_th12x_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + */ +void subghz_protocol_decoder_holtek_th12x_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_holtek_th12x_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderHoltek_HT12X. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_holtek_th12x_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance + * @param output Resulting text + */ +void subghz_protocol_decoder_holtek_th12x_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index ed46f5c97..2022e9c47 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -13,6 +13,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, &subghz_protocol_clemsa, &subghz_protocol_ansonic, &subghz_protocol_smc5326, + &subghz_protocol_holtek_th12x, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 9f4467394..998fb56b3 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -37,5 +37,6 @@ #include "clemsa.h" #include "ansonic.h" #include "smc5326.h" +#include "holtek_ht12x.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; From 08eb666f7b45cc9d160c4ea2a36d6a12f00281f2 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 27 Dec 2022 11:39:04 +0300 Subject: [PATCH 5/7] [FL-3000] File browser: Empty folder label (#2188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/modules/file_browser.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index d21a48b54..e03a032c1 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -544,6 +544,18 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { model->item_cnt); } + uint32_t folder_item_cnt = (model->is_root) ? (model->item_cnt) : (model->item_cnt - 1); + if(folder_item_cnt == 0) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_str_aligned( + canvas, + canvas_width(canvas) / 2, + canvas_height(canvas) / 2, + AlignCenter, + AlignCenter, + ""); + } + furi_string_free(filename); } From ded7e727d03342ade0acf9c32ec42d5d8eda2d63 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 27 Dec 2022 11:14:03 +0200 Subject: [PATCH 6/7] [FL-3060] New MFC Bruteforce animation (#2190) * Change the wording in the headers * Add support for text in the progress bar * New MFC key bruteforce screen * Typo fix * nfc: rename Flipper Dict to System Dict * elements: fix types * Display the correct key attack sector Co-authored-by: gornekich Co-authored-by: Aleksandr Kutuzov --- .../main/nfc/scenes/nfc_scene_extra_actions.c | 2 +- .../scenes/nfc_scene_mf_classic_dict_attack.c | 19 +++++-- .../nfc/scenes/nfc_scene_mf_classic_keys.c | 8 +-- applications/main/nfc/scenes/nfc_scene_read.c | 2 +- applications/main/nfc/views/dict_attack.c | 50 ++++++++++++++++--- applications/main/nfc/views/dict_attack.h | 4 ++ applications/services/gui/canvas.h | 1 + applications/services/gui/elements.c | 25 ++++++++++ applications/services/gui/elements.h | 17 +++++++ firmware/targets/f7/api_symbols.csv | 5 +- lib/nfc/helpers/mf_classic_dict.c | 4 +- lib/nfc/helpers/mf_classic_dict.h | 2 +- lib/nfc/nfc_device.h | 3 +- lib/nfc/nfc_worker.c | 8 +++ lib/nfc/nfc_worker.h | 3 ++ 15 files changed, 130 insertions(+), 23 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index fc6021d73..717e8efc4 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -43,7 +43,7 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexMfClassicKeys) { - if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) { + if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeys); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index acb5b783c..b82bf5521 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -53,10 +53,10 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(Nfc* nfc, DictAttackSt // Setup view if(state == DictAttackStateUserDictInProgress) { worker_state = NfcWorkerStateMfClassicDictAttack; - dict_attack_set_header(nfc->dict_attack, "Mf Classic User Dict."); + dict_attack_set_header(nfc->dict_attack, "MF Classic User Dictionary"); dict = mf_classic_dict_alloc(MfClassicDictTypeUser); - // If failed to load user dictionary - try flipper dictionary + // If failed to load user dictionary - try the system dictionary if(!dict) { FURI_LOG_E(TAG, "User dictionary not found"); state = DictAttackStateFlipperDictInProgress; @@ -64,11 +64,11 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(Nfc* nfc, DictAttackSt } if(state == DictAttackStateFlipperDictInProgress) { worker_state = NfcWorkerStateMfClassicDictAttack; - dict_attack_set_header(nfc->dict_attack, "Mf Classic Flipper Dict."); - dict = mf_classic_dict_alloc(MfClassicDictTypeFlipper); + dict_attack_set_header(nfc->dict_attack, "MF Classic System Dictionary"); + dict = mf_classic_dict_alloc(MfClassicDictTypeSystem); if(!dict) { FURI_LOG_E(TAG, "Flipper dictionary not found"); - // Pass through to let worker handle the failure + // Pass through to let the worker handle the failure } } // Free previous dictionary @@ -153,6 +153,15 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent nfc_worker_stop(nfc->worker); consumed = true; } + } else if(event.event == NfcWorkerEventKeyAttackStart) { + dict_attack_set_key_attack( + nfc->dict_attack, + true, + nfc->dev->dev_data.mf_classic_dict_attack_data.current_sector); + } else if(event.event == NfcWorkerEventKeyAttackStop) { + dict_attack_set_key_attack(nfc->dict_attack, false, 0); + } else if(event.event == NfcWorkerEventKeyAttackNextSector) { + dict_attack_inc_key_attack_current_sector(nfc->dict_attack); } } else if(event.type == SceneManagerEventTypeBack) { scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index dee9553d4..8a7dc2c18 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -12,7 +12,7 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) { // Load flipper dict keys total uint32_t flipper_dict_keys_total = 0; - MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeFlipper); + MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeSystem); if(dict) { flipper_dict_keys_total = mf_classic_dict_get_total_keys(dict); mf_classic_dict_free(dict); @@ -26,11 +26,11 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) { } widget_add_string_element( - nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Mifare Classic Keys"); + nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Classic Keys"); char temp_str[32]; - snprintf(temp_str, sizeof(temp_str), "Flipper list: %lu", flipper_dict_keys_total); + snprintf(temp_str, sizeof(temp_str), "System dict: %lu", flipper_dict_keys_total); widget_add_string_element(nfc->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, temp_str); - snprintf(temp_str, sizeof(temp_str), "User list: %lu", user_dict_keys_total); + snprintf(temp_str, sizeof(temp_str), "User dict: %lu", user_dict_keys_total); widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str); widget_add_button_element( nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc); diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 2607cbd8f..4252883b2 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -91,7 +91,7 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { DOLPHIN_DEED(DolphinDeedNfcReadSuccess); consumed = true; } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) { - if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) { + if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack); } else { scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index c5f55ae76..5ae204a06 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -24,6 +24,8 @@ typedef struct { uint8_t keys_found; uint16_t dict_keys_total; uint16_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { @@ -36,10 +38,19 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); } else if(m->state == DictAttackStateRead) { char draw_str[32] = {}; - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned( - canvas, 64, 2, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->sector_current); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); float dict_progress = m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total); @@ -49,13 +60,14 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { if(progress > 1.0) { progress = 1.0; } - elements_progress_bar(canvas, 5, 15, 120, progress); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->dict_keys_current, m->dict_keys_total); + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); canvas_set_font(canvas, FontSecondary); snprintf(draw_str, sizeof(draw_str), "Keys found: %d/%d", m->keys_found, m->keys_total); - canvas_draw_str_aligned(canvas, 1, 28, AlignLeft, AlignTop, draw_str); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); snprintf( draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); - canvas_draw_str_aligned(canvas, 1, 40, AlignLeft, AlignTop, draw_str); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } elements_button_center(canvas, "Skip"); } @@ -113,6 +125,7 @@ void dict_attack_reset(DictAttack* dict_attack) { model->keys_found = 0; model->dict_keys_total = 0; model->dict_keys_current = 0; + model->is_key_attack = false; furi_string_reset(model->header); }, false); @@ -235,3 +248,28 @@ void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tri }, true); } + +void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + model->is_key_attack = is_key_attack; + model->key_attack_current_sector = sector; + }, + true); +} + +void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack) { + furi_assert(dict_attack); + with_view_model( + dict_attack->view, + DictAttackViewModel * model, + { + if(model->key_attack_current_sector < model->sectors_total) { + model->key_attack_current_sector++; + } + }, + true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 684f17f06..2839534a7 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -38,3 +38,7 @@ void dict_attack_inc_keys_found(DictAttack* dict_attack); void dict_attack_set_total_dict_keys(DictAttack* dict_attack, uint16_t dict_keys_total); void dict_attack_inc_current_dict_key(DictAttack* dict_attack, uint16_t keys_tried); + +void dict_attack_set_key_attack(DictAttack* dict_attack, bool is_key_attack, uint8_t sector); + +void dict_attack_inc_key_attack_current_sector(DictAttack* dict_attack); \ No newline at end of file diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index a3df5adc7..c4eb8649f 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -17,6 +17,7 @@ extern "C" { typedef enum { ColorWhite = 0x00, ColorBlack = 0x01, + ColorXOR = 0x02, } Color; /** Fonts enumeration */ diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index cd4c105ae..e22889bf9 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -41,6 +41,31 @@ void elements_progress_bar(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, canvas_draw_box(canvas, x + 1, y + 1, progress_length, height - 2); } +void elements_progress_bar_with_text( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + float progress, + const char* text) { + furi_assert(canvas); + furi_assert((progress >= 0.0f) && (progress <= 1.0f)); + uint8_t height = 11; + + uint8_t progress_length = roundf(progress * (width - 2)); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, x + 1, y + 1, width - 2, height - 2); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, x, y, width, height, 3); + + canvas_draw_box(canvas, x + 1, y + 1, progress_length, height - 2); + + canvas_set_color(canvas, ColorXOR); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, x + width / 2, y + 2, AlignCenter, AlignTop, text); +} + void elements_scrollbar_pos( Canvas* canvas, uint8_t x, diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 162f0d410..485335131 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -31,6 +31,23 @@ extern "C" { */ void elements_progress_bar(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, float progress); +/** Draw progress bar with text. + * + * @param canvas Canvas instance + * @param x progress bar position on X axis + * @param y progress bar position on Y axis + * @param width progress bar width + * @param progress progress (0.0 - 1.0) + * @param text text to draw + */ +void elements_progress_bar_with_text( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + float progress, + const char* text); + /** Draw scrollbar on canvas at specific position. * * @param canvas Canvas instance diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8503a838a..72d44b501 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,11.2,, +Version,+,11.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -562,8 +562,8 @@ Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* -Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" Function,+,bt_keys_storage_set_default_path,void,Bt* +Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* @@ -754,6 +754,7 @@ Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" +Function,+,elements_progress_bar_with_text,void,"Canvas*, uint8_t, uint8_t, uint8_t, float, const char*" Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c index 98076479f..93098d409 100644 --- a/lib/nfc/helpers/mf_classic_dict.c +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -20,7 +20,7 @@ bool mf_classic_dict_check_presence(MfClassicDictType dict_type) { Storage* storage = furi_record_open(RECORD_STORAGE); bool dict_present = false; - if(dict_type == MfClassicDictTypeFlipper) { + if(dict_type == MfClassicDictTypeSystem) { dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; } else if(dict_type == MfClassicDictTypeUser) { dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; @@ -42,7 +42,7 @@ MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { bool dict_loaded = false; do { - if(dict_type == MfClassicDictTypeFlipper) { + if(dict_type == MfClassicDictTypeSystem) { if(!buffered_file_stream_open( dict->stream, MF_CLASSIC_DICT_FLIPPER_PATH, diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h index 5b0ee312a..3b2d560ad 100644 --- a/lib/nfc/helpers/mf_classic_dict.h +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -8,7 +8,7 @@ typedef enum { MfClassicDictTypeUser, - MfClassicDictTypeFlipper, + MfClassicDictTypeSystem, MfClassicDictTypeUnitTest, } MfClassicDictType; diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 55ee4ac4c..8b2e6e5ba 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -18,7 +18,7 @@ extern "C" { #define NFC_DEV_NAME_MAX_LEN 22 #define NFC_READER_DATA_MAX_SIZE 64 -#define NFC_DICT_KEY_BATCH_SIZE 50 +#define NFC_DICT_KEY_BATCH_SIZE 10 #define NFC_APP_EXTENSION ".nfc" #define NFC_APP_SHADOW_EXTENSION ".shd" @@ -48,6 +48,7 @@ typedef struct { typedef struct { MfClassicDict* dict; + uint8_t current_sector; } NfcMfClassicDictAttackData; typedef enum { diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 0ffe1d07b..4a66593cb 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -573,17 +573,24 @@ static void nfc_worker_mf_classic_key_attack( FuriHalNfcTxRxContext* tx_rx, uint16_t start_sector) { furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); bool card_found_notified = true; bool card_removed_notified = false; MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; + NfcMfClassicDictAttackData* dict_attack_data = + &nfc_worker->dev_data->mf_classic_dict_attack_data; uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); furi_assert(start_sector < total_sectors); + nfc_worker->callback(NfcWorkerEventKeyAttackStart, nfc_worker->context); + // Check every sector's A and B keys with the given key for(size_t i = start_sector; i < total_sectors; i++) { + nfc_worker->callback(NfcWorkerEventKeyAttackNextSector, nfc_worker->context); + dict_attack_data->current_sector = i; furi_hal_nfc_sleep(); if(furi_hal_nfc_activate_nfca(200, NULL)) { furi_hal_nfc_sleep(); @@ -632,6 +639,7 @@ static void nfc_worker_mf_classic_key_attack( } if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; } + nfc_worker->callback(NfcWorkerEventKeyAttackStop, nfc_worker->context); } void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index fdcaa72fd..ce542828a 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -55,6 +55,9 @@ typedef enum { NfcWorkerEventNewDictKeyBatch, NfcWorkerEventFoundKeyA, NfcWorkerEventFoundKeyB, + NfcWorkerEventKeyAttackStart, + NfcWorkerEventKeyAttackStop, + NfcWorkerEventKeyAttackNextSector, // Write Mifare Classic events NfcWorkerEventWrongCard, From 727f043747ad337a035ea946733e42be7fd166cb Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Tue, 27 Dec 2022 22:59:36 +1000 Subject: [PATCH 7/7] OpenOCD scripts (#2101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Scripts: option bytes check * Scripts: option bytes set * Scripts: openocd config * Scripts: increased readability, process IPCCBR option byte * Scripts: split dap_ob.py * Updater: process IPCCBR option byte * Scripts: move chip-related functions to chip definition * Scripts: freeze CPU registers * Scripts: flash programming routine * ob.py * otp.py * otp: handle errors correctly * downgrade to python 3.9 * correct type hinting * Scripts: fix path to ob.data Co-authored-by: あく --- firmware/targets/f7/furi_hal/furi_hal_flash.c | 2 +- scripts/flipper/utils/openocd.py | 173 +++++++++ scripts/flipper/utils/programmer.py | 31 ++ scripts/flipper/utils/programmer_openocd.py | 281 ++++++++++++++ scripts/flipper/utils/register.py | 95 +++++ scripts/flipper/utils/stm32wb55.py | 352 ++++++++++++++++++ scripts/ob.py | 92 +++-- scripts/otp.py | 75 ++-- 8 files changed, 1035 insertions(+), 66 deletions(-) create mode 100644 scripts/flipper/utils/openocd.py create mode 100644 scripts/flipper/utils/programmer.py create mode 100644 scripts/flipper/utils/programmer_openocd.py create mode 100644 scripts/flipper/utils/register.py create mode 100644 scripts/flipper/utils/stm32wb55.py diff --git a/firmware/targets/f7/furi_hal/furi_hal_flash.c b/firmware/targets/f7/furi_hal/furi_hal_flash.c index e49cd5f29..fc021d969 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_flash.c +++ b/firmware/targets/f7/furi_hal/furi_hal_flash.c @@ -488,7 +488,7 @@ static const FuriHalFlashObMapping furi_hal_flash_ob_reg_map[FURI_HAL_FLASH_OB_T OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), OB_REG_DEF(FuriHalFlashObInvalid, (NULL)), - OB_REG_DEF(FuriHalFlashObRegisterIPCCMail, (NULL)), + OB_REG_DEF(FuriHalFlashObRegisterIPCCMail, (&FLASH->IPCCBR)), OB_REG_DEF(FuriHalFlashObRegisterSecureFlash, (NULL)), OB_REG_DEF(FuriHalFlashObRegisterC2Opts, (NULL)), }; diff --git a/scripts/flipper/utils/openocd.py b/scripts/flipper/utils/openocd.py new file mode 100644 index 000000000..1309055b8 --- /dev/null +++ b/scripts/flipper/utils/openocd.py @@ -0,0 +1,173 @@ +import socket +import subprocess +import logging + + +class OpenOCD: + """OpenOCD cli wrapper""" + + COMMAND_TOKEN = "\x1a" + + def __init__(self, config: dict = {}) -> None: + assert isinstance(config, dict) + + # Params base + self.params = [] + + self.gdb_port = 3333 + self.telnet_port = 4444 + self.tcl_port = 6666 + + # Port + if port_base := config.get("port_base", None): + self.gdb_port = port_base + self.tcl_port = port_base + 1 + self.telnet_port = port_base + 2 + + self._add_command(f"gdb_port {self.gdb_port}") + self._add_command(f"tcl_port {self.tcl_port}") + self._add_command(f"telnet_port {self.telnet_port}") + + # Config files + + if interface := config.get("interface", None): + pass + else: + interface = "interface/stlink.cfg" + + if target := config.get("target", None): + pass + else: + target = "target/stm32wbx.cfg" + + self._add_file(interface) + self._add_file(target) + + # Programmer settings + if serial := config.get("serial", None): + self._add_command(f"{serial}") + + # Other params + if "params" in config: + self.params += config["params"] + + # logging + self.logger = logging.getLogger() + + def _add_command(self, command: str): + self.params.append("-c") + self.params.append(command) + + def _add_file(self, file: str): + self.params.append("-f") + self.params.append(file) + + def start(self, args: list[str] = []): + """Start OpenOCD process""" + + params = ["openocd", *self.params, *args] + self.logger.debug(f"_execute: {params}") + self.process = subprocess.Popen( + params, stderr=subprocess.PIPE, stdout=subprocess.PIPE + ) + + self._wait_for_openocd_tcl() + + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.connect(("127.0.0.1", self.tcl_port)) + + def _wait_for_openocd_tcl(self): + """Wait for OpenOCD to start""" + # TODO: timeout + while True: + stderr = self.process.stderr + if not stderr: + break + line = stderr.readline() + if not line: + break + line = line.decode("utf-8").strip() + self.logger.debug(f"OpenOCD: {line}") + if "Listening on port" in line and "for tcl connections" in line: + break + + def stop(self): + self.send_tcl("exit") + self.send_tcl("shutdown") + self.socket.close() + try: + self.process.wait(timeout=10) + except subprocess.TimeoutExpired as e: + self.process.kill() + self.logger.error("Failed to stop OpenOCD") + self.logger.exception(e) + self.postmortem() + + def send_tcl(self, cmd) -> str: + """Send a command string to TCL RPC. Return the result that was read.""" + + try: + data = (cmd + OpenOCD.COMMAND_TOKEN).encode("utf-8") + self.logger.debug(f"<- {data}") + + self.socket.send(data) + except Exception as e: + self.logger.error("Failed to send command to OpenOCD") + self.logger.exception(e) + self.postmortem() + raise + + try: + data = self._recv() + return data + except Exception as e: + self.logger.error("Failed to receive response from OpenOCD") + self.logger.exception(e) + self.postmortem() + raise + + def _recv(self): + """Read from the stream until the token (\x1a) was received.""" + # TODO: timeout + data = bytes() + while True: + chunk = self.socket.recv(4096) + data += chunk + if bytes(OpenOCD.COMMAND_TOKEN, encoding="utf-8") in chunk: + break + + self.logger.debug(f"-> {data}") + + data = data.decode("utf-8").strip() + data = data[:-1] # strip trailing \x1a + + return data + + def postmortem(self) -> None: + """Postmortem analysis of the OpenOCD process""" + stdout, stderr = self.process.communicate() + + log = self.logger.error + if self.process.returncode == 0: + log = self.logger.debug + log("OpenOCD exited normally") + else: + log("OpenOCD exited with error") + + log(f"Exit code: {self.process.returncode}") + for line in stdout.decode("utf-8").splitlines(): + log(f"Stdout: {line}") + + for line in stderr.decode("utf-8").splitlines(): + log(f"Stderr: {line}") + + def read_32(self, addr: int) -> int: + """Read 32-bit value from memory""" + data = self.send_tcl(f"mdw {addr}").strip() + data = data.split(": ")[-1] + data = int(data, 16) + return data + + def write_32(self, addr: int, value: int) -> None: + """Write 32-bit value to memory""" + self.send_tcl(f"mww {addr} {value}") diff --git a/scripts/flipper/utils/programmer.py b/scripts/flipper/utils/programmer.py new file mode 100644 index 000000000..84452d154 --- /dev/null +++ b/scripts/flipper/utils/programmer.py @@ -0,0 +1,31 @@ +from abc import ABC, abstractmethod +from enum import Enum + + +class Programmer(ABC): + def __init__(self): + pass + + class RunMode(Enum): + Run = "run" + Stop = "stop" + + @abstractmethod + def reset(self, mode: RunMode = RunMode.Run) -> bool: + pass + + @abstractmethod + def flash(self, address: int, file_path: str, verify: bool = True) -> bool: + pass + + @abstractmethod + def option_bytes_validate(self, file_path: str) -> bool: + pass + + @abstractmethod + def option_bytes_set(self, file_path: str) -> bool: + pass + + @abstractmethod + def otp_write(self, address: int, file_path: str) -> bool: + pass diff --git a/scripts/flipper/utils/programmer_openocd.py b/scripts/flipper/utils/programmer_openocd.py new file mode 100644 index 000000000..b33406103 --- /dev/null +++ b/scripts/flipper/utils/programmer_openocd.py @@ -0,0 +1,281 @@ +import logging +import os +import typing + +from flipper.utils.programmer import Programmer +from flipper.utils.openocd import OpenOCD +from flipper.utils.stm32wb55 import STM32WB55 +from flipper.assets.obdata import OptionBytesData + + +class OpenOCDProgrammer(Programmer): + def __init__( + self, + interface: str = "interface/cmsis-dap.cfg", + port_base: typing.Union[int, None] = None, + serial: typing.Union[str, None] = None, + ): + super().__init__() + + config = {} + + config["interface"] = interface + config["target"] = "target/stm32wbx.cfg" + + if not serial is None: + if interface == "interface/cmsis-dap.cfg": + config["serial"] = f"cmsis_dap_serial {serial}" + elif "stlink" in interface: + config["serial"] = f"stlink_serial {serial}" + + if not port_base is None: + config["port_base"] = port_base + + self.openocd = OpenOCD(config) + self.logger = logging.getLogger() + + def reset(self, mode: Programmer.RunMode = Programmer.RunMode.Run) -> bool: + stm32 = STM32WB55() + if mode == Programmer.RunMode.Run: + stm32.reset(self.openocd, stm32.RunMode.Run) + elif mode == Programmer.RunMode.Stop: + stm32.reset(self.openocd, stm32.RunMode.Init) + else: + raise Exception("Unknown mode") + + return True + + def flash(self, address: int, file_path: str, verify: bool = True) -> bool: + if not os.path.exists(file_path): + raise Exception(f"File {file_path} not found") + + self.openocd.start() + self.openocd.send_tcl(f"init") + self.openocd.send_tcl( + f"program {file_path} 0x{address:08x}{' verify' if verify else ''} reset exit" + ) + self.openocd.stop() + + return True + + def _ob_print_diff_table(self, ob_reference: bytes, ob_read: bytes, print_fn): + print_fn( + f'{"Reference": <20} {"Device": <20} {"Diff Reference": <20} {"Diff Device": <20}' + ) + + # Split into 8 byte, word + word + for i in range(0, len(ob_reference), 8): + ref = ob_reference[i : i + 8] + read = ob_read[i : i + 8] + + diff_str1 = "" + diff_str2 = "" + for j in range(0, len(ref.hex()), 2): + byte_str_1 = ref.hex()[j : j + 2] + byte_str_2 = read.hex()[j : j + 2] + + if byte_str_1 == byte_str_2: + diff_str1 += "__" + diff_str2 += "__" + else: + diff_str1 += byte_str_1 + diff_str2 += byte_str_2 + + print_fn( + f"{ref.hex(): <20} {read.hex(): <20} {diff_str1: <20} {diff_str2: <20}" + ) + + def option_bytes_validate(self, file_path: str) -> bool: + # Registers + stm32 = STM32WB55() + + # OpenOCD + self.openocd.start() + stm32.reset(self.openocd, stm32.RunMode.Init) + + # Generate Option Bytes data + ob_data = OptionBytesData(file_path) + ob_values = ob_data.gen_values().export() + ob_reference = ob_values.reference + ob_compare_mask = ob_values.compare_mask + ob_length = len(ob_reference) + ob_words = int(ob_length / 4) + + # Read Option Bytes + ob_read = bytes() + for i in range(ob_words): + addr = stm32.OPTION_BYTE_BASE + i * 4 + value = self.openocd.read_32(addr) + ob_read += value.to_bytes(4, "little") + + # Compare Option Bytes with reference by mask + ob_compare = bytes() + for i in range(ob_length): + ob_compare += bytes([ob_read[i] & ob_compare_mask[i]]) + + # Compare Option Bytes + return_code = False + + if ob_reference == ob_compare: + self.logger.info("Option Bytes are valid") + return_code = True + else: + self.logger.error("Option Bytes are invalid") + self._ob_print_diff_table(ob_reference, ob_compare, self.logger.error) + + # Stop OpenOCD + stm32.reset(self.openocd, stm32.RunMode.Run) + self.openocd.stop() + + return return_code + + def _unpack_u32(self, data: bytes, offset: int): + return int.from_bytes(data[offset : offset + 4], "little") + + def option_bytes_set(self, file_path: str) -> bool: + # Registers + stm32 = STM32WB55() + + # OpenOCD + self.openocd.start() + stm32.reset(self.openocd, stm32.RunMode.Init) + + # Generate Option Bytes data + ob_data = OptionBytesData(file_path) + ob_values = ob_data.gen_values().export() + ob_reference_bytes = ob_values.reference + ob_compare_mask_bytes = ob_values.compare_mask + ob_write_mask_bytes = ob_values.write_mask + ob_length = len(ob_reference_bytes) + ob_dwords = int(ob_length / 8) + + # Clear flash errors + stm32.clear_flash_errors(self.openocd) + + # Unlock Flash and Option Bytes + stm32.flash_unlock(self.openocd) + stm32.option_bytes_unlock(self.openocd) + + ob_need_to_apply = False + + for i in range(ob_dwords): + device_addr = stm32.OPTION_BYTE_BASE + i * 8 + device_value = self.openocd.read_32(device_addr) + ob_write_mask = self._unpack_u32(ob_write_mask_bytes, i * 8) + ob_compare_mask = self._unpack_u32(ob_compare_mask_bytes, i * 8) + ob_value_ref = self._unpack_u32(ob_reference_bytes, i * 8) + ob_value_masked = device_value & ob_compare_mask + + need_patch = ((ob_value_masked ^ ob_value_ref) & ob_write_mask) != 0 + if need_patch: + ob_need_to_apply = True + + self.logger.info( + f"Need to patch: {device_addr:08X}: {ob_value_masked:08X} != {ob_value_ref:08X}, REG[{i}]" + ) + + # Check if this option byte (dword) is mapped to a register + device_reg_addr = stm32.option_bytes_id_to_address(i) + + # Construct new value for the OB register + ob_value = device_value & (~ob_write_mask) + ob_value |= ob_value_ref & ob_write_mask + + self.logger.info(f"Writing {ob_value:08X} to {device_reg_addr:08X}") + self.openocd.write_32(device_reg_addr, ob_value) + + if ob_need_to_apply: + stm32.option_bytes_apply(self.openocd) + else: + self.logger.info(f"Option Bytes are already correct") + + # Load Option Bytes + # That will reset and also lock the Option Bytes and the Flash + stm32.option_bytes_load(self.openocd) + + # Stop OpenOCD + stm32.reset(self.openocd, stm32.RunMode.Run) + self.openocd.stop() + + return True + + def otp_write(self, address: int, file_path: str) -> bool: + # Open file, check that it aligned to 8 bytes + with open(file_path, "rb") as f: + data = f.read() + if len(data) % 8 != 0: + self.logger.error(f"File {file_path} is not aligned to 8 bytes") + return False + + # Check that address is aligned to 8 bytes + if address % 8 != 0: + self.logger.error(f"Address {address} is not aligned to 8 bytes") + return False + + # Get size of data + data_size = len(data) + + # Check that data size is aligned to 8 bytes + if data_size % 8 != 0: + self.logger.error(f"Data size {data_size} is not aligned to 8 bytes") + return False + + self.logger.debug(f"Writing {data_size} bytes to OTP at {address:08X}") + self.logger.debug(f"Data: {data.hex().upper()}") + + # Start OpenOCD + oocd = self.openocd + oocd.start() + + # Registers + stm32 = STM32WB55() + + try: + # Check that OTP is empty for the given address + # Also check that data is already written + already_written = True + for i in range(0, data_size, 4): + file_word = int.from_bytes(data[i : i + 4], "little") + device_word = oocd.read_32(address + i) + if device_word != 0xFFFFFFFF and device_word != file_word: + self.logger.error( + f"OTP memory at {address + i:08X} is not empty: {device_word:08X}" + ) + raise Exception("OTP memory is not empty") + + if device_word != file_word: + already_written = False + + if already_written: + self.logger.info(f"OTP memory is already written with the given data") + return True + + self.reset(self.RunMode.Stop) + stm32.clear_flash_errors(oocd) + + # Write OTP memory by 8 bytes + for i in range(0, data_size, 8): + word_1 = int.from_bytes(data[i : i + 4], "little") + word_2 = int.from_bytes(data[i + 4 : i + 8], "little") + self.logger.debug( + f"Writing {word_1:08X} {word_2:08X} to {address + i:08X}" + ) + stm32.write_flash_64(oocd, address + i, word_1, word_2) + + # Validate OTP memory + validation_result = True + + for i in range(0, data_size, 4): + file_word = int.from_bytes(data[i : i + 4], "little") + device_word = oocd.read_32(address + i) + if file_word != device_word: + self.logger.error( + f"Validation failed: {file_word:08X} != {device_word:08X} at {address + i:08X}" + ) + validation_result = False + finally: + # Stop OpenOCD + stm32.reset(oocd, stm32.RunMode.Run) + oocd.stop() + + return validation_result diff --git a/scripts/flipper/utils/register.py b/scripts/flipper/utils/register.py new file mode 100644 index 000000000..26d66730c --- /dev/null +++ b/scripts/flipper/utils/register.py @@ -0,0 +1,95 @@ +from dataclasses import dataclass +from flipper.utils.openocd import OpenOCD + + +@dataclass +class RegisterBitDefinition: + name: str + offset: int + size: int + value: int = 0 + + +class Register32: + def __init__(self, address: int, definition_list: list[RegisterBitDefinition]): + self.__dict__["names"] = [definition.name for definition in definition_list] + self.names = [definition.name for definition in definition_list] # typecheck + self.address = address + self.definition_list = definition_list + + # Validate that the definitions are not overlapping + for i in range(len(definition_list)): + for j in range(i + 1, len(definition_list)): + if self._is_overlapping(definition_list[i], definition_list[j]): + raise ValueError("Register definitions are overlapping") + + self.freezed = True + + def _is_overlapping( + self, a: RegisterBitDefinition, b: RegisterBitDefinition + ) -> bool: + if a.offset + a.size <= b.offset: + return False + if b.offset + b.size <= a.offset: + return False + return True + + def _get_definition(self, name: str) -> RegisterBitDefinition: + for definition in self.definition_list: + if definition.name == name: + return definition + raise ValueError(f"Register definition '{name}' not found") + + def get_definition_list(self) -> list[RegisterBitDefinition]: + return self.definition_list + + def get_address(self) -> int: + return self.address + + def set_reg_value(self, name: str, value: int): + definition = self._get_definition(name) + if value > (1 << definition.size) - 1: + raise ValueError( + f"Value {value} is too large for register definition '{name}'" + ) + definition.value = value + + def get_reg_value(self, name: str) -> int: + definition = self._get_definition(name) + return definition.value + + def __getattr__(self, attr): + if str(attr) in self.names: + return self.get_reg_value(str(attr)) + else: + return self.__dict__[attr] + + def __setattr__(self, attr, value): + if str(attr) in self.names: + self.set_reg_value(str(attr), value) + else: + if attr in self.__dict__ or "freezed" not in self.__dict__: + self.__dict__[attr] = value + else: + raise AttributeError(f"Attribute '{attr}' not found") + + def __dir__(self): + return self.names + + def set(self, value: int): + for definition in self.definition_list: + definition.value = (value >> definition.offset) & ( + (1 << definition.size) - 1 + ) + + def get(self) -> int: + value = 0 + for definition in self.definition_list: + value |= definition.value << definition.offset + return value + + def load(self, openocd: OpenOCD): + self.set(openocd.read_32(self.address)) + + def store(self, openocd: OpenOCD): + openocd.write_32(self.address, self.get()) diff --git a/scripts/flipper/utils/stm32wb55.py b/scripts/flipper/utils/stm32wb55.py new file mode 100644 index 000000000..910b0d7d6 --- /dev/null +++ b/scripts/flipper/utils/stm32wb55.py @@ -0,0 +1,352 @@ +import logging +from enum import Enum + +from flipper.utils.openocd import OpenOCD +from flipper.utils.register import Register32, RegisterBitDefinition + + +class STM32WB55: + # Address of OTP memory in flash + OTP_BASE = 0x1FFF7000 + + # Address of Option byte in flash + OPTION_BYTE_BASE = 0x1FFF8000 + + # Flash base address + FLASH_BASE = 0x58004000 + + # Flash unlock register + FLASH_KEYR = FLASH_BASE + 0x08 + + # Option byte unlock register + FLASH_OPTKEYR = FLASH_BASE + 0x0C + + # Flash unlock keys + FLASH_UNLOCK_KEY1 = 0x45670123 + FLASH_UNLOCK_KEY2 = 0xCDEF89AB + + # Option byte unlock keys + FLASH_UNLOCK_OPTKEY1 = 0x08192A3B + FLASH_UNLOCK_OPTKEY2 = 0x4C5D6E7F + + # Flash control register + FLASH_CR = Register32( + FLASH_BASE + 0x14, + [ + RegisterBitDefinition("PG", 0, 1), + RegisterBitDefinition("PER", 1, 1), + RegisterBitDefinition("MER", 2, 1), + RegisterBitDefinition("PNB", 3, 8), + RegisterBitDefinition("_", 11, 5), + RegisterBitDefinition("STRT", 16, 1), + RegisterBitDefinition("OPT_STRT", 17, 1), + RegisterBitDefinition("FSTPG", 18, 1), + RegisterBitDefinition("_", 19, 5), + RegisterBitDefinition("EOPIE", 24, 1), + RegisterBitDefinition("ERRIE", 25, 1), + RegisterBitDefinition("RD_ERRIE", 26, 1), + RegisterBitDefinition("OBL_LAUNCH", 27, 1), + RegisterBitDefinition("_", 28, 2), + RegisterBitDefinition("OPT_LOCK", 30, 1), + RegisterBitDefinition("LOCK", 31, 1), + ], + ) + + # Flash status register + FLASH_SR = Register32( + FLASH_BASE + 0x10, + [ + RegisterBitDefinition("EOP", 0, 1), + RegisterBitDefinition("OP_ERR", 1, 1), + RegisterBitDefinition("_", 2, 1), + RegisterBitDefinition("PROG_ERR", 3, 1), + RegisterBitDefinition("WRP_ERR", 4, 1), + RegisterBitDefinition("PGA_ERR", 5, 1), + RegisterBitDefinition("SIZE_ERR", 6, 1), + RegisterBitDefinition("PGS_ERR", 7, 1), + RegisterBitDefinition("MISS_ERR", 8, 1), + RegisterBitDefinition("FAST_ERR", 9, 1), + RegisterBitDefinition("_", 10, 3), + RegisterBitDefinition("OPTNV", 13, 1), + RegisterBitDefinition("RD_ERR", 14, 1), + RegisterBitDefinition("OPTV_ERR", 15, 1), + RegisterBitDefinition("BSY", 16, 1), + RegisterBitDefinition("_", 17, 1), + RegisterBitDefinition("CFGBSY", 18, 1), + RegisterBitDefinition("PESD", 19, 1), + RegisterBitDefinition("_", 20, 12), + ], + ) + + # Option byte registers + FLASH_OPTR = FLASH_BASE + 0x20 + FLASH_PCROP1ASR = FLASH_BASE + 0x24 + FLASH_PCROP1AER = FLASH_BASE + 0x28 + FLASH_WRP1AR = FLASH_BASE + 0x2C + FLASH_WRP1BR = FLASH_BASE + 0x30 + FLASH_PCROP1BSR = FLASH_BASE + 0x34 + FLASH_PCROP1BER = FLASH_BASE + 0x38 + FLASH_IPCCBR = FLASH_BASE + 0x3C + + # Map option byte dword index to register address + OPTION_BYTE_MAP_TO_REGS = { + 0: FLASH_OPTR, + 1: FLASH_PCROP1ASR, + 2: FLASH_PCROP1AER, + 3: FLASH_WRP1AR, + 4: FLASH_WRP1BR, + 5: FLASH_PCROP1BSR, + 6: FLASH_PCROP1BER, + 7: None, # Invalid Options + 8: None, # Invalid Options + 9: None, # Invalid Options + 10: None, # Invalid Options + 11: None, # Invalid Options + 12: None, # Invalid Options + 13: FLASH_IPCCBR, + 14: None, # Secure Flash + 15: None, # Core 2 Options + } + + def __init__(self): + self.logger = logging.getLogger("STM32WB55") + + class RunMode(Enum): + Init = "init" + Run = "run" + Halt = "halt" + + def reset(self, oocd: OpenOCD, mode: RunMode): + self.logger.debug("Resetting device") + oocd.send_tcl(f"reset {mode.value}") + + def clear_flash_errors(self, oocd: OpenOCD): + # Errata 2.2.9: Flash OPTVERR flag is always set after system reset + # And also clear all other flash error flags + self.logger.debug(f"Resetting flash errors") + self.FLASH_SR.load(oocd) + self.FLASH_SR.OP_ERR = 1 + self.FLASH_SR.PROG_ERR = 1 + self.FLASH_SR.WRP_ERR = 1 + self.FLASH_SR.PGA_ERR = 1 + self.FLASH_SR.SIZE_ERR = 1 + self.FLASH_SR.PGS_ERR = 1 + self.FLASH_SR.MISS_ERR = 1 + self.FLASH_SR.FAST_ERR = 1 + self.FLASH_SR.RD_ERR = 1 + self.FLASH_SR.OPTV_ERR = 1 + self.FLASH_SR.store(oocd) + + def flash_unlock(self, oocd: OpenOCD): + # Check if flash is already unlocked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.LOCK == 0: + self.logger.debug("Flash is already unlocked") + return + + # Unlock flash + self.logger.debug("Unlocking Flash") + oocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY1) + oocd.write_32(self.FLASH_KEYR, self.FLASH_UNLOCK_KEY2) + + # Check if flash is unlocked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.LOCK == 0: + self.logger.debug("Flash unlocked") + else: + self.logger.error("Flash unlock failed") + raise Exception("Flash unlock failed") + + def option_bytes_unlock(self, oocd: OpenOCD): + # Check if options is already unlocked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.OPT_LOCK == 0: + self.logger.debug("Options is already unlocked") + return + + # Unlock options + self.logger.debug("Unlocking Options") + oocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY1) + oocd.write_32(self.FLASH_OPTKEYR, self.FLASH_UNLOCK_OPTKEY2) + + # Check if options is unlocked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.OPT_LOCK == 0: + self.logger.debug("Options unlocked") + else: + self.logger.error("Options unlock failed") + raise Exception("Options unlock failed") + + def option_bytes_lock(self, oocd: OpenOCD): + # Check if options is already locked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.OPT_LOCK == 1: + self.logger.debug("Options is already locked") + return + + # Lock options + self.logger.debug("Locking Options") + self.FLASH_CR.OPT_LOCK = 1 + self.FLASH_CR.store(oocd) + + # Check if options is locked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.OPT_LOCK == 1: + self.logger.debug("Options locked") + else: + self.logger.error("Options lock failed") + raise Exception("Options lock failed") + + def flash_lock(self, oocd: OpenOCD): + # Check if flash is already locked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.LOCK == 1: + self.logger.debug("Flash is already locked") + return + + # Lock flash + self.logger.debug("Locking Flash") + self.FLASH_CR.LOCK = 1 + self.FLASH_CR.store(oocd) + + # Check if flash is locked + self.FLASH_CR.load(oocd) + if self.FLASH_CR.LOCK == 1: + self.logger.debug("Flash locked") + else: + self.logger.error("Flash lock failed") + raise Exception("Flash lock failed") + + def option_bytes_apply(self, oocd: OpenOCD): + self.logger.debug(f"Applying Option Bytes") + + self.FLASH_CR.load(oocd) + self.FLASH_CR.OPT_STRT = 1 + self.FLASH_CR.store(oocd) + + # Wait for Option Bytes to be applied + self.flash_wait_for_operation(oocd) + + def option_bytes_load(self, oocd: OpenOCD): + self.logger.debug(f"Loading Option Bytes") + self.FLASH_CR.load(oocd) + self.FLASH_CR.OBL_LAUNCH = 1 + self.FLASH_CR.store(oocd) + + def option_bytes_id_to_address(self, id: int) -> int: + # Check if this option byte (dword) is mapped to a register + device_reg_addr = self.OPTION_BYTE_MAP_TO_REGS.get(id, None) + if device_reg_addr is None: + raise Exception(f"Option Byte {id} is not mapped to a register") + + return device_reg_addr + + def flash_wait_for_operation(self, oocd: OpenOCD): + # Wait for flash operation to complete + # TODO: timeout + while True: + self.FLASH_SR.load(oocd) + if self.FLASH_SR.BSY == 0: + break + + def flash_dump_status_register(self, oocd: OpenOCD): + self.FLASH_SR.load(oocd) + self.logger.info(f"FLASH_SR: {self.FLASH_SR.get():08x}") + if self.FLASH_SR.EOP: + self.logger.info(" End of operation") + if self.FLASH_SR.OP_ERR: + self.logger.error(" Operation error") + if self.FLASH_SR.PROG_ERR: + self.logger.error(" Programming error") + if self.FLASH_SR.WRP_ERR: + self.logger.error(" Write protection error") + if self.FLASH_SR.PGA_ERR: + self.logger.error(" Programming alignment error") + if self.FLASH_SR.SIZE_ERR: + self.logger.error(" Size error") + if self.FLASH_SR.PGS_ERR: + self.logger.error(" Programming sequence error") + if self.FLASH_SR.MISS_ERR: + self.logger.error(" Fast programming data miss error") + if self.FLASH_SR.FAST_ERR: + self.logger.error(" Fast programming error") + if self.FLASH_SR.OPTNV: + self.logger.info(" User option OPTVAL indication") + if self.FLASH_SR.RD_ERR: + self.logger.info(" PCROP read error") + if self.FLASH_SR.OPTV_ERR: + self.logger.info(" Option and Engineering bits loading validity error") + if self.FLASH_SR.BSY: + self.logger.info(" Busy") + if self.FLASH_SR.CFGBSY: + self.logger.info(" Programming or erase configuration busy") + if self.FLASH_SR.PESD: + self.logger.info(" Programming / erase operation suspended.") + + def write_flash_64(self, oocd: OpenOCD, address: int, word_1: int, word_2: int): + self.logger.debug(f"Writing flash at address {address:08x}") + + if address % 8 != 0: + self.logger.error("Address must be aligned to 8 bytes") + raise Exception("Address must be aligned to 8 bytes") + + if word_1 == oocd.read_32(address) and word_2 == oocd.read_32(address + 4): + self.logger.debug("Data is already programmed") + return + + self.flash_unlock(oocd) + + # Check that no flash main memory operation is ongoing by checking the BSY bit + self.FLASH_SR.load(oocd) + if self.FLASH_SR.BSY: + self.logger.error("Flash is busy") + self.flash_dump_status_register(oocd) + raise Exception("Flash is busy") + + # Enable end of operation interrupts and error interrupts + self.FLASH_CR.load(oocd) + self.FLASH_CR.EOPIE = 1 + self.FLASH_CR.ERRIE = 1 + self.FLASH_CR.store(oocd) + + # Check that flash memory program and erase operations are allowed + if self.FLASH_SR.PESD: + self.logger.error("Flash operations are not allowed") + self.flash_dump_status_register(oocd) + raise Exception("Flash operations are not allowed") + + # Check and clear all error programming flags due to a previous programming. + self.clear_flash_errors(oocd) + + # Set the PG bit in the Flash memory control register (FLASH_CR) + self.FLASH_CR.load(oocd) + self.FLASH_CR.PG = 1 + self.FLASH_CR.store(oocd) + + # Perform the data write operation at the desired memory address, only double word (64 bits) can be programmed. + # Write the first word + oocd.send_tcl(f"mww 0x{address:08x} 0x{word_1:08x}") + # Write the second word + oocd.send_tcl(f"mww 0x{(address + 4):08x} 0x{word_2:08x}") + + # Wait for the BSY bit to be cleared + self.flash_wait_for_operation(oocd) + + # Check that EOP flag is set in the FLASH_SR register + self.FLASH_SR.load(oocd) + if not self.FLASH_SR.EOP: + self.logger.error("Flash operation failed") + self.flash_dump_status_register(oocd) + raise Exception("Flash operation failed") + + # Clear the EOP flag + self.FLASH_SR.load(oocd) + self.FLASH_SR.EOP = 1 + self.FLASH_SR.store(oocd) + + # Clear the PG bit in the FLASH_CR register + self.FLASH_CR.load(oocd) + self.FLASH_CR.PG = 0 + self.FLASH_CR.store(oocd) + + self.flash_lock(oocd) diff --git a/scripts/ob.py b/scripts/ob.py index 722549d69..178fe16a7 100755 --- a/scripts/ob.py +++ b/scripts/ob.py @@ -1,69 +1,79 @@ #!/usr/bin/env python3 -import logging -import argparse -import subprocess -import sys -import os +from os import path from flipper.app import App -from flipper.cube import CubeProgrammer +from flipper.utils.programmer_openocd import OpenOCDProgrammer class Main(App): def init(self): + # Subparsers self.subparsers = self.parser.add_subparsers(help="sub-command help") + + # Check command self.parser_check = self.subparsers.add_parser( "check", help="Check Option Bytes" ) - self._addArgsSWD(self.parser_check) + self._add_args(self.parser_check) self.parser_check.set_defaults(func=self.check) + # Set command self.parser_set = self.subparsers.add_parser("set", help="Set Option Bytes") - self._addArgsSWD(self.parser_set) + self._add_args(self.parser_set) self.parser_set.set_defaults(func=self.set) - # OB - self.ob = {} - def _addArgsSWD(self, parser): + def _add_args(self, parser): parser.add_argument( - "--port", type=str, help="Port to connect: swd or usb1", default="swd" + "--port-base", type=int, help="OpenOCD port base", default=3333 + ) + parser.add_argument( + "--interface", + type=str, + help="OpenOCD interface", + default="interface/cmsis-dap.cfg", + ) + parser.add_argument( + "--serial", type=str, help="OpenOCD interface serial number" + ) + parser.add_argument( + "--ob-path", + type=str, + help="Option bytes file", + default=path.join(path.dirname(__file__), "ob.data"), ) - parser.add_argument("--serial", type=str, help="ST-Link Serial Number") - - def _getCubeParams(self): - return { - "port": self.args.port, - "serial": self.args.serial, - } - - def before(self): - self.logger.info(f"Loading Option Bytes data") - file_path = os.path.join(os.path.dirname(sys.argv[0]), "ob.data") - with open(file_path, "r") as file: - for line in file.readlines(): - k, v, o = line.split(":") - self.ob[k.strip()] = v.strip(), o.strip() def check(self): self.logger.info(f"Checking Option Bytes") - cp = CubeProgrammer(self._getCubeParams()) - if cp.checkOptionBytes(self.ob): - self.logger.info(f"OB Check OK") - return 0 - else: - self.logger.error(f"OB Check FAIL") - return 255 + + # OpenOCD + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + return_code = 1 + if openocd.option_bytes_validate(self.args.ob_path): + return_code = 0 + + return return_code def set(self): self.logger.info(f"Setting Option Bytes") - cp = CubeProgrammer(self._getCubeParams()) - if cp.setOptionBytes(self.ob): - self.logger.info(f"OB Set OK") - return 0 - else: - self.logger.error(f"OB Set FAIL") - return 255 + + # OpenOCD + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + return_code = 1 + if openocd.option_bytes_set(self.args.ob_path): + return_code = 0 + + return return_code if __name__ == "__main__": diff --git a/scripts/otp.py b/scripts/otp.py index 48056fd2a..3bfe30d2d 100755 --- a/scripts/otp.py +++ b/scripts/otp.py @@ -35,6 +35,7 @@ OTP_DISPLAYS = { from flipper.app import App from flipper.cube import CubeProgrammer +from flipper.utils.programmer_openocd import OpenOCDProgrammer class Main(App): @@ -53,21 +54,21 @@ class Main(App): self.parser_flash_first = self.subparsers.add_parser( "flash_first", help="Flash first block of OTP to device" ) - self._addArgsSWD(self.parser_flash_first) + self._addArgsOpenOCD(self.parser_flash_first) self._addFirstArgs(self.parser_flash_first) self.parser_flash_first.set_defaults(func=self.flash_first) # Flash Second self.parser_flash_second = self.subparsers.add_parser( "flash_second", help="Flash second block of OTP to device" ) - self._addArgsSWD(self.parser_flash_second) + self._addArgsOpenOCD(self.parser_flash_second) self._addSecondArgs(self.parser_flash_second) self.parser_flash_second.set_defaults(func=self.flash_second) # Flash All self.parser_flash_all = self.subparsers.add_parser( "flash_all", help="Flash OTP to device" ) - self._addArgsSWD(self.parser_flash_all) + self._addArgsOpenOCD(self.parser_flash_all) self._addFirstArgs(self.parser_flash_all) self._addSecondArgs(self.parser_flash_all) self.parser_flash_all.set_defaults(func=self.flash_all) @@ -75,17 +76,19 @@ class Main(App): self.logger = logging.getLogger() self.timestamp = datetime.datetime.now().timestamp() - def _addArgsSWD(self, parser): + def _addArgsOpenOCD(self, parser): parser.add_argument( - "--port", type=str, help="Port to connect: swd or usb1", default="swd" + "--port-base", type=int, help="OpenOCD port base", default=3333 + ) + parser.add_argument( + "--interface", + type=str, + help="OpenOCD interface", + default="interface/cmsis-dap.cfg", + ) + parser.add_argument( + "--serial", type=str, help="OpenOCD interface serial number" ) - parser.add_argument("--serial", type=str, help="ST-Link Serial Number") - - def _getCubeParams(self): - return { - "port": self.args.port, - "serial": self.args.serial, - } def _addFirstArgs(self, parser): parser.add_argument("--version", type=int, help="Version", required=True) @@ -173,14 +176,22 @@ class Main(App): file.write(self._packFirst()) self.logger.info(f"Flashing OTP") - cp = CubeProgrammer(self._getCubeParams()) - cp.flashBin("0x1FFF7000", filename) - cp.resetTarget() + + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + if not openocd.otp_write(0x1FFF7000, filename): + raise Exception("Failed to flash OTP") + self.logger.info(f"Flashed Successfully") - os.remove(filename) except Exception as e: self.logger.exception(e) return 1 + finally: + os.remove(filename) return 0 @@ -197,14 +208,22 @@ class Main(App): file.write(self._packSecond()) self.logger.info(f"Flashing OTP") - cp = CubeProgrammer(self._getCubeParams()) - cp.flashBin("0x1FFF7010", filename) - cp.resetTarget() + + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + if not openocd.otp_write(0x1FFF7010, filename): + raise Exception("Failed to flash OTP") + self.logger.info(f"Flashed Successfully") - os.remove(filename) except Exception as e: self.logger.exception(e) return 1 + finally: + os.remove(filename) return 0 @@ -223,14 +242,22 @@ class Main(App): file.write(self._packSecond()) self.logger.info(f"Flashing OTP") - cp = CubeProgrammer(self._getCubeParams()) - cp.flashBin("0x1FFF7000", filename) - cp.resetTarget() + + openocd = OpenOCDProgrammer( + self.args.interface, + self.args.port_base, + self.args.serial, + ) + + if not openocd.otp_write(0x1FFF7000, filename): + raise Exception("Failed to flash OTP") + self.logger.info(f"Flashed Successfully") - os.remove(filename) except Exception as e: self.logger.exception(e) return 1 + finally: + os.remove(filename) return 0