From 271c65a969e20a7194b716b23d19fc23a9ce3fa0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:51:28 +0300 Subject: [PATCH] subghz: add jarolift protocol and various fixes --- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../main/subghz/helpers/subghz_gen_info.c | 9 + .../main/subghz/helpers/subghz_gen_info.h | 6 + .../helpers/subghz_txrx_create_protocol_key.c | 30 + .../helpers/subghz_txrx_create_protocol_key.h | 8 + .../resources/subghz/assets/keeloq_mfcodes | 137 +-- .../subghz/scenes/subghz_scene_set_button.c | 5 + .../subghz/scenes/subghz_scene_set_counter.c | 16 + .../subghz/scenes/subghz_scene_set_seed.c | 2 + .../subghz/scenes/subghz_scene_set_serial.c | 8 + .../subghz/scenes/subghz_scene_set_type.c | 11 + lib/subghz/protocols/alutech_at_4n.c | 13 +- lib/subghz/protocols/jarolift.c | 777 ++++++++++++++++++ lib/subghz/protocols/jarolift.h | 108 +++ lib/subghz/protocols/keeloq_common.h | 2 + lib/subghz/protocols/kinggates_stylo_4k.c | 8 +- lib/subghz/protocols/protocol_items.c | 2 +- lib/subghz/protocols/protocol_items.h | 1 + lib/subghz/protocols/public_api.h | 18 + targets/f7/api_symbols.csv | 1 + 20 files changed, 1081 insertions(+), 82 deletions(-) create mode 100644 lib/subghz/protocols/jarolift.c create mode 100644 lib/subghz/protocols/jarolift.h diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 4fb40f5c0..011b53025 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -72,6 +72,7 @@ typedef enum { SetTypeSomfyTelis, SetTypeKingGatesStylo4k, SetTypeBenincaARC, + SetTypeJarolift, SetTypeANMotorsAT4, SetTypeAlutechAT4N, SetTypePhoenix_V2_433, diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index a0f07a1d8..bfa609e32 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -541,6 +541,15 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .beninca_arc.btn = 0x02, .beninca_arc.cnt = 0x03}; break; + case SetTypeJarolift: + gen_info = (GenInfo){ + .type = GenJarolift, + .mod = "AM650", + .freq = 433920000, + .jarolift.serial = key & 0xFFFFF00, + .jarolift.btn = 0x02, + .jarolift.cnt = 0x03}; + break; case SetTypeMotorline433: gen_info = (GenInfo){ .type = GenKeeloq, diff --git a/applications/main/subghz/helpers/subghz_gen_info.h b/applications/main/subghz/helpers/subghz_gen_info.h index e47c218d6..a680b7ba5 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.h +++ b/applications/main/subghz/helpers/subghz_gen_info.h @@ -12,6 +12,7 @@ typedef enum { GenSomfyTelis, GenKingGatesStylo4k, GenBenincaARC, + GenJarolift, GenNiceFlorS, GenSecPlus1, GenSecPlus2, @@ -73,6 +74,11 @@ typedef struct { uint8_t btn; uint32_t cnt; } beninca_arc; + struct { + uint32_t serial; + uint8_t btn; + uint16_t cnt; + } jarolift; struct { uint32_t serial; uint8_t btn; diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 075720dfc..27fb71f5b 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -395,6 +395,36 @@ bool subghz_txrx_gen_beninca_arc_protocol( return res; } +bool subghz_txrx_gen_jarolift_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint8_t btn, + uint16_t cnt) { + SubGhzTxRx* txrx = context; + + bool res = false; + + txrx->transmitter = + subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_JAROLIFT_NAME); + subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0); + + if(txrx->transmitter && subghz_protocol_jarolift_create_data( + subghz_transmitter_get_protocol_instance(txrx->transmitter), + txrx->fff_data, + serial, + btn, + cnt, + txrx->preset)) { + res = true; + } + + subghz_transmitter_free(txrx->transmitter); + + return res; +} + bool subghz_txrx_gen_secplus_v2_protocol( SubGhzTxRx* instance, const char* name_preset, diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h index afc1059b5..285770975 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -124,6 +124,14 @@ bool subghz_txrx_gen_beninca_arc_protocol( uint8_t btn, uint32_t cnt); +bool subghz_txrx_gen_jarolift_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint8_t btn, + uint16_t cnt); + bool subghz_txrx_gen_came_atomo_protocol( void* context, const char* preset_name, diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index b55548844..ae6315a2d 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,71 +1,72 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 4F 77 4F 20 66 75 72 72 79 20 49 56 20 55 77 55 -1D0560740B25F58EE0E85BF949139971E5AA08C5499CC74B11992D124C281012 -C05E2D2C715D8E24C518EF2841DA02173C05DD5BA5310EE85D09709500DB1726 -9EA5721836369FF918859077F50E33100F7AC53E8E8F31E25296579F875359DF -D2A8AD1B65BC66B459525124CDC5011C79D98F542702FC69EABD64F908C0D80D -2FA5F078BEB59851D42BC7E4E331AE3A8C384892DF003238CDA82450A6CD02AC -E54ED5F49A093BC938521195C86FECF35FB6EC463C54C6E1609592DC5FA03CCB5E1EDA362FC9AB008C85E66B60147EBA -048F4A28B18496487D65A924F4E37766C3563F41090D442DE61D7A5DD82F5FF1 -DA876A11401727E5102B578F87CCF9596AB9D9925FC90CDF99C9DEC7261B2C8C -03D3842335D69A3AA42452274130B3FAAEE6087CB8D783B0E770062C034BB302 -7F50401E9FFAF10D1F61067C2E830EBBEC7C8B3B20F5C0AC2E10E68912BF2C82 -160BC0CA2FF01E076830F29846C1F6CCBAC7857F2043E8163449048BA8C99AA8 -09357F089CB148DDC578E0F11EC10659EF68A57440700F584922CB9842E2BF08AC977CCAA11355E89FA5C18113349F5E -910D166F40F264225BD4C8EB16C5CC6374F8F8E1202D5BF28FA2E8BA38E420A36E67611E6D151051F7C74843E4A72BBC -D9ACEBC528D7CA74B894A0378095E03C9BDBBE13DDA3FCD6D60CD3CAC49746B8 -90F94FD262E1859B7E5C08E7ED5B16CDB56D0E930034E315CD011DE3759150DA -6620E176FC61DF250EF2BBC2AB27E0DA45A6E1E27F4E94405EE01C36E892EFBF -7D6A062453236E4C353F19B875D079157D016B223B7D429C8F5BEA9D0F703EED -20631589CB541B32C23CA7EA93B12E2016B5A90A2C9CAA6F5849C0B67158EA635858F26A86282C124AEA3FD31B7FD365 -E6CF167CEA0639D1504E0BFC8BAAA7FC7FA36A2286CBBE1312803422018A9F00 -0EB81E23FF37366BA4890CBD46BC8AF5A2CD56E9802B8DF5CDDD5114F677CDF6 -62195292F6F920DAF91F812FC3B94E8539C157D35D3BC94F2FB7A8481406C655AC0F112C4E10AC36892D43D95827BC1D -65FB9BEBDB9BE975168D47B02CA8A2E0ADE2CD1949E90B06689B0475395663BD5AF3C8FEC42C4138CBBB8956AC55B475 -EF0A6961C754FCDBAE0222099DD8AA38DD6A92BCD53A576E105BC5BBD23400B7 -A608752C8602A5BD538441DE046DD7AE011FBA87210B57372BCBB471ECC2B720 -222C83DDC445F72152E563CD068851389364C1D83C9F7D6353325EFD553560B1 -96977544F2F821408C1A88FADB9B1E12D9CA97638A622190F83BC640508B6029 -B596A98BDDA5BD2FF1F812BE67FD00456D4D313E9497147E2439B51972B6B752 -FC276CAC90756F397C3BC616631CE8B9257E6C25D0DC15C5ED1CEB439ACF04D9 -383DD624B98E650E5B4BA28990B4D1912B785C689E6B6A05D77B47B6501CD98F -410814DC8B38DD6EF781B55CA02730092F252082A77400AD90F22BF45A41C849 -8DA240E13E8B512B50FF4504A61037D0A3920B00523D51EEF9996CC3907C175E -E655B7C31E346154C5EF7C59E3A710A2A2F145E7403E4ADB388B3A27D6FF59DE -E2FB7F96EA16B20589995E95D0959B4ECC1EDDF86E347EB85FD29D0D5933A2A1 -00D910F2B050900735A8446220FF7321813252862A69C05A7A534118E50E61DB -BA1B9E7E26E04542183F085060421553CDD3FD9034AAAFCB7EE980A68B98087E -216EBA33FCE4B834BF64621E557923D8AE41F5895266B7BBDFCA6EAF985F036E -2E9075A45ED6D86C172C9ADAAF5E991DA8DA9CBF2F24D746D22A331E236FBA4A -04E4B185C150AF45A67E15D68282C7558B13BFFB05BFCB71BCFD2B92DE5D9701 -1FDC4D759EA89681F76A8F7D336118FE6801EFD10D73925C2749775D9DFED282 -FFB32167FBF860418AAABF29B0D4FEF57BB07454ECF4BD2CF175D44E84C04CED -7C6E419658872D298F2E7B02568B9ED870FDFFE5082ED0BBE689FD04EFFFE7E7 -ADAB0F3108398C75ECC6D2E572960B5685C336DDF3D6F5B9C12D069F27BA15A8 -DAC772C1A81181738CDB8C0E89C2E5B7A57E2DA65CA15232DE96A3C4A599A0E9 -7F6204274D90E88B3F5D5AC86EEEE76C27C0D083E79ADA7BCF7D060FE6F05A3E -5BAB4CCB593418CD8965C09C0925EDB78BB4C8A10892F264DF12F50E532F006E -7C67525C921ABAEC4BFAA376162A7B2B2827AE4C9840DC37F067FE3B72BE7304B6EEB5FB1AD17CFF5F079EFECC04AD56 -94FF6DD4CE63381778E86E61423EDDDD9CC71C3462D66F1AD9A0AD5D378AECE2 -E9CA5BA5C6D6101EA3A51F28E48D49789D60273A9F70D56020D003265517AFA3 -EBF55649D226E9FDAAB57C2E1D75E3FD3A8D216488E97D00E932B3542E731D20 -2B576C3616DDAFEE23A8ECE24BBD89590C8C2D551F14D8EFA4DBB30216F89C31 -EE8D3CF3C503DE4FF368F23585F1A7017FF66A910696A6760AFD2B9822911960 -A32910F791C5EE9998DCE5371B3252427315C9D11AD506CE65760611D873C81D -740646D11A32C65A8549B3AEA8A499866C35D926B2BA21ED73934AA37ADCC1E6 -0E66EF4CA934A5D1ACED28CBEEAA3AF7941E10918DA79379090B6339F11E267E -D79D8666FD947B0D0D504FF10B048B147CB000AD8CDC1F0DEB395FB72B789963 -2F7BA07F18F4A91AEDC08867E9CC4B8689B0831A7CE0E0AEF3D92C0CA9BBC698 -1AB4351CFF02CC600C972CE87F69B23F8ECCC32A90BD5F429F8017A80306F23D -ED2AD447E7DF7A34D78A313395FA1C3AF63CE02B77A5B08CE19493CFA1173232 -C8C8DEAF10AA3994EE7D6DC8E1EB403042627E0F3524409F40C03A7C0C106A80 -5778B4A3E7BE82C07BA6A311A87649F3C7AE5107A89571E14AEF05B9E285C87A -30080347DB3B580B18E8EAC66E1B7227F791773F0342EE0DF8267EC993EC3F24 -2DB3B2A17C165B5C6A1D4944A5B595016588F028DD4F763C4ED6B7FE7849E918 -C1F0CF343B77F31D9A2E810821393EE9D1E0D4B54A87B2DD8CCFBB16FBD77A75B50A0E78D1E8A86310572443731B9DB5 -88EF373C37AADAE1155E7DBDBB7E0B048E3BFEFB412DC49EA8A48E1544B6DC87 -98694781F3EE698ABA8D2CDFFB1CA0425AA17BFE904FC7812E65A78DF1CA06C2 -6BBBCA6672311E1A3BF7001B3222890C8A68A8B7D87DE91624BB9D1FEC0E2728 -4550A44B654085C3A3620B5D4D2C6A7F962275BC5926B9B7E3A706F128BF6D6C967E2BF2ACD4DD000BDC8BDE69684F6F +IV: 6E 6F 74 69 63 65 73 20 62 75 6C 67 65 4F 77 4F +212F687B2D38E6E9066E95894E455A6AD3A8861CDDAFD09DBC2557506676332D +2CFF25E9743E4588820D24998B5047E8019C6E200922BCB66830C7F722822A79 +74AA0EF345C27F583042C4EDDBB83D5446387D4E5B31DFD3F7B9D09F0662A0AA95BEB40BF37678954CAE0200B2898D22 +6998443F71D17938425CD231B8500E769976601315D79D673AA37E4F9BDCE50B +3335691566FEDDDE5828BE0A5BBBD9AC013987A1134E68020D3F0F477E09BC71 +1B3A3CBB3D56783220BEAECEA15FFF5CD92AF44F63547F6F84F4533D2B3D820B +A1ECFE74E714D0E1514D0C18903598E7FB7E7AF5C0165DEDE85DFAE05B26C0EC +BA3F2EFBE7CB6671206EA90DA903FF48CCABEC9F393D55B9DB3B7E207A48C059 +CA91805C8ADF02501B41D6BF0C69D7C3DD6E218D4B4EB6BED2FDE1367256E544 +5465E78D877F2594AF40115723C8AC71D09F60ACCC8121CD3B6BA271BEA43B5C +BB1D3DE0A048694FC86CA8E79BB7BFBB1EF91515C44E917FD5017676EE5C4B13 +48C08D8B2B42D025D2557CC650EDA3C5CC301CD71169CFE0044C07A95224109B +3DAE6C5B94C237FF10CF82FAA58517DB4DD4E4D82FD81CFAAA4836C9B8460CE1 +645F8F5D213889BAB0F437497856A0CF402E3D4A3679258BB0BF2B768D1F1714 +AFC3C462698C795B1A407F27F499CD1757931BD676BA43E4B41E8EF2672EC4AE +5A19F0D1CD87C40883D7BE51377CDAD94C977B4F6CFCCECED91D56867C9EC211 +716645BD4F5B18002417A92D6A2BD29D18087F7ADB549A90A2661EB4163EF190 +54B83F7F7F50AD4C3DC3498822240AEB7D9A43728704F14691A39C79FAC9B6A3 +FFDDCEFAFF3ADA3616B388684B1AD4987B83E459BA5037E377FE17CC7EDAA430 +9CC98590A371E580B164A5F1369945A6DB653797331EC898755E84BE93EDF3EF +63697AD15064194A3986CC1F3111A09E3D86AC8D370A8EC4D22EDDA4B9661B63 +F7CE4FA63EA9D388AE4AB6A468768AE91E2E3CF6F1D9A5EA17845466A6801A8C +38B2DE32CF36D68842CB31FD97394892B76FB4E81FCFB33164676BAB354939DE +93192FA1D7E347AF9FC98EE49A8B02869527511D0B263FD3B08CCFE645A39F51 +3D5F832CF75D94B58B078B4EE0DFC1D987795CD49959517F391F9E9F09CA187C +6CBCCCE0F8E6A833D5CA18E09E821A7F93B9FD7D912632E277B1721985F4B701 +5896050E9F4950CD7061ED4DD3BA9FC6013CA52F5C1B0493110FA3934116AC66 +F0D3FF283FBD7CFBFE697DBE752EDD5A1F124DCCAE3B629A146E0E85A7ED4E06 +6B6C7C365A5CABA2F1023ED1F97D182F0B9D575C64393272E19B8B809DF3C329 +1AF30761D85AE1C6DD4D78D4ECE79983C7F58ECE859709570262811CFCA8C97F +FC86201D96F4AE36C63A1C00DA34AC14BFDE317383D19A534362E6149E860366 +54F55F34A27E1D690F56269D29C795105C8407A4E1A9ED431588F559948C115A +4DD62ECD2FE8CBB783E3D7F0B1D95496D585405DE5AEB07C8A4E053B3AAAD808 +EDB356AFA869EFB987F099239449D6B9F1469EA6D8C9F0A6B4171F685E2853A1 +03C058A2C0DB83179E520F035BBAAB01673EFE4FDE717A0F5D1AB6B81BC9D033 +D2F33C08136AB161EED6121E721F8C479CED5F26A1B7DEEDEA8580314C232C5E +51CB3275D6D7250CD88A446242590C3DFD36DF83F5E34379DE73601511CD24B9 +ED4EC928AC505CBEFA6ACB2901C8E2221FC5073624B0B78F8365DD0442EFBA10 +76F959BBF8A4EC4AC22F41F06719BCBA58F070CB65B282BFC2C99F6CC02E30DB +0A44A7C5D4ECF2F08CBDF3FE947D45C6AC586D4558DBFB36719927F58EBB525B +F096777DDC5BF671CBF1A9682EBEE8308AA1002F6CB6899D575A30CFACB30125C8378864C77922360ADD0AC486EFF7F4 +372863D5B6F6D7A671AB734A99403AF230DEBBBA435AB8F236B179960FAFDFC6 +6CC31C5E86CF6851D92FA973253B3B1FC9023B150980A216A63720CD74592EB7 +6643B2CA94415FE4A4E25A3086B8370172C53283360A7BD54B322BD889A5A831 +03209037BD7E96A16C69A4F0B6FEB3F2ABA1408353BE09C53485CB7C2362A490 +BE4619EC34F7FB8DFE53A8ABFDE6A831895B88D0AB1A9B4861BC7EC7F1121611 +41223F7AD8B0F28D08C1EEC2B96324F337F6428759032CAE58A04E8511BC4CBCE497EE1502F18BED5B5038E719F4F085 +1533D9B78F16803DFD8AC0C9C85ECEB6BABE9ADF83BF0B6692857BAA03843444064FCDBC89464D9E002AAFF53C65098B +466CC36F82D1AF9BA0704789B2F8B249E79D4964E7E5CB74917760E7D30AB7FA +E9673C0CF35137C179132DDCA57869E285FE6014BB32C5DD78E7549ED0076906 +A511A9999C777750FA348772A9E2881A75DEC94ED833AC282037B3E10913B150 +F20652B2CA0E0ADD34331D2462CD5AFACC4E01F24EF17C97CFF57089039AD7D8 +AFCB6518894626B9E8AFC7E7D9D0430D1D820969CE79B61B91E63D08D4995308D8403705AE4E25AB0F254D47FB03A1A2 +C43987548689A1DD9A65A3BE76F97A8470263ACAC0696B5882383AE97893B5D6 +7FB47AC9A16094C38D436F9932041A40F6B4644A633361B47C6F6E4A8F2E7C26 +BDD48C4689912872055999ECADCF93D152ACDEC34C6318F2FBBBA1A34C8CC166FCBA31AED11F95C973B21A646828CEEA +1582E265DF46C8A9A019AF338104C267873FD3C2C7AF290263FEF921CC9926E76F37FA31881307C675CF37F8A5A236D7 +790D8AF6D9F5B634F953F5751378BA5412425B950C696549902EB6A9383630D0 +6F44FF0DB55651218C81B41153C9D77B1911C5BFA70650FB9326DD6B8A1479EE +4B0891C1C24E004AB2869A858941C47346EF0789DFAEA1CD1B0E008A73AF2F7E +7E9F990652FD6632612866B6ECA726EAE2112234EBCA59D0C1BF220B94E1F472 +9C08E9DAE977DD30E61E6634F51BFD654330C34E9FB3AA2E8856B428141D7164 +7C0E756C593FFBBE2609F63D142A0D53F99772EB0722616DFC7B203BA70AA5AE +52C03D8984496DFF95503D843CA1980A6CA46AAE3390C6D4D38E277A0937429C185C118B98B6EBE0711F10F47F8D8F3F +DE8A5BFD2AB351BCAD1D7817737946EF2445E3C974C9E11BF7AD058463F0E437 +D09ABB320F59A75C4B2EE8B52E45D52C46226DD8BC8339F53229578733928F1F +C2E6F135E638C83363097EB79ED9A58E94A8E5F626E27083D1363E8BC61B55A4 +F06CC55446D0FCCCE5AD007949AE3FCA9C6FCB3C0CA1B5DE1BA65544B55326F1CF726E1F1E5CA90CA2B986930C9B9D83 diff --git a/applications/main/subghz/scenes/subghz_scene_set_button.c b/applications/main/subghz/scenes/subghz_scene_set_button.c index e8914dbc5..d2ef60a91 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_button.c +++ b/applications/main/subghz/scenes/subghz_scene_set_button.c @@ -44,6 +44,10 @@ void subghz_scene_set_button_on_enter(void* context) { byte_ptr = &subghz->gen_info->beninca_arc.btn; byte_count = sizeof(subghz->gen_info->beninca_arc.btn); break; + case GenJarolift: + byte_ptr = &subghz->gen_info->jarolift.btn; + byte_count = sizeof(subghz->gen_info->jarolift.btn); + break; case GenNiceFlorS: byte_ptr = &subghz->gen_info->nice_flor_s.btn; byte_count = sizeof(subghz->gen_info->nice_flor_s.btn); @@ -92,6 +96,7 @@ bool subghz_scene_set_button_on_event(void* context, SceneManagerEvent event) { case GenSomfyTelis: case GenKingGatesStylo4k: case GenBenincaARC: + case GenJarolift: case GenNiceFlorS: case GenSecPlus2: scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetCounter); diff --git a/applications/main/subghz/scenes/subghz_scene_set_counter.c b/applications/main/subghz/scenes/subghz_scene_set_counter.c index a58749256..8510d4aca 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_counter.c +++ b/applications/main/subghz/scenes/subghz_scene_set_counter.c @@ -50,6 +50,10 @@ void subghz_scene_set_counter_on_enter(void* context) { byte_ptr = (uint8_t*)&subghz->gen_info->beninca_arc.cnt; byte_count = sizeof(subghz->gen_info->beninca_arc.cnt); break; + case GenJarolift: + byte_ptr = (uint8_t*)&subghz->gen_info->jarolift.cnt; + byte_count = sizeof(subghz->gen_info->jarolift.cnt); + break; case GenNiceFlorS: byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.cnt; byte_count = sizeof(subghz->gen_info->nice_flor_s.cnt); @@ -128,6 +132,9 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) { case GenBenincaARC: subghz->gen_info->beninca_arc.cnt = __bswap32(subghz->gen_info->beninca_arc.cnt); break; + case GenJarolift: + subghz->gen_info->jarolift.cnt = __bswap16(subghz->gen_info->jarolift.cnt); + break; case GenNiceFlorS: subghz->gen_info->nice_flor_s.cnt = __bswap16(subghz->gen_info->nice_flor_s.cnt); break; @@ -204,6 +211,15 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) { subghz->gen_info->beninca_arc.btn, subghz->gen_info->beninca_arc.cnt); break; + case GenJarolift: + generated_protocol = subghz_txrx_gen_jarolift_protocol( + subghz->txrx, + subghz->gen_info->mod, + subghz->gen_info->freq, + subghz->gen_info->jarolift.serial, + subghz->gen_info->jarolift.btn, + subghz->gen_info->jarolift.cnt); + break; case GenNiceFlorS: generated_protocol = subghz_txrx_gen_nice_flor_s_protocol( subghz->txrx, diff --git a/applications/main/subghz/scenes/subghz_scene_set_seed.c b/applications/main/subghz/scenes/subghz_scene_set_seed.c index 648cfa1d3..36307f11f 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -32,6 +32,7 @@ void subghz_scene_set_seed_on_enter(void* context) { case GenSomfyTelis: case GenKingGatesStylo4k: case GenBenincaARC: + case GenJarolift: case GenNiceFlorS: case GenSecPlus2: case GenPhoenixV2: @@ -93,6 +94,7 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) { case GenSomfyTelis: case GenKingGatesStylo4k: case GenBenincaARC: + case GenJarolift: case GenNiceFlorS: case GenSecPlus2: case GenPhoenixV2: diff --git a/applications/main/subghz/scenes/subghz_scene_set_serial.c b/applications/main/subghz/scenes/subghz_scene_set_serial.c index 9219842e5..f30b1a4a5 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_serial.c +++ b/applications/main/subghz/scenes/subghz_scene_set_serial.c @@ -50,6 +50,10 @@ void subghz_scene_set_serial_on_enter(void* context) { byte_ptr = (uint8_t*)&subghz->gen_info->beninca_arc.serial; byte_count = sizeof(subghz->gen_info->beninca_arc.serial); break; + case GenJarolift: + byte_ptr = (uint8_t*)&subghz->gen_info->jarolift.serial; + byte_count = sizeof(subghz->gen_info->jarolift.serial); + break; case GenNiceFlorS: byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.serial; byte_count = sizeof(subghz->gen_info->nice_flor_s.serial); @@ -122,6 +126,9 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) { subghz->gen_info->kinggates_stylo_4k.serial = __bswap32(subghz->gen_info->kinggates_stylo_4k.serial); break; + case GenJarolift: + subghz->gen_info->jarolift.serial = __bswap32(subghz->gen_info->jarolift.serial); + break; case GenBenincaARC: subghz->gen_info->beninca_arc.serial = __bswap32(subghz->gen_info->beninca_arc.serial); @@ -154,6 +161,7 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) { case GenSomfyTelis: case GenKingGatesStylo4k: case GenBenincaARC: + case GenJarolift: case GenNiceFlorS: case GenSecPlus2: scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetButton); diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 28cd4bcf7..2be40602b 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -22,6 +22,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz", [SetTypeKingGatesStylo4k] = "KingGates Stylo4k 433M.", [SetTypeBenincaARC] = "Beninca ARC 433MHz", + [SetTypeJarolift] = "Jarolift 433MHz", [SetTypeHCS101_433_92] = "KL: HCS101 433MHz", [SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz", [SetTypeDoorHan_433_92] = "KL: DoorHan 433MHz", @@ -207,6 +208,15 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) { gen_info.beninca_arc.btn, gen_info.beninca_arc.cnt); break; + case GenJarolift: + generated_protocol = subghz_txrx_gen_jarolift_protocol( + subghz->txrx, + gen_info.mod, + gen_info.freq, + gen_info.jarolift.serial, + gen_info.jarolift.btn, + gen_info.jarolift.cnt); + break; case GenNiceFlorS: generated_protocol = subghz_txrx_gen_nice_flor_s_protocol( subghz->txrx, @@ -288,6 +298,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { case GenSomfyTelis: // Serial (u32), Button (u8), Counter (u16) case GenKingGatesStylo4k: // Serial (u32), Button (u8), Counter (u16) case GenBenincaARC: // Serial (u32), Button (u8), Counter (u32) + case GenJarolift: // Serial (u32), Button (u4), Counter (u16) case GenNiceFlorS: // Serial (u32), Button (u8), Counter (u16) case GenSecPlus2: // Serial (u32), Button (u8), Counter (u32) case GenPhoenixV2: // Serial (u32), Counter (u16) diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 8dff70dd1..687a1e930 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -25,7 +25,6 @@ struct SubGhzProtocolDecoderAlutech_at_4n { SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; - uint64_t data; uint32_t crc; uint16_t header_count; @@ -578,13 +577,12 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; break; } - if((instance->header_count > 2) && + if((instance->header_count > 9) && (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short * 10) < subghz_protocol_alutech_at_4n_const.te_delta * 10)) { // Found header instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; instance->decoder.decode_data = 0; - instance->data = 0; instance->decoder.decode_count_bit = 0; } else { instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; @@ -617,8 +615,8 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; if(instance->decoder.decode_count_bit == subghz_protocol_alutech_at_4n_const.min_count_bit_for_found) { - if(instance->generic.data != instance->data) { - instance->generic.data = instance->data; + if(instance->generic.data != instance->generic.data_2) { + instance->generic.data = instance->generic.data_2; instance->generic.data_count_bit = instance->decoder.decode_count_bit; instance->crc = instance->decoder.decode_data; @@ -627,7 +625,6 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 instance->base.callback(&instance->base, instance->base.context); } instance->decoder.decode_data = 0; - instance->data = 0; instance->decoder.decode_count_bit = 0; instance->header_count = 0; } @@ -640,7 +637,7 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 subghz_protocol_alutech_at_4n_const.te_delta * 2)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); if(instance->decoder.decode_count_bit == 64) { - instance->data = instance->decoder.decode_data; + instance->generic.data_2 = instance->decoder.decode_data; instance->decoder.decode_data = 0; } instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; @@ -652,7 +649,7 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 subghz_protocol_alutech_at_4n_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); if(instance->decoder.decode_count_bit == 64) { - instance->data = instance->decoder.decode_data; + instance->generic.data_2 = instance->decoder.decode_data; instance->decoder.decode_data = 0; } instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; diff --git a/lib/subghz/protocols/jarolift.c b/lib/subghz/protocols/jarolift.c new file mode 100644 index 000000000..9881b9892 --- /dev/null +++ b/lib/subghz/protocols/jarolift.c @@ -0,0 +1,777 @@ +#include "jarolift.h" +#include "core/log.h" +#include "keeloq_common.h" + +#include "../subghz_keystore.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#include "../blocks/custom_btn_i.h" + +#define TAG "SubGhzProtocolJarolift" + +static const SubGhzBlockConst subghz_protocol_jarolift_const = { + .te_short = 400, + .te_long = 800, + .te_delta = 167, + .min_count_bit_for_found = 72, +}; + +struct SubGhzProtocolDecoderJarolift { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + SubGhzKeystore* keystore; +}; + +struct SubGhzProtocolEncoderJarolift { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + SubGhzKeystore* keystore; +}; + +typedef enum { + JaroliftDecoderStepReset = 0, + JaroliftDecoderStepCheckPreambula, + JaroliftDecoderStepSaveDuration, + JaroliftDecoderStepCheckDuration, +} JaroliftDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_jarolift_decoder = { + .alloc = subghz_protocol_decoder_jarolift_alloc, + .free = subghz_protocol_decoder_jarolift_free, + + .feed = subghz_protocol_decoder_jarolift_feed, + .reset = subghz_protocol_decoder_jarolift_reset, + + .get_hash_data = subghz_protocol_decoder_jarolift_get_hash_data, + .serialize = subghz_protocol_decoder_jarolift_serialize, + .deserialize = subghz_protocol_decoder_jarolift_deserialize, + .get_string = subghz_protocol_decoder_jarolift_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_jarolift_encoder = { + .alloc = subghz_protocol_encoder_jarolift_alloc, + .free = subghz_protocol_encoder_jarolift_free, + + .deserialize = subghz_protocol_encoder_jarolift_deserialize, + .stop = subghz_protocol_encoder_jarolift_stop, + .yield = subghz_protocol_encoder_jarolift_yield, +}; + +const SubGhzProtocol subghz_protocol_jarolift = { + .name = SUBGHZ_PROTOCOL_JAROLIFT_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_jarolift_decoder, + .encoder = &subghz_protocol_jarolift_encoder, +}; + +// +// Encoder +// + +// Pre define function +static void subghz_protocol_jarolift_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore); + +/** + * Defines the button value for the current btn_id + * Basic set | 0x1 | 0x2 | 0x4 | 0x8 | + * @return Button code + */ +static uint8_t subghz_protocol_jarolift_get_btn_code(void); + +void* subghz_protocol_encoder_jarolift_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderJarolift* instance = malloc(sizeof(SubGhzProtocolEncoderJarolift)); + + instance->base.protocol = &subghz_protocol_jarolift; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + + return instance; +} + +void subghz_protocol_encoder_jarolift_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderJarolift* instance = context; + free(instance->encoder.upload); + free(instance); +} + +void subghz_protocol_encoder_jarolift_stop(void* context) { + SubGhzProtocolEncoderJarolift* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_jarolift_yield(void* context) { + SubGhzProtocolEncoderJarolift* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderJarolift* instance + * @param btn Button number, 4 bit + */ +static bool + subghz_protocol_jarolift_gen_data(SubGhzProtocolEncoderJarolift* instance, uint8_t btn) { + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + btn = subghz_protocol_jarolift_get_btn_code(); + + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + // standart counter mode. PULL data from subghz_block_generic_global variables + if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { + // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } + } else { + if((instance->generic.cnt + 0x1) > 0xFFFF) { + instance->generic.cnt = 0; + } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { + instance->generic.cnt = 0xFFFE; + } else { + instance->generic.cnt++; + } + } + + //(instance->generic.seed >> 8) = 8 bit grouping channel 0-7 + uint32_t hop_decrypted = (uint64_t)((instance->generic.seed >> 8) & 0xFF) << 24 | + ((instance->generic.serial & 0xFF) << 16) | + (instance->generic.cnt & 0xFFFF); + + uint64_t hop_encrypted = 0; + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + if(manufacture_code->type == KEELOQ_LEARNING_NORMAL_JAROLIFT) { + // Normal Learning + uint64_t man = subghz_protocol_keeloq_common_normal_learning( + instance->generic.serial, manufacture_code->key); + hop_encrypted = subghz_protocol_keeloq_common_encrypt(hop_decrypted, man); + break; + } + } + + // If we got some issue, return false + if(hop_encrypted == 0) { + return false; + } + uint64_t fix = (uint64_t)btn << 60 | ((uint64_t)(instance->generic.serial & 0xFFFFFFF) << 32) | + hop_encrypted; + + instance->generic.data = subghz_protocol_blocks_reverse_key(fix, 64); + //(instance->generic.seed & 0xFF) = 8 bit for grouping 8-16 + instance->generic.data_2 = + subghz_protocol_blocks_reverse_key((instance->generic.seed & 0xFF), 8); + + return true; +} + +bool subghz_protocol_jarolift_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderJarolift* instance = context; + instance->generic.serial = (serial & 0xFFFFF00); + instance->generic.cnt = cnt; + instance->generic.btn = btn; + instance->generic.seed = 0x0100; + instance->generic.data_count_bit = 72; + + // Encode data + + //(instance->generic.seed >> 8) = 8 bit grouping channel 0-7 + uint32_t hop_decrypted = (uint64_t)((instance->generic.seed >> 8) & 0xFF) << 24 | + ((instance->generic.serial & 0xFF) << 16) | + (instance->generic.cnt & 0xFFFF); + + uint64_t hop_encrypted = 0; + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + if(manufacture_code->type == KEELOQ_LEARNING_NORMAL_JAROLIFT) { + // Normal Learning + uint64_t man = subghz_protocol_keeloq_common_normal_learning( + instance->generic.serial, manufacture_code->key); + hop_encrypted = subghz_protocol_keeloq_common_encrypt(hop_decrypted, man); + break; + } + } + + uint64_t fix = (uint64_t)instance->generic.btn << 60 | + ((uint64_t)(instance->generic.serial & 0xFFFFFFF) << 32) | hop_encrypted; + + instance->generic.data = subghz_protocol_blocks_reverse_key(fix, 64); + //(instance->generic.seed & 0xFF) = 8 bit for grouping 8-16 + instance->generic.data_2 = + subghz_protocol_blocks_reverse_key((instance->generic.seed & 0xFF), 8); + + // Encode complete, now serialize + SubGhzProtocolStatus res = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; + } + + if((res == SubGhzProtocolStatusOk) && + !flipper_format_insert_or_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data2"); + res = SubGhzProtocolStatusErrorParserOthers; + } + + return res == SubGhzProtocolStatusOk; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderJarolift instance + * @return true On success + */ +static bool subghz_protocol_encoder_jarolift_get_upload( + SubGhzProtocolEncoderJarolift* instance, + uint8_t btn) { + furi_assert(instance); + + // Gen new key + if(!subghz_protocol_jarolift_gen_data(instance, btn)) { + return false; + } + + size_t index = 0; + + // Start 14k us delay + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_long * 18); + + // First header bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)1500); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_short); + + // Finish header + for(uint8_t i = 8; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_short); + } + + // After header + instance->encoder.upload[index - 1].duration = (uint32_t)3800; // Adjust last low duration + + // Send key fix + for(uint8_t i = 64; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_short); + } + } + + // Send grouping byte + for(uint8_t i = 8; i > 0; i--) { + if(bit_read(instance->generic.data_2, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_short); + } + } + + // Set upload size after generating upload, fix it later + instance->encoder.size_upload = index; + + return true; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_jarolift_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderJarolift* instance = context; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + do { + if(SubGhzProtocolStatusOk != + subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; + } + + subghz_protocol_jarolift_remote_controller(&instance->generic, instance->keystore); + + subghz_protocol_encoder_jarolift_get_upload(instance, instance->generic.btn); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to update Key"); + break; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to update Data"); + break; + } + + instance->encoder.is_running = true; + + res = SubGhzProtocolStatusOk; + } while(false); + + return res; +} + +// +// Decoder +// +void* subghz_protocol_decoder_jarolift_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderJarolift* instance = malloc(sizeof(SubGhzProtocolDecoderJarolift)); + instance->base.protocol = &subghz_protocol_jarolift; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + return instance; +} + +void subghz_protocol_decoder_jarolift_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + free(instance); +} + +void subghz_protocol_decoder_jarolift_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + instance->decoder.parser_step = JaroliftDecoderStepReset; +} + +void subghz_protocol_decoder_jarolift_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + + switch(instance->decoder.parser_step) { + case JaroliftDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta) { + instance->decoder.parser_step = JaroliftDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case JaroliftDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta)) { + instance->decoder.parser_step = JaroliftDecoderStepReset; + break; + } + if((!level) && (instance->header_count == 8) && + (DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_long * 5) < + subghz_protocol_jarolift_const.te_delta * 6)) { + // Found gap after header - 4000us +- 996us + instance->decoder.parser_step = JaroliftDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + break; + } else { + instance->decoder.parser_step = JaroliftDecoderStepReset; + instance->header_count = 0; + } + break; + case JaroliftDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = JaroliftDecoderStepCheckDuration; + } else { + instance->header_count = 0; + instance->decoder.parser_step = JaroliftDecoderStepReset; + } + break; + case JaroliftDecoderStepCheckDuration: + if(!level) { + if(instance->decoder.decode_count_bit == 64) { + instance->generic.data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_long) < + subghz_protocol_jarolift_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = JaroliftDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_jarolift_const.te_long) < + subghz_protocol_jarolift_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = JaroliftDecoderStepSaveDuration; + } else { + if(duration >= ((uint32_t)subghz_protocol_jarolift_const.te_long * 3)) { + // Add endbit + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_jarolift_const.te_long) < + subghz_protocol_jarolift_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if((DURATION_DIFF( + instance->decoder.te_last, + subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if(instance->decoder.decode_count_bit == + subghz_protocol_jarolift_const.min_count_bit_for_found) { + instance->generic.data_2 = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + + instance->decoder.parser_step = JaroliftDecoderStepReset; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + break; + } + instance->decoder.parser_step = JaroliftDecoderStepReset; + instance->header_count = 0; + } + } else { + instance->decoder.parser_step = JaroliftDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +/** + * Get button name. + * @param btn Button number, 4 bit + */ +static const char* subghz_protocol_jarolift_get_button_name(uint8_t btn) { + const char* btn_name; + switch(btn) { + case 0x1: + btn_name = "Learn"; + break; + case 0x2: + btn_name = "Down"; + break; + case 0x4: + btn_name = "Stop"; + break; + case 0x8: + btn_name = "Up"; + break; + default: + btn_name = "Unkn"; + break; + } + return btn_name; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param data Input encrypted data + * @param keystore Pointer to a SubGhzKeystore* instance + */ +static void subghz_protocol_jarolift_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore) { + // Jarolift Decoder + // 01.2026 - @xMasterX (MMX) & d82k & Steffen (@bastelbudenbuben de) + + // Key samples (reversed) + // 0x821EB600EAC2EAD4 - Btn Up - cnt 0059 group 0100 + // 0x821EB6007D9BD66A - Btn Up - cnt 005A group 0100 + // 0x821EB600A029FA0E - Btn Up - cnt 005B group 0100 + + uint32_t group = subghz_protocol_blocks_reverse_key(instance->data_2, 8); + uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, 64); + bool ret = false; + uint32_t decrypt = 0; + instance->serial = (key >> 32) & 0xFFFFFFF; + uint32_t hop = key & 0xFFFFFFFF; + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + if(manufacture_code->type == KEELOQ_LEARNING_NORMAL_JAROLIFT) { + uint64_t man = subghz_protocol_keeloq_common_normal_learning( + instance->serial, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(((decrypt >> 16) & 0xFF) == (instance->serial & 0xFF)) { + ret = true; + } + break; + } + } + if(ret) { + instance->btn = (key >> 60) & 0xF; + instance->seed = ((decrypt >> 24) << 8) | (group >> 8); + instance->cnt = decrypt & 0xFFFF; + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + subghz_custom_btn_set_max(3); + } else { + instance->btn = 0; + instance->serial = 0; + instance->cnt = 0; + instance->seed = 0; + } +} + +uint8_t subghz_protocol_decoder_jarolift_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + uint8_t hash = 0; + uint8_t* p = (uint8_t*)&instance->generic.data; + for(size_t i = 0; i < 16; i++) { + hash ^= p[i]; + } + return hash; +} + +SubGhzProtocolStatus subghz_protocol_decoder_jarolift_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + } + + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_insert_or_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data"); + ret = SubGhzProtocolStatusErrorParserOthers; + } + return ret; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_jarolift_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_jarolift_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; + } + } while(false); + return ret; +} + +static uint8_t subghz_protocol_jarolift_get_btn_code(void) { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + + default: + break; + } + } + + return btn; +} + +void subghz_protocol_decoder_jarolift_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + subghz_protocol_jarolift_remote_controller(&instance->generic, instance->keystore); + + // push protocol data to global variable + subghz_block_generic_global.cnt_is_available = true; + subghz_block_generic_global.cnt_length_bit = 16; + subghz_block_generic_global.current_cnt = instance->generic.cnt; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%0llX\r\n" + "Sn:%07lX Btn:%01X - %s\r\n" + "Cnt:%04lX Group:%04lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.data, + instance->generic.serial, + instance->generic.btn, + subghz_protocol_jarolift_get_button_name(instance->generic.btn), + instance->generic.cnt, + instance->generic.seed); +} diff --git a/lib/subghz/protocols/jarolift.h b/lib/subghz/protocols/jarolift.h new file mode 100644 index 000000000..0f666579e --- /dev/null +++ b/lib/subghz/protocols/jarolift.h @@ -0,0 +1,108 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_JAROLIFT_NAME "Jarolift" + +typedef struct SubGhzProtocolDecoderJarolift SubGhzProtocolDecoderJarolift; +typedef struct SubGhzProtocolEncoderJarolift SubGhzProtocolEncoderJarolift; + +extern const SubGhzProtocolDecoder subghz_protocol_jarolift_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_jarolift_encoder; +extern const SubGhzProtocol subghz_protocol_jarolift; + +/** + * Allocate SubGhzProtocolEncoderJarolift. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderJarolift* pointer to a SubGhzProtocolEncoderJarolift instance + */ +void* subghz_protocol_encoder_jarolift_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderJarolift. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + */ +void subghz_protocol_encoder_jarolift_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +SubGhzProtocolStatus + subghz_protocol_encoder_jarolift_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + */ +void subghz_protocol_encoder_jarolift_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_jarolift_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderJarolift. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderJarolift* pointer to a SubGhzProtocolDecoderJarolift instance + */ +void* subghz_protocol_decoder_jarolift_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderJarolift. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + */ +void subghz_protocol_decoder_jarolift_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderJarolift. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + */ +void subghz_protocol_decoder_jarolift_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_jarolift_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_jarolift_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderJarolift. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_jarolift_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderJarolift. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_jarolift_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @param output Resulting text + */ +void subghz_protocol_decoder_jarolift_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/keeloq_common.h b/lib/subghz/protocols/keeloq_common.h index 90c5ef6d7..1250cd2fc 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -26,6 +26,8 @@ #define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2 7u #define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3 8u // #define BENINCA_ARC_KEY_TYPE 9u -- RESERVED +#define KEELOQ_LEARNING_SIMPLE_KINGGATES 10u +#define KEELOQ_LEARNING_NORMAL_JAROLIFT 11u /** * Simple Learning Encrypt diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index e23042b55..9d7313559 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -187,7 +187,7 @@ static bool subghz_protocol_kinggates_stylo_4k_gen_data( uint64_t encrypt = 0; for M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - if(strcmp(furi_string_get_cstr(manufacture_code->name), "Kingates_Stylo4k") == 0) { + if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE_KINGGATES) { // Simple Learning encrypt = subghz_protocol_keeloq_common_encrypt(hop, manufacture_code->key); encrypt = subghz_protocol_blocks_reverse_key(encrypt, 32); @@ -220,7 +220,7 @@ bool subghz_protocol_kinggates_stylo_4k_create_data( uint64_t encrypt = 0; for M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - if(strcmp(furi_string_get_cstr(manufacture_code->name), "Kingates_Stylo4k") == 0) { + if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE_KINGGATES) { // Simple Learning encrypt = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); encrypt = subghz_protocol_blocks_reverse_key(encrypt, 32); @@ -456,7 +456,6 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, subghz_protocol_kinggates_stylo_4k_const.te_delta * 2) { instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; instance->decoder.decode_data = 0; - instance->generic.data_2 = 0; instance->decoder.decode_count_bit = 0; instance->header_count = 0; } @@ -476,7 +475,6 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; instance->decoder.decode_data = 0; - instance->generic.data_2 = 0; instance->decoder.decode_count_bit = 0; instance->header_count = 0; break; @@ -561,7 +559,7 @@ static void subghz_protocol_kinggates_stylo_4k_remote_controller( for M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { - if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE) { + if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE_KINGGATES) { decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); if(((decrypt >> 28) == instance->btn) && (((decrypt >> 24) & 0x0F) == 0x0C) && (((decrypt >> 16) & 0xFF) == (instance->serial & 0xFF))) { diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 2c4054dd2..14d54b8d3 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -27,7 +27,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_hay21, &subghz_protocol_revers_rb2, &subghz_protocol_feron, &subghz_protocol_roger, &subghz_protocol_elplast, &subghz_protocol_treadmill37, - &subghz_protocol_beninca_arc, + &subghz_protocol_beninca_arc, &subghz_protocol_jarolift, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index f61e71420..2c36260ed 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -55,3 +55,4 @@ #include "elplast.h" #include "treadmill37.h" #include "beninca_arc.h" +#include "jarolift.h" diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index 30b8212ce..7539352dc 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -231,6 +231,24 @@ bool subghz_protocol_beninca_arc_create_data( uint32_t cnt, SubGhzRadioPreset* preset); +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_jarolift_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; void subghz_protocol_decoder_bin_raw_data_input_rssi( diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7e6190628..1ad9bc413 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3725,6 +3725,7 @@ Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* Function,+,subghz_protocol_faac_slh_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, uint32_t, const char*, SubGhzRadioPreset*" +Function,+,subghz_protocol_jarolift_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_kinggates_stylo_4k_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*"