From 2fcff37f8854cc62ef4580673a08d082857c915e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 10 Dec 2022 20:08:17 +0300 Subject: [PATCH] Add POCSAG Receiver plugin --- ReadMe.md | 1 + .../plugins/pocsag_pager/application.fam | 13 + .../pocsag_pager/helpers/pocsag_pager_event.h | 14 + .../pocsag_pager/helpers/pocsag_pager_types.h | 49 ++ .../plugins/pocsag_pager/images/Lock_7x8.png | Bin 0 -> 3597 bytes .../pocsag_pager/images/Message_8x7.png | Bin 0 -> 130 bytes .../images/Pin_back_arrow_10x8.png | Bin 0 -> 3606 bytes .../plugins/pocsag_pager/images/Quest_7x8.png | Bin 0 -> 3675 bytes .../pocsag_pager/images/Scanning_123x52.png | Bin 0 -> 1690 bytes .../pocsag_pager/images/Unlock_7x8.png | Bin 0 -> 3598 bytes .../images/WarningDolphin_45x42.png | Bin 0 -> 1139 bytes .../pocsag_pager/pocsag_pager_10px.png | Bin 0 -> 134 bytes .../plugins/pocsag_pager/pocsag_pager_app.c | 210 +++++++++ .../plugins/pocsag_pager/pocsag_pager_app_i.c | 141 ++++++ .../plugins/pocsag_pager/pocsag_pager_app_i.h | 72 +++ .../pocsag_pager/pocsag_pager_history.c | 223 +++++++++ .../pocsag_pager/pocsag_pager_history.h | 112 +++++ .../pocsag_pager/protocols/pcsg_generic.c | 123 +++++ .../pocsag_pager/protocols/pcsg_generic.h | 55 +++ .../plugins/pocsag_pager/protocols/pocsag.c | 371 +++++++++++++++ .../plugins/pocsag_pager/protocols/pocsag.h | 13 + .../pocsag_pager/protocols/protocol_items.c | 9 + .../pocsag_pager/protocols/protocol_items.h | 6 + .../scenes/pocsag_pager_receiver.c | 207 ++++++++ .../pocsag_pager/scenes/pocsag_pager_scene.c | 30 ++ .../pocsag_pager/scenes/pocsag_pager_scene.h | 29 ++ .../scenes/pocsag_pager_scene_about.c | 74 +++ .../scenes/pocsag_pager_scene_config.h | 5 + .../pocsag_pager_scene_receiver_config.c | 221 +++++++++ .../scenes/pocsag_pager_scene_receiver_info.c | 50 ++ .../scenes/pocsag_pager_scene_start.c | 58 +++ .../views/pocsag_pager_receiver.c | 440 ++++++++++++++++++ .../views/pocsag_pager_receiver.h | 39 ++ .../views/pocsag_pager_receiver_info.c | 137 ++++++ .../views/pocsag_pager_receiver_info.h | 16 + 35 files changed, 2718 insertions(+) create mode 100644 applications/plugins/pocsag_pager/application.fam create mode 100644 applications/plugins/pocsag_pager/helpers/pocsag_pager_event.h create mode 100644 applications/plugins/pocsag_pager/helpers/pocsag_pager_types.h create mode 100644 applications/plugins/pocsag_pager/images/Lock_7x8.png create mode 100644 applications/plugins/pocsag_pager/images/Message_8x7.png create mode 100644 applications/plugins/pocsag_pager/images/Pin_back_arrow_10x8.png create mode 100644 applications/plugins/pocsag_pager/images/Quest_7x8.png create mode 100644 applications/plugins/pocsag_pager/images/Scanning_123x52.png create mode 100644 applications/plugins/pocsag_pager/images/Unlock_7x8.png create mode 100644 applications/plugins/pocsag_pager/images/WarningDolphin_45x42.png create mode 100644 applications/plugins/pocsag_pager/pocsag_pager_10px.png create mode 100644 applications/plugins/pocsag_pager/pocsag_pager_app.c create mode 100644 applications/plugins/pocsag_pager/pocsag_pager_app_i.c create mode 100644 applications/plugins/pocsag_pager/pocsag_pager_app_i.h create mode 100644 applications/plugins/pocsag_pager/pocsag_pager_history.c create mode 100644 applications/plugins/pocsag_pager/pocsag_pager_history.h create mode 100644 applications/plugins/pocsag_pager/protocols/pcsg_generic.c create mode 100644 applications/plugins/pocsag_pager/protocols/pcsg_generic.h create mode 100644 applications/plugins/pocsag_pager/protocols/pocsag.c create mode 100644 applications/plugins/pocsag_pager/protocols/pocsag.h create mode 100644 applications/plugins/pocsag_pager/protocols/protocol_items.c create mode 100644 applications/plugins/pocsag_pager/protocols/protocol_items.h create mode 100644 applications/plugins/pocsag_pager/scenes/pocsag_pager_receiver.c create mode 100644 applications/plugins/pocsag_pager/scenes/pocsag_pager_scene.c create mode 100644 applications/plugins/pocsag_pager/scenes/pocsag_pager_scene.h create mode 100644 applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_about.c create mode 100644 applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_config.h create mode 100644 applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_receiver_config.c create mode 100644 applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_receiver_info.c create mode 100644 applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_start.c create mode 100644 applications/plugins/pocsag_pager/views/pocsag_pager_receiver.c create mode 100644 applications/plugins/pocsag_pager/views/pocsag_pager_receiver.h create mode 100644 applications/plugins/pocsag_pager/views/pocsag_pager_receiver_info.c create mode 100644 applications/plugins/pocsag_pager/views/pocsag_pager_receiver_info.h diff --git a/ReadMe.md b/ReadMe.md index 30498db81..86cd2304d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -109,6 +109,7 @@ Also check changelog in releases for latest updates! - BH1750 - Lightmeter [(by oleksiikutuzov)](https://github.com/oleksiikutuzov/flipperzero-lightmeter) - iButton Fuzzer [(by xMasterX)](https://github.com/xMasterX/ibutton-fuzzer) - HEX Viewer [(by QtRoS)](https://github.com/QtRoS/flipper-zero-hex-viewer) +- POCSAG Pager [(by xMasterX & Shmuma)](https://github.com/xMasterX/flipper-pager) Games: - DOOM (fixed) [(by p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/) diff --git a/applications/plugins/pocsag_pager/application.fam b/applications/plugins/pocsag_pager/application.fam new file mode 100644 index 000000000..aafb6a5a3 --- /dev/null +++ b/applications/plugins/pocsag_pager/application.fam @@ -0,0 +1,13 @@ +App( + appid="pocsag_pager", + name="POCSAG Pager", + apptype=FlipperAppType.PLUGIN, + entry_point="pocsag_pager_app", + cdefines=["APP_POCSAG_PAGER"], + requires=["gui"], + stack_size=4 * 1024, + order=50, + fap_icon="pocsag_pager_10px.png", + fap_category="Tools", + fap_icon_assets="images", +) diff --git a/applications/plugins/pocsag_pager/helpers/pocsag_pager_event.h b/applications/plugins/pocsag_pager/helpers/pocsag_pager_event.h new file mode 100644 index 000000000..8bcf64a30 --- /dev/null +++ b/applications/plugins/pocsag_pager/helpers/pocsag_pager_event.h @@ -0,0 +1,14 @@ +#pragma once + +typedef enum { + //PCSGCustomEvent + PCSGCustomEventStartId = 100, + + PCSGCustomEventSceneSettingLock, + + PCSGCustomEventViewReceiverOK, + PCSGCustomEventViewReceiverConfig, + PCSGCustomEventViewReceiverBack, + PCSGCustomEventViewReceiverOffDisplay, + PCSGCustomEventViewReceiverUnlock, +} PCSGCustomEvent; diff --git a/applications/plugins/pocsag_pager/helpers/pocsag_pager_types.h b/applications/plugins/pocsag_pager/helpers/pocsag_pager_types.h new file mode 100644 index 000000000..fabd7f321 --- /dev/null +++ b/applications/plugins/pocsag_pager/helpers/pocsag_pager_types.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#define PCSG_VERSION_APP "0.1" +#define PCSG_DEVELOPED "@xMasterX & @Shmuma" +#define PCSG_GITHUB "https://github.com/xMasterX/flipper-pager" + +#define PCSG_KEY_FILE_VERSION 1 +#define PCSG_KEY_FILE_TYPE "Flipper POCSAG Pager Key File" + +/** PCSGRxKeyState state */ +typedef enum { + PCSGRxKeyStateIDLE, + PCSGRxKeyStateBack, + PCSGRxKeyStateStart, + PCSGRxKeyStateAddKey, +} PCSGRxKeyState; + +/** PCSGHopperState state */ +typedef enum { + PCSGHopperStateOFF, + PCSGHopperStateRunnig, + PCSGHopperStatePause, + PCSGHopperStateRSSITimeOut, +} PCSGHopperState; + +/** PCSGLock */ +typedef enum { + PCSGLockOff, + PCSGLockOn, +} PCSGLock; + +typedef enum { + POCSAGPagerViewVariableItemList, + POCSAGPagerViewSubmenu, + POCSAGPagerViewReceiver, + POCSAGPagerViewReceiverInfo, + POCSAGPagerViewWidget, +} POCSAGPagerView; + +/** POCSAGPagerTxRx state */ +typedef enum { + PCSGTxRxStateIDLE, + PCSGTxRxStateRx, + PCSGTxRxStateTx, + PCSGTxRxStateSleep, +} PCSGTxRxState; diff --git a/applications/plugins/pocsag_pager/images/Lock_7x8.png b/applications/plugins/pocsag_pager/images/Lock_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c9ca2c702f1b93d7d06bd12ae708655c79d7c8 GIT binary patch literal 3597 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 a9|QyVQ~@!E@+ey_4zMz}H7hmoyzn0`d`!~- literal 0 HcmV?d00001 diff --git a/applications/plugins/pocsag_pager/images/Message_8x7.png b/applications/plugins/pocsag_pager/images/Message_8x7.png new file mode 100644 index 0000000000000000000000000000000000000000..642688cd5af3f8bf3e2f12c822edce2c1d9246fe GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^96-#@!3HEvBYEP06k~CayA#8@b22Z192-v;#}J9| z)_zAW1_O@6Z~n{A4RjPVpP|0da0P?P(bWwFNzXVd3>lY8Yh_E@Xsq~Ub|&GLUV{9j c1Mj<;Pq%3Oeo&)42WSq1r>mdKI;Vst0QP_;OBBhHB}+m>LbCLY=Y4wK?~kwVKJMeb&hxs?-|t+n40QpA+b4G8*k_>A)gsvzul2%)`{+ zGXO-B3u=_{$d$PU5YEZSn%Bo%6nB$X*pi8HtvlN(j>)<>oU^ms-{SJc!?CVM_kGpq zD|mb=fG|Jac@dmEE>EYKyFP!dPw~V2q0~L3V4zJ7VgZs-lDyFoU9CnK9lA z{|)s3FeAcdMKT|ltq9$x0m1;iQ-6nS!_cqj3MXxM0Gt2}LS)A!gg7{$QQxIe9%xhs z9ymYp6$g?4Aeep95(3@bioPky5s{%vM(c>C~+;D?q3rCl<9Vk3~u)C^5I%(w`)RT2PH zm)f7N?K9(ykBtnC`Hctjzt`uk1dC{xK3DmG+T--QM)Dliz9M@cHh&jC)x2t{F@ZnKih0C+}OXW@w z`v&$?T!Pj1rsQGSiPMN#jg(cf#BeEqd)~3u;mM}Qyx`i%uR_AH()f-rz&vtJ?~1BK z0wCjWh+r=QKw`~Oyt$4L(2|<}2>>cTD<8d+q=bD10syO=GrJ#HY?6E~&#jfte6C(u zt0YX=Xk{+Bqt-;ma^pzUR`Hw4DHbX&wa9MK#}7nQbGD=p$&@~a?~@uIls$T8lCHGT zTRHoMa^-n3QHw^99AP{1;ufE{Zb&OgDJ@PELckbai^>O2T$Dcqsc&TD3l~}jCU{~r zzv(gLjjtXx|H*H&$^=ebjw433!=?SMd>|aXa>3gB5?)oiL6JC$H*$+NBC6x}hAF7kW)t|J z9m26ua#NsV=VV?4pXG3D@mM_ij@FcBscZ$vT`c+>{Ka38#5<0qS`o5Kbu1s`Lk`}C ztNnHRw(Z$k$NrL*^Gd|*kZ!s*;vl|Vi-WL}unWTUV)XKz^G!Qs$eCE}Ne-py;|QoE ziVIFnDC2DAI9^+BdO1=ikF38qj1|k>fy+;lJzzvK8x_5E17Vq#bN5h7VfH)F-HXT@ zhwUgiVNOuz3x#rqq3K#J8H#9LzFuDEn{={2c`*Pw!K@JLkKSgT`X;p_=<}wD@rmf~ z;gVA4rJ@@!K08%{R8FWAD3_@~)3CQUyiHAObb-A`sHOQ|-+Z0sir>Ak`=mm`YuRLE zvRiUw^7vgB*AQ2;PWD|1mwT?8?;UeHb=$`Ek<+I_v3H91It$fZpB3&YZpDS;;+@(K zdF54mt)Bf!lqxwNW0P|pljlM#d!=%9yW%SZX%=tU#c&gu)D60B?{lPNX$l**VOcE< zdIIZ=4!P^c^-J)}8av)1B>n2);EeHy%mc04Tcui0=!xi=={@WUEb=RgEZW->(No>y zGtHP*oSy9AhtjjmvvjlOkrd=&s943GibEAK6}_QtUrgT;C)pEX^RMTnC;HoM=PBRw z=9RwiyZG%Idtrv4Jsg!__&(xHGl%#&=sLN)edgTIoh`h8iiEm=ymq_1zsj}0Uhw~9 z#8NW#s4ujm8iU4JvG{?xr?d;JWxCeN2BzQy;MMf~vb=1*A#83ixqIOEV` zVaGg#~3WwEx!kV?Q+q$;Ioo@pT$VAd^FJUK|pMWk7 z+6G@N*C4B;DJ`9n-?bZYSO3eQQfKCI=Av#Fcf@1azbbAvzVOP^{k?%t7-9b0z+hZ3 zaVn!cs{C&G8PM z+2JN0Mjo7#`(m!krk0qEMuRP#pvsP;1yp-=xo_t(VjQijbFbzedRSI|z~tIkmRs_| zzW)8E&_4stJKBW4G7xjb>97-2u07S9vv;%V`p9kjaQuUwaZ+YdW*$z8oKmXu9#*!q z%+XIrCsAsIJw|!0mU!Xy;)v!_$Xu^Na16FRuM}78B&~>r-qB$lQ9i;d$5deszcU!{ zTl=!4DREZuWEJOuQ~85O-Q_Hg*+EE+^)p4ySZAeheYhvC!k0y!={Us;;FYATIt}A- zuHORLec$46(H*yLp>@u>8zvVfHSws$-w!_}DiD%=UHO5jok!eG?^a6o;?lWyihn$? zDIXhlckt>wInSo_^n5%}_Ii2}Gnqe0E+&@qiXwmuR{ESqQ+U(U)H80A6kIb79 zf%9=Kr7f>pM2rYV(?^=0aC^Vq+>^Huk#*XW=eAmOudMomc28GLfB11cI@{U7;B zQ-8QzAye z?YX)QgQSmUMA3ROrqjb8(+}^Keqk~C{I7xACr^BG`h2tXW#7w|fwa?Q^Pou#Tc-nA z6Ux=gqvW7&R`EYy$;(ndrfyqZ_A8PP|3nOJFp782&dJ(|nq3+>oA{}~w;(&q!3^~- zt&hEkT}cb_JmgvBk8aC0Q(}I_mU%5U&3zn?_nfJue}^pk^lFtIEJ78dY$NHbLzw$V zXp^Kx-n6?(G4s3qJ66M%C`$TCPDSu}Lmjrwww;{p%X+9*d9fjae!jTBR?Bh)&695p|Np`_A@%C6Gkw(!c ztlQ|bD0BfD08GqSbOJGm#02}0{K-@lg#WAt0w(*SAnr!?Fncs1cZ-)AAzU~M!*noC|vOF)r0RvA`FmlWAHx@MBtF&>xaZy+5F>9 zprIfEOeP%(g@%WR>xUcY(-{6xxUsP@6o!Bz5PAX&y%08)Nnq(wLo|OgSdl`A3^JWb zrcuG`j07KAC=&${1pA*XDD;16sUiPVN>DQ>i$I6M^|Nl)Xlz**5m^jjZ zpQ#thS=L9?WiG40+mRzvqC`xB>H5sFVffs4KqX-!S)&$7{TGz=zWF=INHY2 z0tT}-KpPtw|HfL;h@lh`mH8X%`(G^lkJ$BrpwI=Ltw;=V7|GX$L8E~G&KgPnV=RW& zf8_fI>-)!83~m01g$ja!uJ`tT_4@agV1U-ee}`9~{5$?6s$k|Bg5ln!QST+V7#p3i zF4n&y*YC(C3v7{K(X_L&aAEcMczb*MMhV&2h)M`^tW<_XOB8+kL0OWLfY3%j)E-d2 TFC+3}9cE|kU{!4CefEC<&8td2 literal 0 HcmV?d00001 diff --git a/applications/plugins/pocsag_pager/images/Quest_7x8.png b/applications/plugins/pocsag_pager/images/Quest_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..6825247fbeaf98b4d0d9f8aa15d4f2c5f45dcf3c GIT binary patch literal 3675 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 N!3~Mzlm%~0{|8*@s)qmo literal 0 HcmV?d00001 diff --git a/applications/plugins/pocsag_pager/images/Unlock_7x8.png b/applications/plugins/pocsag_pager/images/Unlock_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..9d82b4daf3c722a5244f537f18b147833c2fb4ee GIT binary patch literal 3598 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 MrJY5ondim-0h+K&wEzGB literal 0 HcmV?d00001 diff --git a/applications/plugins/pocsag_pager/images/WarningDolphin_45x42.png b/applications/plugins/pocsag_pager/images/WarningDolphin_45x42.png new file mode 100644 index 0000000000000000000000000000000000000000..d766ffbb444db1739f2ccd030e506e8bada11ee8 GIT binary patch literal 1139 zcmaJ=TWAz#6rQ4%BpO*okt7zz3Bkm=bK9L=XV}qhW_HceY#KHzP4Z$UGyf(-b}pUy z<8ESW=_MtCk6tj|`k)VrCbo(SMH0n`eW+KIU`wG5O`$IeMg)Vzf7adDhv+af=lqBB zo%5Z`zqhqzdu2s+1%_dji6%LPq#u2o%9fzN?;7Dlq6)^^VVjkKImH23RI|DPo-mXi zkOGP}@Wrnnf?-SQ^>jOIPc{pxWsr*JL*@+|p)oA7EpIDoAAoo_=+RA)c=F3Qf$N$` ze9k55q%DD7y=l+^ZG$aob+Aw6HDcRVJdzhs00Te;&l_3O74jlch$|r7GgAa!aDjay z@rG1;vK5ys2jF3n@vAgV<6)izn!k6Qhd>D(EhD7l zcrhJ1i9|1iwm?z2T#n2INXzM=7@p@Tnx$CQk39VDfC-hn-*jtB5oF-1j&4KUGI1}W z(rxuakw9eMRAJc3%8 zlBq3$QTyJX$a6$&1ldyi4Pe5AEE32Sg@vDzY~7qR?1u@oXh zd9(fBtV<@eK%Tm=yy&p7{=h^#@1W)WFFSe!U5pP~o71uR`FW)7xc*=d5_b}EG@XBZ z@<7MRp-;+|o}SzJvMyfx>37Y4etBizf%0H*zaIF?2ObZt?$D;{o|esJ z*PgAOIO^*8tL%z2)|}k$DP5kN5}8qo*wNa8y?R2=%wO{gCD(`sHt}TzWQuK zMDIJap(nb2=7*ns*oDcRBbC23yy`kvKYV`ovU`a=EmxNv-90;;5r6+l8hQTp^u`Hn XX6*+%8gHC`h)Tl}u@-r>vFqE{F~5F& literal 0 HcmV?d00001 diff --git a/applications/plugins/pocsag_pager/pocsag_pager_10px.png b/applications/plugins/pocsag_pager/pocsag_pager_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..a5686c1c0ca2eb68786aa7c9410c63931f05a58c GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>aqx6;4ABTq z{`3F;|9Unh24xRLWo6~Kr3#xJv^b==j5yW_OkbIB`9x61`H2#0ht@P+R5&XUCTueC g#REkNvw+nM!Ra<%Cov0d1DeI)>FVdQ&MBb@045|TWdHyG literal 0 HcmV?d00001 diff --git a/applications/plugins/pocsag_pager/pocsag_pager_app.c b/applications/plugins/pocsag_pager/pocsag_pager_app.c new file mode 100644 index 000000000..a3d45ac1b --- /dev/null +++ b/applications/plugins/pocsag_pager/pocsag_pager_app.c @@ -0,0 +1,210 @@ +#include "pocsag_pager_app_i.h" + +#include +#include +#include +#include "protocols/protocol_items.h" + +// Comment next line to build on OFW +#define IS_UNLEASHED + +static bool pocsag_pager_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + POCSAGPagerApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool pocsag_pager_app_back_event_callback(void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void pocsag_pager_app_tick_event_callback(void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +POCSAGPagerApp* pocsag_pager_app_alloc() { + POCSAGPagerApp* app = malloc(sizeof(POCSAGPagerApp)); + + // GUI + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&pocsag_pager_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, pocsag_pager_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, pocsag_pager_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, pocsag_pager_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + // Variable Item List + app->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + POCSAGPagerViewVariableItemList, + variable_item_list_get_view(app->variable_item_list)); + + // SubMenu + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, POCSAGPagerViewSubmenu, submenu_get_view(app->submenu)); + + // Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, POCSAGPagerViewWidget, widget_get_view(app->widget)); + + // Receiver + app->pcsg_receiver = pcsg_view_receiver_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + POCSAGPagerViewReceiver, + pcsg_view_receiver_get_view(app->pcsg_receiver)); + + // Receiver Info + app->pcsg_receiver_info = pcsg_view_receiver_info_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + POCSAGPagerViewReceiverInfo, + pcsg_view_receiver_info_get_view(app->pcsg_receiver_info)); + + //init setting + app->setting = subghz_setting_alloc(); + +//ToDo FIX file name setting +#ifdef IS_UNLEASHED + subghz_setting_load(app->setting, EXT_PATH("pocsag/settings.txt"), true); +#else + subghz_setting_load(app->setting, EXT_PATH("pocsag/settings.txt")); +#endif + //init Worker & Protocol & History + app->lock = PCSGLockOff; + app->txrx = malloc(sizeof(POCSAGPagerTxRx)); + app->txrx->preset = malloc(sizeof(SubGhzRadioPreset)); + app->txrx->preset->name = furi_string_alloc(); + + // Custom Presets load without using config file + + FlipperFormat* temp_fm_preset = flipper_format_string_alloc(); + flipper_format_write_string_cstr( + temp_fm_preset, + (const char*)"Custom_preset_data", + (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); + flipper_format_rewind(temp_fm_preset); + subghz_setting_load_custom_preset(app->setting, (const char*)"FM95", temp_fm_preset); + + flipper_format_free(temp_fm_preset); + + FlipperFormat* temp_fm_preset2 = flipper_format_string_alloc(); + flipper_format_write_string_cstr( + temp_fm_preset2, + (const char*)"Custom_preset_data", + (const char*)"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 31 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"); + flipper_format_rewind(temp_fm_preset2); + subghz_setting_load_custom_preset(app->setting, (const char*)"FM150", temp_fm_preset2); + + flipper_format_free(temp_fm_preset2); + + // custom presets loading - end + + pcsg_preset_init(app, "FM95", 439987500, NULL, 0); + + app->txrx->hopper_state = PCSGHopperStateOFF; + app->txrx->history = pcsg_history_alloc(); + app->txrx->worker = subghz_worker_alloc(); + app->txrx->environment = subghz_environment_alloc(); + subghz_environment_set_protocol_registry( + app->txrx->environment, (void*)&pocsag_pager_protocol_registry); + app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment); + + subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable); + subghz_worker_set_overrun_callback( + app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); + subghz_worker_set_pair_callback( + app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); + subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); + + furi_hal_power_suppress_charge_enter(); + + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneStart); + + return app; +} + +void pocsag_pager_app_free(POCSAGPagerApp* app) { + furi_assert(app); + + //CC1101 off + pcsg_sleep(app); + + // Submenu + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewSubmenu); + submenu_free(app->submenu); + + // Variable Item List + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewVariableItemList); + variable_item_list_free(app->variable_item_list); + + // Widget + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewWidget); + widget_free(app->widget); + + // Receiver + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewReceiver); + pcsg_view_receiver_free(app->pcsg_receiver); + + // Receiver Info + view_dispatcher_remove_view(app->view_dispatcher, POCSAGPagerViewReceiverInfo); + pcsg_view_receiver_info_free(app->pcsg_receiver_info); + + //setting + subghz_setting_free(app->setting); + + //Worker & Protocol & History + subghz_receiver_free(app->txrx->receiver); + subghz_environment_free(app->txrx->environment); + pcsg_history_free(app->txrx->history); + subghz_worker_free(app->txrx->worker); + furi_string_free(app->txrx->preset->name); + free(app->txrx->preset); + free(app->txrx); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Notifications + furi_record_close(RECORD_NOTIFICATION); + app->notifications = NULL; + + // Close records + furi_record_close(RECORD_GUI); + + furi_hal_power_suppress_charge_exit(); + + free(app); +} + +int32_t pocsag_pager_app(void* p) { + UNUSED(p); + POCSAGPagerApp* pocsag_pager_app = pocsag_pager_app_alloc(); + + view_dispatcher_run(pocsag_pager_app->view_dispatcher); + + pocsag_pager_app_free(pocsag_pager_app); + + return 0; +} diff --git a/applications/plugins/pocsag_pager/pocsag_pager_app_i.c b/applications/plugins/pocsag_pager/pocsag_pager_app_i.c new file mode 100644 index 000000000..ba6e87c28 --- /dev/null +++ b/applications/plugins/pocsag_pager/pocsag_pager_app_i.c @@ -0,0 +1,141 @@ +#include "pocsag_pager_app_i.h" + +#define TAG "POCSAGPager" +#include + +void pcsg_preset_init( + void* context, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size) { + furi_assert(context); + POCSAGPagerApp* app = context; + furi_string_set(app->txrx->preset->name, preset_name); + app->txrx->preset->frequency = frequency; + app->txrx->preset->data = preset_data; + app->txrx->preset->data_size = preset_data_size; +} + +void pcsg_get_frequency_modulation( + POCSAGPagerApp* app, + FuriString* frequency, + FuriString* modulation) { + furi_assert(app); + if(frequency != NULL) { + furi_string_printf( + frequency, + "%03ld.%02ld", + app->txrx->preset->frequency / 1000000 % 1000, + app->txrx->preset->frequency / 10000 % 100); + } + if(modulation != NULL) { + furi_string_printf(modulation, "%.2s", furi_string_get_cstr(app->txrx->preset->name)); + } +} + +void pcsg_begin(POCSAGPagerApp* app, uint8_t* preset_data) { + furi_assert(app); + UNUSED(preset_data); + furi_hal_subghz_reset(); + furi_hal_subghz_idle(); + furi_hal_subghz_load_custom_preset(preset_data); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + app->txrx->txrx_state = PCSGTxRxStateIDLE; +} + +uint32_t pcsg_rx(POCSAGPagerApp* app, uint32_t frequency) { + furi_assert(app); + if(!furi_hal_subghz_is_frequency_valid(frequency)) { + furi_crash("POCSAGPager: Incorrect RX frequency."); + } + furi_assert( + app->txrx->txrx_state != PCSGTxRxStateRx && app->txrx->txrx_state != PCSGTxRxStateSleep); + + furi_hal_subghz_idle(); + uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); + furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); + furi_hal_subghz_flush_rx(); + furi_hal_subghz_rx(); + + furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker); + subghz_worker_start(app->txrx->worker); + app->txrx->txrx_state = PCSGTxRxStateRx; + return value; +} + +void pcsg_idle(POCSAGPagerApp* app) { + furi_assert(app); + furi_assert(app->txrx->txrx_state != PCSGTxRxStateSleep); + furi_hal_subghz_idle(); + app->txrx->txrx_state = PCSGTxRxStateIDLE; +} + +void pcsg_rx_end(POCSAGPagerApp* app) { + furi_assert(app); + furi_assert(app->txrx->txrx_state == PCSGTxRxStateRx); + if(subghz_worker_is_running(app->txrx->worker)) { + subghz_worker_stop(app->txrx->worker); + furi_hal_subghz_stop_async_rx(); + } + furi_hal_subghz_idle(); + app->txrx->txrx_state = PCSGTxRxStateIDLE; +} + +void pcsg_sleep(POCSAGPagerApp* app) { + furi_assert(app); + furi_hal_subghz_sleep(); + app->txrx->txrx_state = PCSGTxRxStateSleep; +} + +void pcsg_hopper_update(POCSAGPagerApp* app) { + furi_assert(app); + + switch(app->txrx->hopper_state) { + case PCSGHopperStateOFF: + return; + break; + case PCSGHopperStatePause: + return; + break; + case PCSGHopperStateRSSITimeOut: + if(app->txrx->hopper_timeout != 0) { + app->txrx->hopper_timeout--; + return; + } + break; + default: + break; + } + float rssi = -127.0f; + if(app->txrx->hopper_state != PCSGHopperStateRSSITimeOut) { + // See RSSI Calculation timings in CC1101 17.3 RSSI + rssi = furi_hal_subghz_get_rssi(); + + // Stay if RSSI is high enough + if(rssi > -90.0f) { + app->txrx->hopper_timeout = 10; + app->txrx->hopper_state = PCSGHopperStateRSSITimeOut; + return; + } + } else { + app->txrx->hopper_state = PCSGHopperStateRunnig; + } + // Select next frequency + if(app->txrx->hopper_idx_frequency < + subghz_setting_get_hopper_frequency_count(app->setting) - 1) { + app->txrx->hopper_idx_frequency++; + } else { + app->txrx->hopper_idx_frequency = 0; + } + + if(app->txrx->txrx_state == PCSGTxRxStateRx) { + pcsg_rx_end(app); + }; + if(app->txrx->txrx_state == PCSGTxRxStateIDLE) { + subghz_receiver_reset(app->txrx->receiver); + app->txrx->preset->frequency = + subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency); + pcsg_rx(app, app->txrx->preset->frequency); + } +} diff --git a/applications/plugins/pocsag_pager/pocsag_pager_app_i.h b/applications/plugins/pocsag_pager/pocsag_pager_app_i.h new file mode 100644 index 000000000..8a0426dc5 --- /dev/null +++ b/applications/plugins/pocsag_pager/pocsag_pager_app_i.h @@ -0,0 +1,72 @@ +#pragma once + +#include "helpers/pocsag_pager_types.h" + +#include "scenes/pocsag_pager_scene.h" +#include +#include +#include +#include +#include +#include +#include +#include "views/pocsag_pager_receiver.h" +#include "views/pocsag_pager_receiver_info.h" +#include "pocsag_pager_history.h" + +#include +#include +#include +#include +#include + +typedef struct POCSAGPagerApp POCSAGPagerApp; + +struct POCSAGPagerTxRx { + SubGhzWorker* worker; + + SubGhzEnvironment* environment; + SubGhzReceiver* receiver; + SubGhzRadioPreset* preset; + PCSGHistory* history; + uint16_t idx_menu_chosen; + PCSGTxRxState txrx_state; + PCSGHopperState hopper_state; + uint8_t hopper_timeout; + uint8_t hopper_idx_frequency; + PCSGRxKeyState rx_key_state; +}; + +typedef struct POCSAGPagerTxRx POCSAGPagerTxRx; + +struct POCSAGPagerApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + POCSAGPagerTxRx* txrx; + SceneManager* scene_manager; + NotificationApp* notifications; + VariableItemList* variable_item_list; + Submenu* submenu; + Widget* widget; + PCSGReceiver* pcsg_receiver; + PCSGReceiverInfo* pcsg_receiver_info; + PCSGLock lock; + SubGhzSetting* setting; +}; + +void pcsg_preset_init( + void* context, + const char* preset_name, + uint32_t frequency, + uint8_t* preset_data, + size_t preset_data_size); +void pcsg_get_frequency_modulation( + POCSAGPagerApp* app, + FuriString* frequency, + FuriString* modulation); +void pcsg_begin(POCSAGPagerApp* app, uint8_t* preset_data); +uint32_t pcsg_rx(POCSAGPagerApp* app, uint32_t frequency); +void pcsg_idle(POCSAGPagerApp* app); +void pcsg_rx_end(POCSAGPagerApp* app); +void pcsg_sleep(POCSAGPagerApp* app); +void pcsg_hopper_update(POCSAGPagerApp* app); diff --git a/applications/plugins/pocsag_pager/pocsag_pager_history.c b/applications/plugins/pocsag_pager/pocsag_pager_history.c new file mode 100644 index 000000000..d5f97b571 --- /dev/null +++ b/applications/plugins/pocsag_pager/pocsag_pager_history.c @@ -0,0 +1,223 @@ +#include "pocsag_pager_history.h" +#include +#include +#include +#include "protocols/pcsg_generic.h" + +#include + +#define PCSG_HISTORY_MAX 50 +#define TAG "PCSGHistory" + +typedef struct { + FuriString* item_str; + FlipperFormat* flipper_string; + uint8_t type; + SubGhzRadioPreset* preset; +} PCSGHistoryItem; + +ARRAY_DEF(PCSGHistoryItemArray, PCSGHistoryItem, M_POD_OPLIST) + +#define M_OPL_PCSGHistoryItemArray_t() ARRAY_OPLIST(PCSGHistoryItemArray, M_POD_OPLIST) + +typedef struct { + PCSGHistoryItemArray_t data; +} PCSGHistoryStruct; + +struct PCSGHistory { + uint32_t last_update_timestamp; + uint16_t last_index_write; + uint8_t code_last_hash_data; + FuriString* tmp_string; + PCSGHistoryStruct* history; +}; + +PCSGHistory* pcsg_history_alloc(void) { + PCSGHistory* instance = malloc(sizeof(PCSGHistory)); + instance->tmp_string = furi_string_alloc(); + instance->history = malloc(sizeof(PCSGHistoryStruct)); + PCSGHistoryItemArray_init(instance->history->data); + return instance; +} + +void pcsg_history_free(PCSGHistory* instance) { + furi_assert(instance); + furi_string_free(instance->tmp_string); + for + M_EACH(item, instance->history->data, PCSGHistoryItemArray_t) { + furi_string_free(item->item_str); + furi_string_free(item->preset->name); + free(item->preset); + flipper_format_free(item->flipper_string); + item->type = 0; + } + PCSGHistoryItemArray_clear(instance->history->data); + free(instance->history); + free(instance); +} + +uint32_t pcsg_history_get_frequency(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + return item->preset->frequency; +} + +SubGhzRadioPreset* pcsg_history_get_radio_preset(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + return item->preset; +} + +const char* pcsg_history_get_preset(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + return furi_string_get_cstr(item->preset->name); +} + +void pcsg_history_reset(PCSGHistory* instance) { + furi_assert(instance); + furi_string_reset(instance->tmp_string); + for + M_EACH(item, instance->history->data, PCSGHistoryItemArray_t) { + furi_string_free(item->item_str); + furi_string_free(item->preset->name); + free(item->preset); + flipper_format_free(item->flipper_string); + item->type = 0; + } + PCSGHistoryItemArray_reset(instance->history->data); + instance->last_index_write = 0; + instance->code_last_hash_data = 0; +} + +uint16_t pcsg_history_get_item(PCSGHistory* instance) { + furi_assert(instance); + return instance->last_index_write; +} + +uint8_t pcsg_history_get_type_protocol(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + return item->type; +} + +const char* pcsg_history_get_protocol_name(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + flipper_format_rewind(item->flipper_string); + if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + furi_string_reset(instance->tmp_string); + } + return furi_string_get_cstr(instance->tmp_string); +} + +FlipperFormat* pcsg_history_get_raw_data(PCSGHistory* instance, uint16_t idx) { + furi_assert(instance); + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + if(item->flipper_string) { + return item->flipper_string; + } else { + return NULL; + } +} +bool pcsg_history_get_text_space_left(PCSGHistory* instance, FuriString* output) { + furi_assert(instance); + if(instance->last_index_write == PCSG_HISTORY_MAX) { + if(output != NULL) furi_string_printf(output, "Memory is FULL"); + return true; + } + if(output != NULL) + furi_string_printf(output, "%02u/%02u", instance->last_index_write, PCSG_HISTORY_MAX); + return false; +} + +void pcsg_history_get_text_item_menu(PCSGHistory* instance, FuriString* output, uint16_t idx) { + PCSGHistoryItem* item = PCSGHistoryItemArray_get(instance->history->data, idx); + furi_string_set(output, item->item_str); +} + +PCSGHistoryStateAddKey + pcsg_history_add_to_history(PCSGHistory* instance, void* context, SubGhzRadioPreset* preset) { + furi_assert(instance); + furi_assert(context); + + if(instance->last_index_write >= PCSG_HISTORY_MAX) return PCSGHistoryStateAddKeyOverflow; + + SubGhzProtocolDecoderBase* decoder_base = context; + if((instance->code_last_hash_data == + subghz_protocol_decoder_base_get_hash_data(decoder_base)) && + ((furi_get_tick() - instance->last_update_timestamp) < 500)) { + instance->last_update_timestamp = furi_get_tick(); + return PCSGHistoryStateAddKeyTimeOut; + } + + instance->code_last_hash_data = subghz_protocol_decoder_base_get_hash_data(decoder_base); + instance->last_update_timestamp = furi_get_tick(); + + FlipperFormat* fff = flipper_format_string_alloc(); + subghz_protocol_decoder_base_serialize(decoder_base, fff, preset); + + do { + if(!flipper_format_rewind(fff)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + } while(false); + flipper_format_free(fff); + + PCSGHistoryItem* item = PCSGHistoryItemArray_push_raw(instance->history->data); + item->preset = malloc(sizeof(SubGhzRadioPreset)); + item->type = decoder_base->protocol->type; + item->preset->frequency = preset->frequency; + item->preset->name = furi_string_alloc(); + furi_string_set(item->preset->name, preset->name); + item->preset->data = preset->data; + item->preset->data_size = preset->data_size; + + item->item_str = furi_string_alloc(); + item->flipper_string = flipper_format_string_alloc(); + subghz_protocol_decoder_base_serialize(decoder_base, item->flipper_string, preset); + + do { + if(!flipper_format_rewind(item->flipper_string)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_string(item->flipper_string, "Protocol", instance->tmp_string)) { + FURI_LOG_E(TAG, "Missing Protocol"); + break; + } + + if(!flipper_format_rewind(item->flipper_string)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + FuriString* temp_ric = furi_string_alloc(); + if(!flipper_format_read_string(item->flipper_string, "Ric", temp_ric)) { + FURI_LOG_E(TAG, "Missing Ric"); + break; + } + + FuriString* temp_message = furi_string_alloc(); + if(!flipper_format_read_string(item->flipper_string, "Message", temp_message)) { + FURI_LOG_E(TAG, "Missing Message"); + break; + } + + furi_string_printf( + item->item_str, + "%s%s", + furi_string_get_cstr(temp_ric), + furi_string_get_cstr(temp_message)); + + furi_string_free(temp_message); + furi_string_free(temp_ric); + + } while(false); + instance->last_index_write++; + return PCSGHistoryStateAddKeyNewDada; + + return PCSGHistoryStateAddKeyUnknown; +} diff --git a/applications/plugins/pocsag_pager/pocsag_pager_history.h b/applications/plugins/pocsag_pager/pocsag_pager_history.h new file mode 100644 index 000000000..7528fcc29 --- /dev/null +++ b/applications/plugins/pocsag_pager/pocsag_pager_history.h @@ -0,0 +1,112 @@ + +#pragma once + +#include +#include +#include +#include +#include + +typedef struct PCSGHistory PCSGHistory; + +/** History state add key */ +typedef enum { + PCSGHistoryStateAddKeyUnknown, + PCSGHistoryStateAddKeyTimeOut, + PCSGHistoryStateAddKeyNewDada, + PCSGHistoryStateAddKeyUpdateData, + PCSGHistoryStateAddKeyOverflow, +} PCSGHistoryStateAddKey; + +/** Allocate PCSGHistory + * + * @return PCSGHistory* + */ +PCSGHistory* pcsg_history_alloc(void); + +/** Free PCSGHistory + * + * @param instance - PCSGHistory instance + */ +void pcsg_history_free(PCSGHistory* instance); + +/** Clear history + * + * @param instance - PCSGHistory instance + */ +void pcsg_history_reset(PCSGHistory* instance); + +/** Get frequency to history[idx] + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return frequency - frequency Hz + */ +uint32_t pcsg_history_get_frequency(PCSGHistory* instance, uint16_t idx); + +SubGhzRadioPreset* pcsg_history_get_radio_preset(PCSGHistory* instance, uint16_t idx); + +/** Get preset to history[idx] + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return preset - preset name + */ +const char* pcsg_history_get_preset(PCSGHistory* instance, uint16_t idx); + +/** Get history index write + * + * @param instance - PCSGHistory instance + * @return idx - current record index + */ +uint16_t pcsg_history_get_item(PCSGHistory* instance); + +/** Get type protocol to history[idx] + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return type - type protocol + */ +uint8_t pcsg_history_get_type_protocol(PCSGHistory* instance, uint16_t idx); + +/** Get name protocol to history[idx] + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return name - const char* name protocol + */ +const char* pcsg_history_get_protocol_name(PCSGHistory* instance, uint16_t idx); + +/** Get string item menu to history[idx] + * + * @param instance - PCSGHistory instance + * @param output - FuriString* output + * @param idx - record index + */ +void pcsg_history_get_text_item_menu(PCSGHistory* instance, FuriString* output, uint16_t idx); + +/** Get string the remaining number of records to history + * + * @param instance - PCSGHistory instance + * @param output - FuriString* output + * @return bool - is FUUL + */ +bool pcsg_history_get_text_space_left(PCSGHistory* instance, FuriString* output); + +/** Add protocol to history + * + * @param instance - PCSGHistory instance + * @param context - SubGhzProtocolCommon context + * @param preset - SubGhzRadioPreset preset + * @return PCSGHistoryStateAddKey; + */ +PCSGHistoryStateAddKey + pcsg_history_add_to_history(PCSGHistory* instance, void* context, SubGhzRadioPreset* preset); + +/** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data + * + * @param instance - PCSGHistory instance + * @param idx - record index + * @return SubGhzProtocolCommonLoad* + */ +FlipperFormat* pcsg_history_get_raw_data(PCSGHistory* instance, uint16_t idx); diff --git a/applications/plugins/pocsag_pager/protocols/pcsg_generic.c b/applications/plugins/pocsag_pager/protocols/pcsg_generic.c new file mode 100644 index 000000000..890ed43d7 --- /dev/null +++ b/applications/plugins/pocsag_pager/protocols/pcsg_generic.c @@ -0,0 +1,123 @@ +#include "pcsg_generic.h" +#include +#include +#include "../helpers/pocsag_pager_types.h" + +#define TAG "PCSGBlockGeneric" + +void pcsg_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { + const char* preset_name_temp; + if(!strcmp(preset_name, "AM270")) { + preset_name_temp = "FuriHalSubGhzPresetOok270Async"; + } else if(!strcmp(preset_name, "AM650")) { + preset_name_temp = "FuriHalSubGhzPresetOok650Async"; + } else if(!strcmp(preset_name, "FM238")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; + } else if(!strcmp(preset_name, "FM476")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; + } else { + preset_name_temp = "FuriHalSubGhzPresetCustom"; + } + furi_string_set(preset_str, preset_name_temp); +} + +bool pcsg_block_generic_serialize( + PCSGBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(instance); + bool res = false; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, PCSG_KEY_FILE_TYPE, PCSG_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + pcsg_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + break; + } + } + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + if(!flipper_format_write_string(flipper_format, "Ric", instance->result_ric)) { + FURI_LOG_E(TAG, "Unable to add Ric"); + break; + } + + if(!flipper_format_write_string(flipper_format, "Message", instance->result_msg)) { + FURI_LOG_E(TAG, "Unable to add Message"); + break; + } + + res = true; + } while(false); + furi_string_free(temp_str); + return res; +} + +bool pcsg_block_generic_deserialize(PCSGBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + bool res = false; + FuriString* temp_data = furi_string_alloc(); + FuriString* temp_data2 = furi_string_alloc(); + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_string(flipper_format, "Ric", temp_data2)) { + FURI_LOG_E(TAG, "Missing Ric"); + break; + } + if(instance->result_ric != NULL) { + furi_string_set(instance->result_ric, temp_data2); + } else { + instance->result_ric = furi_string_alloc_set(temp_data2); + } + + if(!flipper_format_read_string(flipper_format, "Message", temp_data)) { + FURI_LOG_E(TAG, "Missing Message"); + break; + } + if(instance->result_msg != NULL) { + furi_string_set(instance->result_msg, temp_data); + } else { + instance->result_msg = furi_string_alloc_set(temp_data); + } + + res = true; + } while(0); + + furi_string_free(temp_data); + furi_string_free(temp_data2); + + return res; +} diff --git a/applications/plugins/pocsag_pager/protocols/pcsg_generic.h b/applications/plugins/pocsag_pager/protocols/pcsg_generic.h new file mode 100644 index 000000000..ff925b6c9 --- /dev/null +++ b/applications/plugins/pocsag_pager/protocols/pcsg_generic.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#include +#include "furi.h" +#include "furi_hal.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct PCSGBlockGeneric PCSGBlockGeneric; + +struct PCSGBlockGeneric { + const char* protocol_name; + FuriString* result_ric; + FuriString* result_msg; +}; + +/** + * Get name preset. + * @param preset_name name preset + * @param preset_str Output name preset + */ +void pcsg_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); + +/** + * Serialize data PCSGBlockGeneric. + * @param instance Pointer to a PCSGBlockGeneric 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 pcsg_block_generic_serialize( + PCSGBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data PCSGBlockGeneric. + * @param instance Pointer to a PCSGBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool pcsg_block_generic_deserialize(PCSGBlockGeneric* instance, FlipperFormat* flipper_format); + +float pcsg_block_generic_fahrenheit_to_celsius(float fahrenheit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/plugins/pocsag_pager/protocols/pocsag.c b/applications/plugins/pocsag_pager/protocols/pocsag.c new file mode 100644 index 000000000..69d09d554 --- /dev/null +++ b/applications/plugins/pocsag_pager/protocols/pocsag.c @@ -0,0 +1,371 @@ +#include "pocsag.h" + +#include +#include +#include + +#define TAG "POCSAG" + +static const SubGhzBlockConst pocsag_const = { + .te_short = 833, + .te_delta = 100, +}; + +// Minimal amount of sync bits (interleaving zeros and ones) +#define POCSAG_MIN_SYNC_BITS 32 +#define POCSAG_CW_BITS 32 +#define POCSAG_CW_MASK 0xFFFFFFFF +#define POCSAG_FRAME_SYNC_CODE 0x7CD215D8 +#define POCSAG_IDLE_CODE_WORD 0x7A89C197 + +#define POCSAG_FUNC_NUM 0 +#define POCSAG_FUNC_ALERT1 1 +#define POCSAG_FUNC_ALERT2 2 +#define POCSAG_FUNC_ALPHANUM 3 + +static const char* func_msg[] = {"\e#Num:\e# ", "\e#Alert\e#", "\e#Alert:\e# ", "\e#Msg:\e# "}; +static const char* bcd_chars = "*U -)("; + +struct SubGhzProtocolDecoderPocsag { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + PCSGBlockGeneric generic; + + uint8_t codeword_idx; + uint32_t ric; + uint8_t func; + + // partially decoded character + uint8_t char_bits; + uint8_t char_data; + + // message being decoded + FuriString* msg; + + // Done messages, ready to be serialized/deserialized + FuriString* done_msg; +}; + +typedef struct SubGhzProtocolDecoderPocsag SubGhzProtocolDecoderPocsag; + +typedef enum { + PocsagDecoderStepReset = 0, + PocsagDecoderStepFoundSync, + PocsagDecoderStepFoundPreamble, + PocsagDecoderStepMessage, +} PocsagDecoderStep; + +void* subghz_protocol_decoder_pocsag_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + + SubGhzProtocolDecoderPocsag* instance = malloc(sizeof(SubGhzProtocolDecoderPocsag)); + instance->base.protocol = &subghz_protocol_pocsag; + instance->generic.protocol_name = instance->base.protocol->name; + instance->msg = furi_string_alloc(); + instance->done_msg = furi_string_alloc(); + if(instance->generic.result_msg == NULL) { + instance->generic.result_msg = furi_string_alloc(); + } + if(instance->generic.result_ric == NULL) { + instance->generic.result_ric = furi_string_alloc(); + } + + return instance; +} + +void subghz_protocol_decoder_pocsag_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + furi_string_free(instance->msg); + furi_string_free(instance->done_msg); + free(instance); +} + +void subghz_protocol_decoder_pocsag_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + + instance->decoder.parser_step = PocsagDecoderStepReset; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + instance->codeword_idx = 0; + instance->char_bits = 0; + instance->char_data = 0; + furi_string_reset(instance->msg); + furi_string_reset(instance->done_msg); + furi_string_reset(instance->generic.result_msg); + furi_string_reset(instance->generic.result_ric); +} + +static void pocsag_decode_address_word(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + instance->ric = (data >> 13); + instance->ric = (instance->ric << 3) | (instance->codeword_idx >> 1); + instance->func = (data >> 11) & 0b11; +} + +static bool decode_message_alphanumeric(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + for(uint8_t i = 0; i < 20; i++) { + instance->char_data >>= 1; + if(data & (1 << 30)) { + instance->char_data |= 1 << 6; + } + instance->char_bits++; + if(instance->char_bits == 7) { + if(instance->char_data == 0) return false; + furi_string_push_back(instance->msg, instance->char_data); + instance->char_data = 0; + instance->char_bits = 0; + } + data <<= 1; + } + return true; +} + +static void decode_message_numeric(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + // 5 groups with 4 bits each + uint8_t val; + for(uint8_t i = 0; i < 5; i++) { + val = (data >> (27 - i * 4)) & 0b1111; + // reverse the order of 4 bits + val = (val & 0x5) << 1 | (val & 0xA) >> 1; + val = (val & 0x3) << 2 | (val & 0xC) >> 2; + + if(val <= 9) + val += '0'; + else + val = bcd_chars[val - 10]; + furi_string_push_back(instance->msg, val); + } +} + +// decode message word, maintaining instance state for partial decoding. Return true if more data +// might follow or false if end of message reached. +static bool pocsag_decode_message_word(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + switch(instance->func) { + case POCSAG_FUNC_ALERT2: + case POCSAG_FUNC_ALPHANUM: + return decode_message_alphanumeric(instance, data); + + case POCSAG_FUNC_NUM: + decode_message_numeric(instance, data); + return true; + } + return false; +} + +// Function called when current message got decoded, but other messages might follow +static void pocsag_message_done(SubGhzProtocolDecoderPocsag* instance) { + // append the message to the long-term storage string + furi_string_cat_printf( + instance->generic.result_ric, "\e#RIC: %" PRIu32 "\e# | ", instance->ric); + furi_string_cat_str(instance->generic.result_ric, func_msg[instance->func]); + if(instance->func != POCSAG_FUNC_ALERT1) { + furi_string_cat(instance->done_msg, instance->msg); + } + furi_string_cat_str(instance->done_msg, " "); + + furi_string_cat(instance->generic.result_msg, instance->done_msg); + + // reset the state + instance->char_bits = 0; + instance->char_data = 0; + furi_string_reset(instance->msg); +} + +void subghz_protocol_decoder_pocsag_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + + // reset state - waiting for 32 bits of interleaving 1s and 0s + if(instance->decoder.parser_step == PocsagDecoderStepReset) { + if(DURATION_DIFF(duration, pocsag_const.te_short) < pocsag_const.te_delta) { + // POCSAG signals are inverted + subghz_protocol_blocks_add_bit(&instance->decoder, !level); + + if(instance->decoder.decode_count_bit == POCSAG_MIN_SYNC_BITS) { + instance->decoder.parser_step = PocsagDecoderStepFoundSync; + } + } else if(instance->decoder.decode_count_bit > 0) { + subghz_protocol_decoder_pocsag_reset(context); + } + return; + } + + int bits_count = duration / pocsag_const.te_short; + uint32_t extra = duration - pocsag_const.te_short * bits_count; + + if(DURATION_DIFF(extra, pocsag_const.te_short) < pocsag_const.te_delta) + bits_count++; + else if(extra > pocsag_const.te_delta) { + // in non-reset state we faced the error signal - we reached the end of the packet, flush data + if(furi_string_size(instance->done_msg) > 0) { + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + subghz_protocol_decoder_pocsag_reset(context); + return; + } + + uint32_t codeword; + + // handle state machine for every incoming bit + while(bits_count-- > 0) { + subghz_protocol_blocks_add_bit(&instance->decoder, !level); + + switch(instance->decoder.parser_step) { + case PocsagDecoderStepFoundSync: + if((instance->decoder.decode_data & POCSAG_CW_MASK) == POCSAG_FRAME_SYNC_CODE) { + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + case PocsagDecoderStepFoundPreamble: + // handle codewords + if(instance->decoder.decode_count_bit == POCSAG_CW_BITS) { + codeword = (uint32_t)(instance->decoder.decode_data & POCSAG_CW_MASK); + switch(codeword) { + case POCSAG_IDLE_CODE_WORD: + instance->codeword_idx++; + break; + case POCSAG_FRAME_SYNC_CODE: + instance->codeword_idx = 0; + break; + default: + // Here we expect only address messages + if(codeword >> 31 == 0) { + pocsag_decode_address_word(instance, codeword); + instance->decoder.parser_step = PocsagDecoderStepMessage; + } + instance->codeword_idx++; + } + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + + case PocsagDecoderStepMessage: + if(instance->decoder.decode_count_bit == POCSAG_CW_BITS) { + codeword = (uint32_t)(instance->decoder.decode_data & POCSAG_CW_MASK); + switch(codeword) { + case POCSAG_IDLE_CODE_WORD: + // Idle during the message stops the message + instance->codeword_idx++; + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + pocsag_message_done(instance); + break; + case POCSAG_FRAME_SYNC_CODE: + instance->codeword_idx = 0; + break; + default: + // In this state, both address and message words can arrive + if(codeword >> 31 == 0) { + pocsag_message_done(instance); + pocsag_decode_address_word(instance, codeword); + } else { + if(!pocsag_decode_message_word(instance, codeword)) { + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + pocsag_message_done(instance); + } + } + instance->codeword_idx++; + } + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + } + } +} + +uint8_t subghz_protocol_decoder_pocsag_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + uint8_t hash = 0; + for(size_t i = 0; i < furi_string_size(instance->done_msg); i++) + hash ^= furi_string_get_char(instance->done_msg, i); + return hash; +} + +bool subghz_protocol_decoder_pocsag_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + uint32_t msg_len; + + if(!pcsg_block_generic_serialize(&instance->generic, flipper_format, preset)) return false; + + msg_len = furi_string_size(instance->done_msg); + if(!flipper_format_write_uint32(flipper_format, "MsgLen", &msg_len, 1)) { + FURI_LOG_E(TAG, "Error adding MsgLen"); + return false; + } + + uint8_t* s = (uint8_t*)furi_string_get_cstr(instance->done_msg); + if(!flipper_format_write_hex(flipper_format, "Msg", s, msg_len)) { + FURI_LOG_E(TAG, "Error adding Msg"); + return false; + } + return true; +} + +bool subghz_protocol_decoder_pocsag_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + bool ret = false; + uint32_t msg_len; + uint8_t* buf; + + do { + if(!pcsg_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + + if(!flipper_format_read_uint32(flipper_format, "MsgLen", &msg_len, 1)) { + FURI_LOG_E(TAG, "Missing MsgLen"); + break; + } + + buf = malloc(msg_len); + if(!flipper_format_read_hex(flipper_format, "Msg", buf, msg_len)) { + FURI_LOG_E(TAG, "Missing Msg"); + free(buf); + break; + } + furi_string_set_strn(instance->done_msg, (const char*)buf, msg_len); + free(buf); + + ret = true; + } while(false); + return ret; +} + +void subhz_protocol_decoder_pocsag_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + furi_string_cat_printf(output, "%s\r\n", instance->generic.protocol_name); + furi_string_cat(output, instance->done_msg); +} + +const SubGhzProtocolDecoder subghz_protocol_pocsag_decoder = { + .alloc = subghz_protocol_decoder_pocsag_alloc, + .free = subghz_protocol_decoder_pocsag_free, + .reset = subghz_protocol_decoder_pocsag_reset, + .feed = subghz_protocol_decoder_pocsag_feed, + .get_hash_data = subghz_protocol_decoder_pocsag_get_hash_data, + .serialize = subghz_protocol_decoder_pocsag_serialize, + .deserialize = subghz_protocol_decoder_pocsag_deserialize, + .get_string = subhz_protocol_decoder_pocsag_get_string, +}; + +const SubGhzProtocol subghz_protocol_pocsag = { + .name = SUBGHZ_PROTOCOL_POCSAG_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Load, + + .decoder = &subghz_protocol_pocsag_decoder, +}; diff --git a/applications/plugins/pocsag_pager/protocols/pocsag.h b/applications/plugins/pocsag_pager/protocols/pocsag.h new file mode 100644 index 000000000..559fa3918 --- /dev/null +++ b/applications/plugins/pocsag_pager/protocols/pocsag.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include +#include +#include "pcsg_generic.h" +#include + +#define SUBGHZ_PROTOCOL_POCSAG_NAME "POCSAG" + +extern const SubGhzProtocol subghz_protocol_pocsag; diff --git a/applications/plugins/pocsag_pager/protocols/protocol_items.c b/applications/plugins/pocsag_pager/protocols/protocol_items.c new file mode 100644 index 000000000..7e6ebebbd --- /dev/null +++ b/applications/plugins/pocsag_pager/protocols/protocol_items.c @@ -0,0 +1,9 @@ +#include "protocol_items.h" + +const SubGhzProtocol* pocsag_pager_protocol_registry_items[] = { + &subghz_protocol_pocsag, +}; + +const SubGhzProtocolRegistry pocsag_pager_protocol_registry = { + .items = pocsag_pager_protocol_registry_items, + .size = COUNT_OF(pocsag_pager_protocol_registry_items)}; \ No newline at end of file diff --git a/applications/plugins/pocsag_pager/protocols/protocol_items.h b/applications/plugins/pocsag_pager/protocols/protocol_items.h new file mode 100644 index 000000000..3981cd2e3 --- /dev/null +++ b/applications/plugins/pocsag_pager/protocols/protocol_items.h @@ -0,0 +1,6 @@ +#pragma once +#include "../pocsag_pager_app_i.h" + +#include "pocsag.h" + +extern const SubGhzProtocolRegistry pocsag_pager_protocol_registry; diff --git a/applications/plugins/pocsag_pager/scenes/pocsag_pager_receiver.c b/applications/plugins/pocsag_pager/scenes/pocsag_pager_receiver.c new file mode 100644 index 000000000..3f41b9b5d --- /dev/null +++ b/applications/plugins/pocsag_pager/scenes/pocsag_pager_receiver.c @@ -0,0 +1,207 @@ +#include "../pocsag_pager_app_i.h" +#include "../views/pocsag_pager_receiver.h" + +static const NotificationSequence subghs_sequence_rx = { + &message_green_255, + + &message_vibro_on, + &message_note_c6, + &message_delay_50, + &message_sound_off, + &message_vibro_off, + + &message_delay_50, + NULL, +}; + +static const NotificationSequence subghs_sequence_rx_locked = { + &message_green_255, + + &message_display_backlight_on, + + &message_vibro_on, + &message_note_c6, + &message_delay_50, + &message_sound_off, + &message_vibro_off, + + &message_delay_500, + + &message_display_backlight_off, + NULL, +}; + +static void pocsag_pager_scene_receiver_update_statusbar(void* context) { + POCSAGPagerApp* app = context; + FuriString* history_stat_str; + history_stat_str = furi_string_alloc(); + if(!pcsg_history_get_text_space_left(app->txrx->history, history_stat_str)) { + FuriString* frequency_str; + FuriString* modulation_str; + + frequency_str = furi_string_alloc(); + modulation_str = furi_string_alloc(); + + pcsg_get_frequency_modulation(app, frequency_str, modulation_str); + + pcsg_view_receiver_add_data_statusbar( + app->pcsg_receiver, + furi_string_get_cstr(frequency_str), + furi_string_get_cstr(modulation_str), + furi_string_get_cstr(history_stat_str)); + + furi_string_free(frequency_str); + furi_string_free(modulation_str); + } else { + pcsg_view_receiver_add_data_statusbar( + app->pcsg_receiver, furi_string_get_cstr(history_stat_str), "", ""); + } + furi_string_free(history_stat_str); +} + +void pocsag_pager_scene_receiver_callback(PCSGCustomEvent event, void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static void pocsag_pager_scene_receiver_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + FuriString* str_buff; + str_buff = furi_string_alloc(); + + if(pcsg_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == + PCSGHistoryStateAddKeyNewDada) { + furi_string_reset(str_buff); + + pcsg_history_get_text_item_menu( + app->txrx->history, str_buff, pcsg_history_get_item(app->txrx->history) - 1); + pcsg_view_receiver_add_item_to_menu( + app->pcsg_receiver, + furi_string_get_cstr(str_buff), + pcsg_history_get_type_protocol( + app->txrx->history, pcsg_history_get_item(app->txrx->history) - 1)); + + pocsag_pager_scene_receiver_update_statusbar(app); + notification_message(app->notifications, &sequence_blink_green_10); + if(app->lock != PCSGLockOn) { + notification_message(app->notifications, &subghs_sequence_rx); + } else { + notification_message(app->notifications, &subghs_sequence_rx_locked); + } + } + subghz_receiver_reset(receiver); + furi_string_free(str_buff); + app->txrx->rx_key_state = PCSGRxKeyStateAddKey; +} + +void pocsag_pager_scene_receiver_on_enter(void* context) { + POCSAGPagerApp* app = context; + + FuriString* str_buff; + str_buff = furi_string_alloc(); + + if(app->txrx->rx_key_state == PCSGRxKeyStateIDLE) { + pcsg_preset_init(app, "FM95", 439987500, NULL, 0); + pcsg_history_reset(app->txrx->history); + app->txrx->rx_key_state = PCSGRxKeyStateStart; + } + + pcsg_view_receiver_set_lock(app->pcsg_receiver, app->lock); + + //Load history to receiver + pcsg_view_receiver_exit(app->pcsg_receiver); + for(uint8_t i = 0; i < pcsg_history_get_item(app->txrx->history); i++) { + furi_string_reset(str_buff); + pcsg_history_get_text_item_menu(app->txrx->history, str_buff, i); + pcsg_view_receiver_add_item_to_menu( + app->pcsg_receiver, + furi_string_get_cstr(str_buff), + pcsg_history_get_type_protocol(app->txrx->history, i)); + app->txrx->rx_key_state = PCSGRxKeyStateAddKey; + } + furi_string_free(str_buff); + pocsag_pager_scene_receiver_update_statusbar(app); + + pcsg_view_receiver_set_callback(app->pcsg_receiver, pocsag_pager_scene_receiver_callback, app); + subghz_receiver_set_rx_callback( + app->txrx->receiver, pocsag_pager_scene_receiver_add_to_history_callback, app); + + if(app->txrx->txrx_state == PCSGTxRxStateRx) { + pcsg_rx_end(app); + }; + if((app->txrx->txrx_state == PCSGTxRxStateIDLE) || + (app->txrx->txrx_state == PCSGTxRxStateSleep)) { + pcsg_begin( + app, + subghz_setting_get_preset_data_by_name( + app->setting, furi_string_get_cstr(app->txrx->preset->name))); + + pcsg_rx(app, app->txrx->preset->frequency); + } + + pcsg_view_receiver_set_idx_menu(app->pcsg_receiver, app->txrx->idx_menu_chosen); + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewReceiver); +} + +bool pocsag_pager_scene_receiver_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case PCSGCustomEventViewReceiverBack: + // Stop CC1101 Rx + if(app->txrx->txrx_state == PCSGTxRxStateRx) { + pcsg_rx_end(app); + pcsg_sleep(app); + }; + app->txrx->hopper_state = PCSGHopperStateOFF; + app->txrx->idx_menu_chosen = 0; + subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, app); + + app->txrx->rx_key_state = PCSGRxKeyStateIDLE; + pcsg_preset_init(app, "FM95", 439987500, NULL, 0); + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, POCSAGPagerSceneStart); + consumed = true; + break; + case PCSGCustomEventViewReceiverOK: + app->txrx->idx_menu_chosen = pcsg_view_receiver_get_idx_menu(app->pcsg_receiver); + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiverInfo); + consumed = true; + break; + case PCSGCustomEventViewReceiverConfig: + app->txrx->idx_menu_chosen = pcsg_view_receiver_get_idx_menu(app->pcsg_receiver); + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiverConfig); + consumed = true; + break; + case PCSGCustomEventViewReceiverOffDisplay: + notification_message(app->notifications, &sequence_display_backlight_off); + consumed = true; + break; + case PCSGCustomEventViewReceiverUnlock: + app->lock = PCSGLockOff; + consumed = true; + break; + default: + break; + } + } else if(event.type == SceneManagerEventTypeTick) { + if(app->txrx->hopper_state != PCSGHopperStateOFF) { + pcsg_hopper_update(app); + pocsag_pager_scene_receiver_update_statusbar(app); + } + if(app->txrx->txrx_state == PCSGTxRxStateRx) { + notification_message(app->notifications, &sequence_blink_cyan_10); + } + } + return consumed; +} + +void pocsag_pager_scene_receiver_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene.c b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene.c new file mode 100644 index 000000000..4644d99c0 --- /dev/null +++ b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene.c @@ -0,0 +1,30 @@ +#include "../pocsag_pager_app_i.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const pocsag_pager_scene_on_enter_handlers[])(void*) = { +#include "pocsag_pager_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const pocsag_pager_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "pocsag_pager_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const pocsag_pager_scene_on_exit_handlers[])(void* context) = { +#include "pocsag_pager_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers pocsag_pager_scene_handlers = { + .on_enter_handlers = pocsag_pager_scene_on_enter_handlers, + .on_event_handlers = pocsag_pager_scene_on_event_handlers, + .on_exit_handlers = pocsag_pager_scene_on_exit_handlers, + .scene_num = POCSAGPagerSceneNum, +}; diff --git a/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene.h b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene.h new file mode 100644 index 000000000..d5c64f9d9 --- /dev/null +++ b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) POCSAGPagerScene##id, +typedef enum { +#include "pocsag_pager_scene_config.h" + POCSAGPagerSceneNum, +} POCSAGPagerScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers pocsag_pager_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "pocsag_pager_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "pocsag_pager_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "pocsag_pager_scene_config.h" +#undef ADD_SCENE diff --git a/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_about.c b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_about.c new file mode 100644 index 000000000..2af33c8bf --- /dev/null +++ b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_about.c @@ -0,0 +1,74 @@ +#include "../pocsag_pager_app_i.h" +#include "../helpers/pocsag_pager_types.h" + +void pocsag_pager_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) { + POCSAGPagerApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, result); + } +} + +void pocsag_pager_scene_about_on_enter(void* context) { + POCSAGPagerApp* app = context; + + FuriString* temp_str; + temp_str = furi_string_alloc(); + furi_string_printf(temp_str, "\e#%s\n", "Information"); + + furi_string_cat_printf(temp_str, "Version: %s\n", PCSG_VERSION_APP); + furi_string_cat_printf(temp_str, "Developed by:\n%s\n\n", PCSG_DEVELOPED); + furi_string_cat_printf(temp_str, "Github: %s\n\n", PCSG_GITHUB); + + furi_string_cat_printf(temp_str, "\e#%s\n", "Description"); + furi_string_cat_printf(temp_str, "Receiving POCSAG Pager \nmessages\n\n"); + + furi_string_cat_printf(temp_str, "Supported protocols:\n"); + size_t i = 0; + const char* protocol_name = + subghz_environment_get_protocol_name_registry(app->txrx->environment, i++); + do { + furi_string_cat_printf(temp_str, "%s\n", protocol_name); + protocol_name = subghz_environment_get_protocol_name_registry(app->txrx->environment, i++); + } while(protocol_name != NULL); + + widget_add_text_box_element( + app->widget, + 0, + 0, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! \e!\n", + false); + widget_add_text_box_element( + app->widget, + 0, + 2, + 128, + 14, + AlignCenter, + AlignBottom, + "\e#\e! POCSAG Pager \e!\n", + false); + widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewWidget); +} + +bool pocsag_pager_scene_about_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void pocsag_pager_scene_about_on_exit(void* context) { + POCSAGPagerApp* app = context; + + // Clear views + widget_reset(app->widget); +} diff --git a/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_config.h b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_config.h new file mode 100644 index 000000000..8136af14f --- /dev/null +++ b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_config.h @@ -0,0 +1,5 @@ +ADD_SCENE(pocsag_pager, start, Start) +ADD_SCENE(pocsag_pager, about, About) +ADD_SCENE(pocsag_pager, receiver, Receiver) +ADD_SCENE(pocsag_pager, receiver_config, ReceiverConfig) +ADD_SCENE(pocsag_pager, receiver_info, ReceiverInfo) diff --git a/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_receiver_config.c b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_receiver_config.c new file mode 100644 index 000000000..154e7d270 --- /dev/null +++ b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_receiver_config.c @@ -0,0 +1,221 @@ +#include "../pocsag_pager_app_i.h" + +enum PCSGSettingIndex { + PCSGSettingIndexFrequency, + PCSGSettingIndexHopping, + PCSGSettingIndexModulation, + PCSGSettingIndexLock, +}; + +#define HOPPING_COUNT 2 +const char* const hopping_text[HOPPING_COUNT] = { + "OFF", + "ON", +}; +const uint32_t hopping_value[HOPPING_COUNT] = { + PCSGHopperStateOFF, + PCSGHopperStateRunnig, +}; + +uint8_t pocsag_pager_scene_receiver_config_next_frequency(const uint32_t value, void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { + if(value == subghz_setting_get_frequency(app->setting, i)) { + index = i; + break; + } else { + index = subghz_setting_get_frequency_default_index(app->setting); + } + } + return index; +} + +uint8_t pocsag_pager_scene_receiver_config_next_preset(const char* preset_name, void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { + if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { + index = i; + break; + } else { + // index = subghz_setting_get_frequency_default_index(app ->setting); + } + } + return index; +} + +uint8_t pocsag_pager_scene_receiver_config_hopper_value_index( + const uint32_t value, + const uint32_t values[], + uint8_t values_count, + void* context) { + furi_assert(context); + UNUSED(values_count); + POCSAGPagerApp* app = context; + + if(value == values[0]) { + return 0; + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig), + " -----"); + return 1; + } +} + +static void pocsag_pager_scene_receiver_config_set_frequency(VariableItem* item) { + POCSAGPagerApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + if(app->txrx->hopper_state == PCSGHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, index) / 1000000, + (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); + } else { + variable_item_set_current_value_index( + item, subghz_setting_get_frequency_default_index(app->setting)); + } +} + +static void pocsag_pager_scene_receiver_config_set_preset(VariableItem* item) { + POCSAGPagerApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, index)); + pcsg_preset_init( + app, + subghz_setting_get_preset_name(app->setting, index), + app->txrx->preset->frequency, + subghz_setting_get_preset_data(app->setting, index), + subghz_setting_get_preset_data_size(app->setting, index)); +} + +static void pocsag_pager_scene_receiver_config_set_hopping_running(VariableItem* item) { + POCSAGPagerApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, hopping_text[index]); + if(hopping_value[index] == PCSGHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_default_frequency(app->setting) / 1000000, + (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig), + text_buf); + app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig), + " -----"); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } + + app->txrx->hopper_state = hopping_value[index]; +} + +static void + pocsag_pager_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + POCSAGPagerApp* app = context; + if(index == PCSGSettingIndexLock) { + view_dispatcher_send_custom_event(app->view_dispatcher, PCSGCustomEventSceneSettingLock); + } +} + +void pocsag_pager_scene_receiver_config_on_enter(void* context) { + POCSAGPagerApp* app = context; + VariableItem* item; + uint8_t value_index; + + item = variable_item_list_add( + app->variable_item_list, + "Frequency:", + subghz_setting_get_frequency_count(app->setting), + pocsag_pager_scene_receiver_config_set_frequency, + app); + value_index = + pocsag_pager_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); + scene_manager_set_scene_state( + app->scene_manager, POCSAGPagerSceneReceiverConfig, (uint32_t)item); + variable_item_set_current_value_index(item, value_index); + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, value_index) / 1000000, + (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + + item = variable_item_list_add( + app->variable_item_list, + "Hopping:", + HOPPING_COUNT, + pocsag_pager_scene_receiver_config_set_hopping_running, + app); + value_index = pocsag_pager_scene_receiver_config_hopper_value_index( + app->txrx->hopper_state, hopping_value, HOPPING_COUNT, app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, hopping_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, + "Modulation:", + subghz_setting_get_preset_count(app->setting), + pocsag_pager_scene_receiver_config_set_preset, + app); + value_index = pocsag_pager_scene_receiver_config_next_preset( + furi_string_get_cstr(app->txrx->preset->name), app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, value_index)); + + variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); + variable_item_list_set_enter_callback( + app->variable_item_list, pocsag_pager_scene_receiver_config_var_list_enter_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewVariableItemList); +} + +bool pocsag_pager_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PCSGCustomEventSceneSettingLock) { + app->lock = PCSGLockOn; + scene_manager_previous_scene(app->scene_manager); + consumed = true; + } + } + return consumed; +} + +void pocsag_pager_scene_receiver_config_on_exit(void* context) { + POCSAGPagerApp* app = context; + variable_item_list_set_selected_item(app->variable_item_list, 0); + variable_item_list_reset(app->variable_item_list); +} diff --git a/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_receiver_info.c b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_receiver_info.c new file mode 100644 index 000000000..5f17d9fb7 --- /dev/null +++ b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_receiver_info.c @@ -0,0 +1,50 @@ +#include "../pocsag_pager_app_i.h" +#include "../views/pocsag_pager_receiver.h" + +void pocsag_pager_scene_receiver_info_callback(PCSGCustomEvent event, void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +static void pocsag_pager_scene_receiver_info_add_to_history_callback( + SubGhzReceiver* receiver, + SubGhzProtocolDecoderBase* decoder_base, + void* context) { + furi_assert(context); + POCSAGPagerApp* app = context; + + if(pcsg_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset) == + PCSGHistoryStateAddKeyUpdateData) { + pcsg_view_receiver_info_update( + app->pcsg_receiver_info, + pcsg_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); + subghz_receiver_reset(receiver); + + notification_message(app->notifications, &sequence_blink_green_10); + app->txrx->rx_key_state = PCSGRxKeyStateAddKey; + } +} + +void pocsag_pager_scene_receiver_info_on_enter(void* context) { + POCSAGPagerApp* app = context; + + subghz_receiver_set_rx_callback( + app->txrx->receiver, pocsag_pager_scene_receiver_info_add_to_history_callback, app); + pcsg_view_receiver_info_update( + app->pcsg_receiver_info, + pcsg_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen)); + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewReceiverInfo); +} + +bool pocsag_pager_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + return consumed; +} + +void pocsag_pager_scene_receiver_info_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_start.c b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_start.c new file mode 100644 index 000000000..d2a94facb --- /dev/null +++ b/applications/plugins/pocsag_pager/scenes/pocsag_pager_scene_start.c @@ -0,0 +1,58 @@ +#include "../pocsag_pager_app_i.h" + +typedef enum { + SubmenuIndexPOCSAGPagerReceiver, + SubmenuIndexPOCSAGPagerAbout, +} SubmenuIndex; + +void pocsag_pager_scene_start_submenu_callback(void* context, uint32_t index) { + POCSAGPagerApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void pocsag_pager_scene_start_on_enter(void* context) { + UNUSED(context); + POCSAGPagerApp* app = context; + Submenu* submenu = app->submenu; + + submenu_add_item( + submenu, + "Receive messages", + SubmenuIndexPOCSAGPagerReceiver, + pocsag_pager_scene_start_submenu_callback, + app); + submenu_add_item( + submenu, + "About", + SubmenuIndexPOCSAGPagerAbout, + pocsag_pager_scene_start_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, POCSAGPagerSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, POCSAGPagerViewSubmenu); +} + +bool pocsag_pager_scene_start_on_event(void* context, SceneManagerEvent event) { + POCSAGPagerApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexPOCSAGPagerAbout) { + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneAbout); + consumed = true; + } else if(event.event == SubmenuIndexPOCSAGPagerReceiver) { + scene_manager_next_scene(app->scene_manager, POCSAGPagerSceneReceiver); + consumed = true; + } + scene_manager_set_scene_state(app->scene_manager, POCSAGPagerSceneStart, event.event); + } + + return consumed; +} + +void pocsag_pager_scene_start_on_exit(void* context) { + POCSAGPagerApp* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/plugins/pocsag_pager/views/pocsag_pager_receiver.c b/applications/plugins/pocsag_pager/views/pocsag_pager_receiver.c new file mode 100644 index 000000000..d8398cbfe --- /dev/null +++ b/applications/plugins/pocsag_pager/views/pocsag_pager_receiver.c @@ -0,0 +1,440 @@ +#include "pocsag_pager_receiver.h" +#include "../pocsag_pager_app_i.h" +#include +#include + +#include +#include +#include + +#define FRAME_HEIGHT 12 +#define MAX_LEN_PX 112 +#define MENU_ITEMS 4u +#define UNLOCK_CNT 3 + +typedef struct { + FuriString* item_str; + uint8_t type; +} PCSGReceiverMenuItem; + +ARRAY_DEF(PCSGReceiverMenuItemArray, PCSGReceiverMenuItem, M_POD_OPLIST) + +#define M_OPL_PCSGReceiverMenuItemArray_t() ARRAY_OPLIST(PCSGReceiverMenuItemArray, M_POD_OPLIST) + +struct PCSGReceiverHistory { + PCSGReceiverMenuItemArray_t data; +}; + +typedef struct PCSGReceiverHistory PCSGReceiverHistory; + +static const Icon* ReceiverItemIcons[] = { + [SubGhzProtocolTypeUnknown] = &I_Quest_7x8, + [SubGhzProtocolTypeStatic] = &I_Message_8x7, + [SubGhzProtocolTypeDynamic] = &I_Lock_7x8, +}; + +typedef enum { + PCSGReceiverBarShowDefault, + PCSGReceiverBarShowLock, + PCSGReceiverBarShowToUnlockPress, + PCSGReceiverBarShowUnlock, +} PCSGReceiverBarShow; + +struct PCSGReceiver { + PCSGLock lock; + uint8_t lock_count; + FuriTimer* timer; + View* view; + PCSGReceiverCallback callback; + void* context; +}; + +typedef struct { + FuriString* frequency_str; + FuriString* preset_str; + FuriString* history_stat_str; + PCSGReceiverHistory* history; + uint16_t idx; + uint16_t list_offset; + uint16_t history_item; + PCSGReceiverBarShow bar_show; +} PCSGReceiverModel; + +void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock lock) { + furi_assert(pcsg_receiver); + pcsg_receiver->lock_count = 0; + if(lock == PCSGLockOn) { + pcsg_receiver->lock = lock; + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowLock; }, + true); + furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(1000)); + } else { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowDefault; }, + true); + } +} + +void pcsg_view_receiver_set_callback( + PCSGReceiver* pcsg_receiver, + PCSGReceiverCallback callback, + void* context) { + furi_assert(pcsg_receiver); + furi_assert(callback); + pcsg_receiver->callback = callback; + pcsg_receiver->context = context; +} + +static void pcsg_view_receiver_update_offset(PCSGReceiver* pcsg_receiver) { + furi_assert(pcsg_receiver); + + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + size_t history_item = model->history_item; + uint16_t bounds = history_item > 3 ? 2 : history_item; + + if(history_item > 3 && model->idx >= (int16_t)(history_item - 1)) { + model->list_offset = model->idx - 3; + } else if(model->list_offset < model->idx - bounds) { + model->list_offset = + CLAMP(model->list_offset + 1, (int16_t)(history_item - bounds), 0); + } else if(model->list_offset > model->idx - bounds) { + model->list_offset = CLAMP(model->idx - 1, (int16_t)(history_item - bounds), 0); + } + }, + true); +} + +void pcsg_view_receiver_add_item_to_menu( + PCSGReceiver* pcsg_receiver, + const char* name, + uint8_t type) { + furi_assert(pcsg_receiver); + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + PCSGReceiverMenuItem* item_menu = + PCSGReceiverMenuItemArray_push_raw(model->history->data); + item_menu->item_str = furi_string_alloc_set(name); + item_menu->type = type; + if((model->idx == model->history_item - 1)) { + model->history_item++; + model->idx++; + } else { + model->history_item++; + } + }, + true); + pcsg_view_receiver_update_offset(pcsg_receiver); +} + +void pcsg_view_receiver_add_data_statusbar( + PCSGReceiver* pcsg_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str) { + furi_assert(pcsg_receiver); + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + furi_string_set_str(model->frequency_str, frequency_str); + furi_string_set_str(model->preset_str, preset_str); + furi_string_set_str(model->history_stat_str, history_stat_str); + }, + true); +} + +static void pcsg_view_receiver_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box(canvas, 0, 0 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 0, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 1, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 1); + + canvas_draw_dot(canvas, 0, (0 + idx * FRAME_HEIGHT) + 11); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, 0 + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, (0 + idx * FRAME_HEIGHT) + 11); +} + +void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + elements_button_left(canvas, "Config"); + canvas_draw_line(canvas, 46, 51, 125, 51); + + bool scrollbar = model->history_item > 4; + FuriString* str_buff; + str_buff = furi_string_alloc(); + + PCSGReceiverMenuItem* item_menu; + + for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) { + size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); + item_menu = PCSGReceiverMenuItemArray_get(model->history->data, idx); + furi_string_set(str_buff, item_menu->item_str); + furi_string_replace_all(str_buff, "#", ""); + elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 7 : MAX_LEN_PX); + if(model->idx == idx) { + pcsg_view_receiver_draw_frame(canvas, i, scrollbar); + } else { + canvas_set_color(canvas, ColorBlack); + } + canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); + canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); + furi_string_reset(str_buff); + } + if(scrollbar) { + elements_scrollbar_pos(canvas, 128, 0, 49, model->idx, model->history_item); + } + furi_string_free(str_buff); + + canvas_set_color(canvas, ColorBlack); + + if(model->history_item == 0) { + canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 63, 46, "Scanning..."); + canvas_draw_line(canvas, 46, 51, 125, 51); + canvas_set_font(canvas, FontSecondary); + } + + switch(model->bar_show) { + case PCSGReceiverBarShowLock: + canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); + canvas_draw_str(canvas, 74, 62, "Locked"); + break; + case PCSGReceiverBarShowToUnlockPress: + canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); + canvas_set_font(canvas, FontSecondary); + elements_bold_rounded_frame(canvas, 14, 8, 99, 48); + elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); + canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42); + canvas_draw_dot(canvas, 17, 61); + break; + case PCSGReceiverBarShowUnlock: + canvas_draw_icon(canvas, 64, 55, &I_Unlock_7x8); + canvas_draw_str(canvas, 74, 62, "Unlocked"); + break; + default: + canvas_draw_str(canvas, 44, 62, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 62, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 62, furi_string_get_cstr(model->history_stat_str)); + break; + } +} + +static void pcsg_view_receiver_timer_callback(void* context) { + furi_assert(context); + PCSGReceiver* pcsg_receiver = context; + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowDefault; }, + true); + if(pcsg_receiver->lock_count < UNLOCK_CNT) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverOffDisplay, pcsg_receiver->context); + } else { + pcsg_receiver->lock = PCSGLockOff; + pcsg_receiver->callback(PCSGCustomEventViewReceiverUnlock, pcsg_receiver->context); + } + pcsg_receiver->lock_count = 0; +} + +bool pcsg_view_receiver_input(InputEvent* event, void* context) { + furi_assert(context); + PCSGReceiver* pcsg_receiver = context; + + if(pcsg_receiver->lock == PCSGLockOn) { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowToUnlockPress; }, + true); + if(pcsg_receiver->lock_count == 0) { + furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(1000)); + } + if(event->key == InputKeyBack && event->type == InputTypeShort) { + pcsg_receiver->lock_count++; + } + if(pcsg_receiver->lock_count >= UNLOCK_CNT) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverUnlock, pcsg_receiver->context); + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { model->bar_show = PCSGReceiverBarShowUnlock; }, + true); + pcsg_receiver->lock = PCSGLockOff; + furi_timer_start(pcsg_receiver->timer, pdMS_TO_TICKS(650)); + } + + return true; + } + + if(event->key == InputKeyBack && event->type == InputTypeShort) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverBack, pcsg_receiver->context); + } else if( + event->key == InputKeyUp && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + if(model->idx != 0) model->idx--; + }, + true); + } else if( + event->key == InputKeyDown && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + if(model->idx != model->history_item - 1) model->idx++; + }, + true); + } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverConfig, pcsg_receiver->context); + } else if(event->key == InputKeyOk && event->type == InputTypeShort) { + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + if(model->history_item != 0) { + pcsg_receiver->callback(PCSGCustomEventViewReceiverOK, pcsg_receiver->context); + } + }, + false); + } + + pcsg_view_receiver_update_offset(pcsg_receiver); + + return true; +} + +void pcsg_view_receiver_enter(void* context) { + furi_assert(context); +} + +void pcsg_view_receiver_exit(void* context) { + furi_assert(context); + PCSGReceiver* pcsg_receiver = context; + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + furi_string_reset(model->frequency_str); + furi_string_reset(model->preset_str); + furi_string_reset(model->history_stat_str); + for + M_EACH(item_menu, model->history->data, PCSGReceiverMenuItemArray_t) { + furi_string_free(item_menu->item_str); + item_menu->type = 0; + } + PCSGReceiverMenuItemArray_reset(model->history->data); + model->idx = 0; + model->list_offset = 0; + model->history_item = 0; + }, + false); + furi_timer_stop(pcsg_receiver->timer); +} + +PCSGReceiver* pcsg_view_receiver_alloc() { + PCSGReceiver* pcsg_receiver = malloc(sizeof(PCSGReceiver)); + + // View allocation and configuration + pcsg_receiver->view = view_alloc(); + + pcsg_receiver->lock = PCSGLockOff; + pcsg_receiver->lock_count = 0; + view_allocate_model(pcsg_receiver->view, ViewModelTypeLocking, sizeof(PCSGReceiverModel)); + view_set_context(pcsg_receiver->view, pcsg_receiver); + view_set_draw_callback(pcsg_receiver->view, (ViewDrawCallback)pcsg_view_receiver_draw); + view_set_input_callback(pcsg_receiver->view, pcsg_view_receiver_input); + view_set_enter_callback(pcsg_receiver->view, pcsg_view_receiver_enter); + view_set_exit_callback(pcsg_receiver->view, pcsg_view_receiver_exit); + + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + model->frequency_str = furi_string_alloc(); + model->preset_str = furi_string_alloc(); + model->history_stat_str = furi_string_alloc(); + model->bar_show = PCSGReceiverBarShowDefault; + model->history = malloc(sizeof(PCSGReceiverHistory)); + PCSGReceiverMenuItemArray_init(model->history->data); + }, + true); + pcsg_receiver->timer = + furi_timer_alloc(pcsg_view_receiver_timer_callback, FuriTimerTypeOnce, pcsg_receiver); + return pcsg_receiver; +} + +void pcsg_view_receiver_free(PCSGReceiver* pcsg_receiver) { + furi_assert(pcsg_receiver); + + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + furi_string_free(model->frequency_str); + furi_string_free(model->preset_str); + furi_string_free(model->history_stat_str); + for + M_EACH(item_menu, model->history->data, PCSGReceiverMenuItemArray_t) { + furi_string_free(item_menu->item_str); + item_menu->type = 0; + } + PCSGReceiverMenuItemArray_clear(model->history->data); + free(model->history); + }, + false); + furi_timer_free(pcsg_receiver->timer); + view_free(pcsg_receiver->view); + free(pcsg_receiver); +} + +View* pcsg_view_receiver_get_view(PCSGReceiver* pcsg_receiver) { + furi_assert(pcsg_receiver); + return pcsg_receiver->view; +} + +uint16_t pcsg_view_receiver_get_idx_menu(PCSGReceiver* pcsg_receiver) { + furi_assert(pcsg_receiver); + uint32_t idx = 0; + with_view_model( + pcsg_receiver->view, PCSGReceiverModel * model, { idx = model->idx; }, false); + return idx; +} + +void pcsg_view_receiver_set_idx_menu(PCSGReceiver* pcsg_receiver, uint16_t idx) { + furi_assert(pcsg_receiver); + with_view_model( + pcsg_receiver->view, + PCSGReceiverModel * model, + { + model->idx = idx; + if(model->idx > 2) model->list_offset = idx - 2; + }, + true); + pcsg_view_receiver_update_offset(pcsg_receiver); +} diff --git a/applications/plugins/pocsag_pager/views/pocsag_pager_receiver.h b/applications/plugins/pocsag_pager/views/pocsag_pager_receiver.h new file mode 100644 index 000000000..5ea2d4859 --- /dev/null +++ b/applications/plugins/pocsag_pager/views/pocsag_pager_receiver.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include "../helpers/pocsag_pager_types.h" +#include "../helpers/pocsag_pager_event.h" + +typedef struct PCSGReceiver PCSGReceiver; + +typedef void (*PCSGReceiverCallback)(PCSGCustomEvent event, void* context); + +void pcsg_view_receiver_set_lock(PCSGReceiver* pcsg_receiver, PCSGLock keyboard); + +void pcsg_view_receiver_set_callback( + PCSGReceiver* pcsg_receiver, + PCSGReceiverCallback callback, + void* context); + +PCSGReceiver* pcsg_view_receiver_alloc(); + +void pcsg_view_receiver_free(PCSGReceiver* pcsg_receiver); + +View* pcsg_view_receiver_get_view(PCSGReceiver* pcsg_receiver); + +void pcsg_view_receiver_add_data_statusbar( + PCSGReceiver* pcsg_receiver, + const char* frequency_str, + const char* preset_str, + const char* history_stat_str); + +void pcsg_view_receiver_add_item_to_menu( + PCSGReceiver* pcsg_receiver, + const char* name, + uint8_t type); + +uint16_t pcsg_view_receiver_get_idx_menu(PCSGReceiver* pcsg_receiver); + +void pcsg_view_receiver_set_idx_menu(PCSGReceiver* pcsg_receiver, uint16_t idx); + +void pcsg_view_receiver_exit(void* context); diff --git a/applications/plugins/pocsag_pager/views/pocsag_pager_receiver_info.c b/applications/plugins/pocsag_pager/views/pocsag_pager_receiver_info.c new file mode 100644 index 000000000..4811f3902 --- /dev/null +++ b/applications/plugins/pocsag_pager/views/pocsag_pager_receiver_info.c @@ -0,0 +1,137 @@ +#include "pocsag_pager_receiver.h" +#include "../pocsag_pager_app_i.h" +#include "pocsag_pager_icons.h" +#include "../protocols/pcsg_generic.h" +#include +#include + +#define abs(x) ((x) > 0 ? (x) : -(x)) + +struct PCSGReceiverInfo { + View* view; +}; + +typedef struct { + FuriString* protocol_name; + PCSGBlockGeneric* generic; +} PCSGReceiverInfoModel; + +void pcsg_view_receiver_info_update(PCSGReceiverInfo* pcsg_receiver_info, FlipperFormat* fff) { + furi_assert(pcsg_receiver_info); + furi_assert(fff); + + with_view_model( + pcsg_receiver_info->view, + PCSGReceiverInfoModel * model, + { + flipper_format_rewind(fff); + flipper_format_read_string(fff, "Protocol", model->protocol_name); + + pcsg_block_generic_deserialize(model->generic, fff); + }, + true); +} + +void pcsg_view_receiver_info_draw(Canvas* canvas, PCSGReceiverInfoModel* model) { + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + if(model->generic->result_ric != NULL) { + elements_text_box( + canvas, + 0, + 0, + 128, + 64, + AlignLeft, + AlignTop, + furi_string_get_cstr(model->generic->result_ric), + false); + } + if(model->generic->result_msg != NULL) { + elements_text_box( + canvas, + 0, + 12, + 128, + 64, + AlignLeft, + AlignTop, + furi_string_get_cstr(model->generic->result_msg), + false); + } +} + +bool pcsg_view_receiver_info_input(InputEvent* event, void* context) { + furi_assert(context); + //PCSGReceiverInfo* pcsg_receiver_info = context; + + if(event->key == InputKeyBack) { + return false; + } + + return true; +} + +void pcsg_view_receiver_info_enter(void* context) { + furi_assert(context); +} + +void pcsg_view_receiver_info_exit(void* context) { + furi_assert(context); + PCSGReceiverInfo* pcsg_receiver_info = context; + + with_view_model( + pcsg_receiver_info->view, + PCSGReceiverInfoModel * model, + { furi_string_reset(model->protocol_name); }, + false); +} + +PCSGReceiverInfo* pcsg_view_receiver_info_alloc() { + PCSGReceiverInfo* pcsg_receiver_info = malloc(sizeof(PCSGReceiverInfo)); + + // View allocation and configuration + pcsg_receiver_info->view = view_alloc(); + + view_allocate_model( + pcsg_receiver_info->view, ViewModelTypeLocking, sizeof(PCSGReceiverInfoModel)); + view_set_context(pcsg_receiver_info->view, pcsg_receiver_info); + view_set_draw_callback( + pcsg_receiver_info->view, (ViewDrawCallback)pcsg_view_receiver_info_draw); + view_set_input_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_input); + view_set_enter_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_enter); + view_set_exit_callback(pcsg_receiver_info->view, pcsg_view_receiver_info_exit); + + with_view_model( + pcsg_receiver_info->view, + PCSGReceiverInfoModel * model, + { + model->generic = malloc(sizeof(PCSGBlockGeneric)); + model->protocol_name = furi_string_alloc(); + }, + true); + + return pcsg_receiver_info; +} + +void pcsg_view_receiver_info_free(PCSGReceiverInfo* pcsg_receiver_info) { + furi_assert(pcsg_receiver_info); + + with_view_model( + pcsg_receiver_info->view, + PCSGReceiverInfoModel * model, + { + furi_string_free(model->protocol_name); + free(model->generic); + }, + false); + + view_free(pcsg_receiver_info->view); + free(pcsg_receiver_info); +} + +View* pcsg_view_receiver_info_get_view(PCSGReceiverInfo* pcsg_receiver_info) { + furi_assert(pcsg_receiver_info); + return pcsg_receiver_info->view; +} diff --git a/applications/plugins/pocsag_pager/views/pocsag_pager_receiver_info.h b/applications/plugins/pocsag_pager/views/pocsag_pager_receiver_info.h new file mode 100644 index 000000000..dfc85ec88 --- /dev/null +++ b/applications/plugins/pocsag_pager/views/pocsag_pager_receiver_info.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "../helpers/pocsag_pager_types.h" +#include "../helpers/pocsag_pager_event.h" +#include + +typedef struct PCSGReceiverInfo PCSGReceiverInfo; + +void pcsg_view_receiver_info_update(PCSGReceiverInfo* pcsg_receiver_info, FlipperFormat* fff); + +PCSGReceiverInfo* pcsg_view_receiver_info_alloc(); + +void pcsg_view_receiver_info_free(PCSGReceiverInfo* pcsg_receiver_info); + +View* pcsg_view_receiver_info_get_view(PCSGReceiverInfo* pcsg_receiver_info);