From cf6d6bb9afbf4b714b9e59ac6eaf101505c3c33f Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 1 Aug 2024 09:10:02 -0400 Subject: [PATCH 001/236] Initial structure for nonce collection --- .../protocols/mf_classic/mf_classic_poller.c | 338 +++++++++++++++++- .../mf_classic/mf_classic_poller_i.h | 45 +++ 2 files changed, 366 insertions(+), 17 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 8c50230ca0..a865de8149 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -680,25 +680,32 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { - dict_attack_ctx->current_key_type = MfClassicKeyTypeB; - instance->state = MfClassicPollerStateKeyReuseAuthKeyB; - } else { - dict_attack_ctx->reuse_key_sector++; - if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { - instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; - command = instance->callback(instance->general_event, instance->context); - instance->state = MfClassicPollerStateRequestKey; + do { + if(!dict_attack_ctx->prng_type) { + instance->state = MfClassicPollerStateNestedAnalyzePRNG; + break; + } + + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + dict_attack_ctx->current_key_type = MfClassicKeyTypeB; + instance->state = MfClassicPollerStateKeyReuseAuthKeyB; } else { - instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; - instance->mfc_event_data.key_attack_data.current_sector = - dict_attack_ctx->reuse_key_sector; - command = instance->callback(instance->general_event, instance->context); + dict_attack_ctx->reuse_key_sector++; + if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; + command = instance->callback(instance->general_event, instance->context); + instance->state = MfClassicPollerStateRequestKey; + } else { + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = + dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); - dict_attack_ctx->current_key_type = MfClassicKeyTypeA; - instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + dict_attack_ctx->current_key_type = MfClassicKeyTypeA; + instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + } } - } + } while(false); return command; } @@ -829,6 +836,294 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst return command; } +void nonce_distance(uint32_t* msb, uint32_t* lsb) { + uint16_t x = 1, pos; + uint8_t calc_ok = 0; + + for(uint16_t i = 1; i; ++i) { + pos = (x & 0xff) << 8 | x >> 8; + + if((pos == *msb) & !(calc_ok >> 0 & 0x01)) { + *msb = i; + calc_ok |= 0x01; + } + + if((pos == *lsb) & !(calc_ok >> 1 & 0x01)) { + *lsb = i; + calc_ok |= 0x02; + } + + if(calc_ok == 0x03) { + return; + } + + x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; + } +} + +// TODO: Faster? https://github.com/RfidResearchGroup/proxmark3/commit/bd3e8db852186d4ab9d5dda890d1cd52389b1254 +bool validate_prng_nonce(uint32_t nonce) { + uint32_t msb = nonce >> 16; + uint32_t lsb = nonce & 0xffff; + nonce_distance(&msb, &lsb); + return ((65535 - msb + lsb) % 65535) == 16; +} + +// Helper function to add a nonce to the array +static bool add_nested_nonce(MfClassicNestedNonceArray* array, uint32_t cuid, uint8_t key_idx, uint32_t nt, uint32_t nt_enc, uint8_t par, uint16_t dist) { + MfClassicNestedNonce* new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); + if (new_nonces == NULL) return false; + + array->nonces = new_nonces; + array->nonces[array->count].cuid = cuid; + array->nonces[array->count].key_idx = key_idx; + array->nonces[array->count].nt = nt; + array->nonces[array->count].nt_enc = nt_enc; + array->nonces[array->count].par = par; + array->nonces[array->count].dist = dist; + array->count++; + return true; +} + +NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + // Analyze PRNG by collecting nt + uint8_t nonce_limit = 10; + + if (dict_attack_ctx->nt_count > 0) { + if(!validate_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; + } + + if(dict_attack_ctx->nt_count <= nonce_limit) { + instance->state = MfClassicPollerStateNestedCollectNt; + return command; + } + + if(dict_attack_ctx->hard_nt_count > 3) { + dict_attack_ctx->prng_type = MfClassicPrngTypeHard; + // FIXME: E -> D + FURI_LOG_E(TAG, "Detected Hard PRNG"); + } else { + dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + // FIXME: E -> D + FURI_LOG_E(TAG, "Detected Weak PRNG"); + } + + instance->state = MfClassicPollerStateNestedController; + return command; +} + +NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) { + // Stub + NfcCommand command = NfcCommandContinue; + return command; +} + +NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance) { + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + MfClassicNt nt = {}; + MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt); + + if(error != MfClassicErrorNone) { + instance->state = MfClassicPollerStateKeyReuseStart; + dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; + // FIXME: E -> D + FURI_LOG_E(TAG, "Failed to collect nt"); + } else { + // FIXME: E -> D + FURI_LOG_E(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]); + uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); + dict_attack_ctx->nt_prev = nt_data; + dict_attack_ctx->nt_count++; + instance->state = MfClassicPollerStateNestedAnalyzePRNG; + } + + return command; +} + +NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { + // TODO: Collect parity + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + MfClassicNestedNonceArray result = {NULL, 0}; + + do { + if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + FURI_LOG_E(TAG, "Hard PRNG, skipping calibration"); + // TODO: Collect hardnested nonces (cuid, sec, par, nt_enc, A/B) + // https://github.com/AloneLiberty/FlipperNested/blob/eba6163d7ef22adef5f9fe4d77a78ccfcc27a952/lib/nested/nested.c#L564-L645 + break; + } + + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error; + + uint32_t davg = 0, dmin = 0, dmax = 0; + uint32_t collected = 0; + const uint32_t rounds = 10; + + uint32_t nt_enc_arr[rounds]; + uint32_t nt_prev = 0; + uint32_t nt_enc_prev = 0; + uint32_t same_nt_enc_cnt = 0; + uint32_t dist = 0; + bool static_encrypted = false; + + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); + + if (error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + break; + } + + FURI_LOG_E(TAG, "Full authentication successful"); + + nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + + // Step 2: Perform nested authentication multiple times + for (uint32_t round_no = 0; round_no < rounds; round_no++) { + error = mf_classic_poller_auth_nested( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); + + if (error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication %lu", round_no); + continue; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + if (nt_enc == nt_enc_prev) { + same_nt_enc_cnt++; + if (same_nt_enc_cnt > 3) { + static_encrypted = true; + break; + } + } else { + same_nt_enc_cnt = 0; + nt_enc_prev = nt_enc; + } + nt_enc_arr[round_no] = nt_enc; + } + + if (static_encrypted) { + FURI_LOG_E(TAG, "Static encrypted nonce detected"); + if (dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + // TODO: Backdoor static nested attack + break; + } else { + // TODO: If not present, just log nonces with parity bits + bool success = add_nested_nonce(&result, cuid, dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, 0, 0); + if (!success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + } + break; + } + } + + // Find the distance between each nonce + // TODO: Distance calculation + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); + Crypto1 crypto; + crypto1_init(&crypto, known_key); + for (uint32_t rtr = 0; rtr < rounds; rtr++) { + bool found = false; + for (int i = 0; i < 65535; i++) { + Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even}; + uint32_t nth_successor = prng_successor(nt_prev, i); + if ((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) == nt_enc_arr[rtr]) { + FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); + FURI_LOG_E(TAG, "dist from nt prev: %i", i); + nt_prev = nth_successor; + found = true; + /* + dist_a = i; + found_dist++; + printf("ks: %08x\n", nth_successor ^ nt_enc); + */ + break; + } + } + if (!found) { + FURI_LOG_E(TAG, "Failed to find distance for nt_enc %08lx", nt_enc_arr[rtr]); + FURI_LOG_E(TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev); + } + } + + if (collected > 0) { + davg /= collected; + FURI_LOG_E( + TAG, + "Calibration completed: min=%lu max=%lu avg=%lu collected=%lu", + dmin, + dmax, + davg, + collected); + } else { + FURI_LOG_E(TAG, "Failed to collect any valid nonce distances"); + } + } while(false); + + instance->state = MfClassicPollerStateNestedController; + + mf_classic_poller_halt(instance); + return command; +} + +NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { + // Log collected nonces to /ext/nfc/.nested.log + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + size_t nonce_idx = dict_attack_ctx->nested_nonce.count; + while (nonce_idx > 0) { + // TODO: Make sure we're not off by one here + FURI_LOG_E(TAG, "nt: %08lx", dict_attack_ctx->nested_nonce.nonces[nonce_idx].nt); + nonce_idx--; + } + free(dict_attack_ctx->nested_nonce.nonces); + return command; +} + +NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { + // Iterate through keys + // To run an accelerated dictionary attack, first we need static nested nonces. + // If the PRNG is hard, we can't run an accelerated dictionary attack. + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + if (dict_attack_ctx->nested_nonce.count > 0) { + instance->state = MfClassicPollerStateNestedLog; + return command; + } + if (dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { + instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; + return command; + } + // Target all sectors, key A and B + for (uint8_t key_idx = 0; key_idx <= (instance->sectors_total * 2); key_idx++) { + dict_attack_ctx->nested_target_key = key_idx; + instance->state = MfClassicPollerStateNestedCollectNtEnc; + return command; + } + // TODO: If we've recovered all keys, read blocks and go to complete + instance->state = MfClassicPollerStateKeyReuseStart; + return command; +} + NfcCommand mf_classic_poller_handler_success(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; instance->mfc_event.type = MfClassicPollerEventTypeSuccess; @@ -868,6 +1163,15 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a, [MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b, [MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector, + [MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng, + [MfClassicPollerStateNestedAnalyzeBackdoor] = mf_classic_poller_handler_nested_analyze_backdoor, + [MfClassicPollerStateNestedCollectNt] = + mf_classic_poller_handler_nested_collect_nt, + [MfClassicPollerStateNestedController] = + mf_classic_poller_handler_nested_controller, + [MfClassicPollerStateNestedCollectNtEnc] = + mf_classic_poller_handler_nested_collect_nt_enc, + [MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log, [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, }; @@ -953,4 +1257,4 @@ const NfcPollerBase mf_classic_poller = { .run = (NfcPollerRun)mf_classic_poller_run, .detect = (NfcPollerDetect)mf_classic_poller_detect, .get_data = (NfcPollerGetData)mf_classic_poller_get_data, -}; +}; \ No newline at end of file diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 14a7c61fd4..f72145e8f8 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -21,6 +21,33 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; +typedef enum { + MfClassicPrngTypeUnknown, // Tag not yet tested + MfClassicPrngTypeNoTag, // No tag detected during test + MfClassicPrngTypeWeak, // Weak PRNG, standard Nested + MfClassicPrngTypeHard, // Hard PRNG, Hardnested +} MfClassicPrngType; + +typedef enum { + MfClassicBackdoorUnknown, // Tag not yet tested + MfClassicBackdoorNone, // No observed backdoor + MfClassicBackdoorFM11RF08S, // Tag responds to Fudan FM11RF08S backdoor (static encrypted nonce tags) +} MfClassicBackdoor; + +typedef struct { + uint32_t cuid; // Card UID + uint8_t key_idx; // Key index + uint32_t nt; // Nonce + uint32_t nt_enc; // Encrypted nonce + uint8_t par; // Parity + uint16_t dist; // Distance +} MfClassicNestedNonce; + +typedef struct { + MfClassicNestedNonce* nonces; + size_t count; +} MfClassicNestedNonceArray; + typedef enum { MfClassicPollerStateDetectType, MfClassicPollerStateStart, @@ -49,6 +76,14 @@ typedef enum { MfClassicPollerStateSuccess, MfClassicPollerStateFail, + // Enhanced dictionary attack states + MfClassicPollerStateNestedAnalyzePRNG, + MfClassicPollerStateNestedAnalyzeBackdoor, + MfClassicPollerStateNestedCollectNt, + MfClassicPollerStateNestedController, + MfClassicPollerStateNestedCollectNtEnc, + MfClassicPollerStateNestedLog, + MfClassicPollerStateNum, } MfClassicPollerState; @@ -63,6 +98,7 @@ typedef struct { MfClassicBlock tag_block; } MfClassicPollerWriteContext; +// TODO: Investigate reducing the number of members of this struct typedef struct { uint8_t current_sector; MfClassicKey current_key; @@ -70,6 +106,15 @@ typedef struct { bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; + // Enhanced dictionary attack + MfClassicPrngType prng_type; + MfClassicBackdoor backdoor; + uint32_t nt_prev; + uint32_t nt_next; + uint8_t nt_count; + uint8_t hard_nt_count; + uint8_t nested_target_key; + MfClassicNestedNonceArray nested_nonce; } MfClassicPollerDictAttackContext; typedef struct { From b15460315ea5664f839dac59ed2eba106ab6ceea Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 1 Aug 2024 22:16:10 -0400 Subject: [PATCH 002/236] Nonce logging --- .../protocols/mf_classic/mf_classic_poller.c | 116 +++++++++++++++--- .../mf_classic/mf_classic_poller_i.h | 3 + 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index a865de8149..cb1d645e72 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -3,6 +3,8 @@ #include #include +#include +#include #define TAG "MfClassicPoller" @@ -869,6 +871,15 @@ bool validate_prng_nonce(uint32_t nonce) { return ((65535 - msb + lsb) % 65535) == 16; } +// Return 1 if the nonce is invalid else return 0 +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { + return ( + (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ + (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ + (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) + ) ? 1 : 0; +} + // Helper function to add a nonce to the array static bool add_nested_nonce(MfClassicNestedNonceArray* array, uint32_t cuid, uint8_t key_idx, uint32_t nt, uint32_t nt_enc, uint8_t par, uint16_t dist) { MfClassicNestedNonce* new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); @@ -916,8 +927,10 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan } NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) { - // Stub NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + // TODO: Check for Fudan backdoor + dict_attack_ctx->backdoor = MfClassicBackdoorNone; return command; } @@ -947,6 +960,24 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Collect parity + // TODO: FURI_CRITICAL_ENTER + // xTickCount + // Modify targets/f7/furi_hal/furi_hal_rtc.c to expose sub-second time (https://stackoverflow.com/questions/55534873/how-to-setup-the-stm32f4-real-time-clockrtc-to-get-valid-values-in-the-sub-sec) + // furi_hal_rtc_get_timestamp isn't precise enough + // TODO: Measure ticks with https://github.com/flipperdevices/flipperzero-firmware/blob/bec6bd381f222cf14658fd862a2c7f6e620bbf00/targets/f7/furi_hal/furi_hal_idle_timer.h#L51 + /* + uint32_t ncount = 0; + uint32_t nttest = prng_successor(nt1, dmin - 1); + + for(j = dmin; j < dmax + 1; j++) { + nttest = prng_successor(nttest, 1); + ks1 = nt2 ^ nttest; + + if(valid_nonce(nttest, nt2, ks1, par_array)) { + if(ncount > 0) { // we are only interested in disambiguous nonces, try again + FURI_LOG_D(TAG, "Nonce#%lu: dismissed (ambiguous), ntdist=%lu", i + 1, j); + (..) + */ NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicNestedNonceArray result = {NULL, 0}; @@ -975,6 +1006,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst uint32_t same_nt_enc_cnt = 0; uint32_t dist = 0; bool static_encrypted = false; + // Store last timestamp + // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -995,14 +1028,14 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Step 2: Perform nested authentication multiple times for (uint32_t round_no = 0; round_no < rounds; round_no++) { - error = mf_classic_poller_auth_nested( - instance, - block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, - &auth_ctx); + error = mf_classic_poller_auth_nested( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); - if (error != MfClassicErrorNone) { + if (error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform nested authentication %lu", round_no); continue; } @@ -1012,8 +1045,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst same_nt_enc_cnt++; if (same_nt_enc_cnt > 3) { static_encrypted = true; - break; - } + break; + } } else { same_nt_enc_cnt = 0; nt_enc_prev = nt_enc; @@ -1086,16 +1119,64 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { - // Log collected nonces to /ext/nfc/.nested.log + furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); + furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); + NfcCommand command = NfcCommandContinue; + bool params_saved = false; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - size_t nonce_idx = dict_attack_ctx->nested_nonce.count; - while (nonce_idx > 0) { - // TODO: Make sure we're not off by one here - FURI_LOG_E(TAG, "nt: %08lx", dict_attack_ctx->nested_nonce.nonces[nonce_idx].nt); - nonce_idx--; - } + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + + do { + if ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && (dict_attack_ctx->nested_nonce.count != 2)) { + FURI_LOG_E(TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %lu", dict_attack_ctx->nested_nonce.count); + break; + } + + if(!buffered_file_stream_open(stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) break; + + bool params_write_success = true; + for(size_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { + MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; + furi_string_printf(temp_str, "Sec %d key %c cuid %08lx", (nonce->key_idx / 2), (nonce->key_idx % 2 == 0) ? 'A' : 'B', nonce->cuid); + for(uint8_t nt_idx = 0; nt_idx < ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? 2 : 1); nt_idx++) { + furi_string_cat_printf( + temp_str, + " nt%u %08lx ks%u %08lx par%u ", + nt_idx, + nonce->nt, + nt_idx, + nonce->nt_enc ^ nonce->nt, + nt_idx); + for(uint8_t pb = 0; pb < 4; pb++) { + furi_string_cat_printf(temp_str, "%u", (nonce->par >> (3 - pb)) & 1); + } + } + if (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + furi_string_cat_printf(temp_str, " dist %u\n", nonce->dist); + } else { + furi_string_cat_printf(temp_str, "\n"); + } + if(!stream_write_string(stream, temp_str)) { + params_write_success = false; + break; + } + } + if(!params_write_success) break; + + params_saved = true; + } while(false); + + furi_assert(params_saved); free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.count = 0; + furi_string_free(temp_str); + buffered_file_stream_close(stream); + stream_free(stream); + furi_record_close(RECORD_STORAGE); + instance->state = MfClassicPollerStateNestedController; return command; } @@ -1103,6 +1184,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Iterate through keys // To run an accelerated dictionary attack, first we need static nested nonces. // If the PRNG is hard, we can't run an accelerated dictionary attack. + // TODO: Look ahead dictionary attack NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; if (dict_attack_ctx->nested_nonce.count > 0) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index f72145e8f8..ede052e20a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -10,6 +10,9 @@ extern "C" { #endif #define MF_CLASSIC_FWT_FC (60000) +#define NFC_FOLDER ANY_PATH("nfc") +#define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" +#define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) typedef enum { MfClassicAuthStateIdle, From ef16770f5e0a3e757587e5248fef58b2cea8ca6b Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 2 Aug 2024 07:04:43 -0400 Subject: [PATCH 003/236] Dictionary attack structure --- .../protocols/mf_classic/mf_classic_poller.c | 25 ++++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index cb1d645e72..3a2562893e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -981,6 +981,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicNestedNonceArray result = {NULL, 0}; + // TODO: Target sector, key A/B do { if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { @@ -1061,7 +1062,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst break; } else { // TODO: If not present, just log nonces with parity bits - bool success = add_nested_nonce(&result, cuid, dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, 0, 0); + bool success = add_nested_nonce(&result, cuid, dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, 0, 65535); if (!success) { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } @@ -1118,6 +1119,14 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst return command; } +NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + // TODO: Nested dictionary attack with ks1 + instance->state = MfClassicPollerStateNestedLog; + return command; +} + NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); @@ -1182,14 +1191,16 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys - // To run an accelerated dictionary attack, first we need static nested nonces. - // If the PRNG is hard, we can't run an accelerated dictionary attack. - // TODO: Look ahead dictionary attack NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; if (dict_attack_ctx->nested_nonce.count > 0) { - instance->state = MfClassicPollerStateNestedLog; - return command; + if (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } else if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + instance->state = MfClassicPollerStateNestedLog; + return command; + } } if (dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; @@ -1253,6 +1264,8 @@ static const MfClassicPollerReadHandler mf_classic_poller_handler_nested_controller, [MfClassicPollerStateNestedCollectNtEnc] = mf_classic_poller_handler_nested_collect_nt_enc, + [MfClassicPollerStateNestedDictAttack] = + mf_classic_poller_handler_nested_dict_attack, [MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log, [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index ede052e20a..1565c80ad3 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -85,6 +85,7 @@ typedef enum { MfClassicPollerStateNestedCollectNt, MfClassicPollerStateNestedController, MfClassicPollerStateNestedCollectNtEnc, + MfClassicPollerStateNestedDictAttack, MfClassicPollerStateNestedLog, MfClassicPollerStateNum, From 213ec1dfb0564897e248d00763205f86b8d8ae2f Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 2 Aug 2024 08:15:53 -0400 Subject: [PATCH 004/236] Fix compilation --- .../protocols/mf_classic/mf_classic_poller.c | 44 +++++++++---------- .../mf_classic/mf_classic_poller_i.h | 2 + 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3a2562893e..cb55dfcbc3 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -872,6 +872,7 @@ bool validate_prng_nonce(uint32_t nonce) { } // Return 1 if the nonce is invalid else return 0 +/* uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { return ( (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ @@ -879,6 +880,7 @@ uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) ) ? 1 : 0; } +*/ // Helper function to add a nonce to the array static bool add_nested_nonce(MfClassicNestedNonceArray* array, uint32_t cuid, uint8_t key_idx, uint32_t nt, uint32_t nt_enc, uint8_t par, uint16_t dist) { @@ -997,19 +999,14 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - uint32_t davg = 0, dmin = 0, dmax = 0; - uint32_t collected = 0; - const uint32_t rounds = 10; - + const uint32_t rounds = 11; uint32_t nt_enc_arr[rounds]; uint32_t nt_prev = 0; uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; - uint32_t dist = 0; bool static_encrypted = false; // Store last timestamp - // Step 1: Perform full authentication once error = mf_classic_poller_auth( instance, @@ -1075,7 +1072,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); Crypto1 crypto; crypto1_init(&crypto, known_key); - for (uint32_t rtr = 0; rtr < rounds; rtr++) { + // Skip the first round (typically an outlier) + for (uint32_t rtr = 1; rtr < rounds; rtr++) { bool found = false; for (int i = 0; i < 65535; i++) { Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even}; @@ -1083,13 +1081,14 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst if ((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) == nt_enc_arr[rtr]) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); + if (i > dict_attack_ctx->d_max) { + dict_attack_ctx->d_max = i; + } + if (i < dict_attack_ctx->d_min) { + dict_attack_ctx->d_min = i; + } nt_prev = nth_successor; found = true; - /* - dist_a = i; - found_dist++; - printf("ks: %08x\n", nth_successor ^ nt_enc); - */ break; } } @@ -1098,19 +1097,16 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst FURI_LOG_E(TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev); } } + bool is_static = (dict_attack_ctx->d_min == dict_attack_ctx->d_max); - if (collected > 0) { - davg /= collected; FURI_LOG_E( TAG, - "Calibration completed: min=%lu max=%lu avg=%lu collected=%lu", - dmin, - dmax, - davg, - collected); - } else { - FURI_LOG_E(TAG, "Failed to collect any valid nonce distances"); - } + "Calibration completed: min=%u max=%u static: %s", + dict_attack_ctx->d_min, + dict_attack_ctx->d_max, + is_static ? "true" : "false"); + + //printf("ks: %08x\n", nth_successor ^ nt_enc); } while(false); instance->state = MfClassicPollerStateNestedController; @@ -1121,7 +1117,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; - MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + //MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; // TODO: Nested dictionary attack with ks1 instance->state = MfClassicPollerStateNestedLog; return command; @@ -1140,7 +1136,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { do { if ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && (dict_attack_ctx->nested_nonce.count != 2)) { - FURI_LOG_E(TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %lu", dict_attack_ctx->nested_nonce.count); + FURI_LOG_E(TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %u", dict_attack_ctx->nested_nonce.count); break; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 1565c80ad3..2a53411974 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -119,6 +119,8 @@ typedef struct { uint8_t hard_nt_count; uint8_t nested_target_key; MfClassicNestedNonceArray nested_nonce; + uint16_t d_min; + uint16_t d_max; } MfClassicPollerDictAttackContext; typedef struct { From 9ba78bf085b5e0ad525467b642d44cfff8a06f20 Mon Sep 17 00:00:00 2001 From: noproto Date: Sat, 3 Aug 2024 21:35:52 -0400 Subject: [PATCH 005/236] Identified method to reduce candidate states --- .../protocols/mf_classic/mf_classic_poller.c | 189 ++++++++++-------- 1 file changed, 101 insertions(+), 88 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index cb55dfcbc3..c0daffe758 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -838,37 +838,15 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst return command; } -void nonce_distance(uint32_t* msb, uint32_t* lsb) { - uint16_t x = 1, pos; - uint8_t calc_ok = 0; - - for(uint16_t i = 1; i; ++i) { - pos = (x & 0xff) << 8 | x >> 8; - - if((pos == *msb) & !(calc_ok >> 0 & 0x01)) { - *msb = i; - calc_ok |= 0x01; - } - - if((pos == *lsb) & !(calc_ok >> 1 & 0x01)) { - *lsb = i; - calc_ok |= 0x02; - } - - if(calc_ok == 0x03) { - return; - } - +bool validate_prng_nonce(uint32_t nonce) { + if(nonce == 0) return false; + uint16_t x = nonce >> 16; + x = (x & 0xff) << 8 | x >> 8; + for(uint8_t i = 0; i < 16; i++) { x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; } -} - -// TODO: Faster? https://github.com/RfidResearchGroup/proxmark3/commit/bd3e8db852186d4ab9d5dda890d1cd52389b1254 -bool validate_prng_nonce(uint32_t nonce) { - uint32_t msb = nonce >> 16; - uint32_t lsb = nonce & 0xffff; - nonce_distance(&msb, &lsb); - return ((65535 - msb + lsb) % 65535) == 16; + x = (x & 0xff) << 8 | x >> 8; + return x == (nonce & 0xFFFF); } // Return 1 if the nonce is invalid else return 0 @@ -883,9 +861,17 @@ uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) */ // Helper function to add a nonce to the array -static bool add_nested_nonce(MfClassicNestedNonceArray* array, uint32_t cuid, uint8_t key_idx, uint32_t nt, uint32_t nt_enc, uint8_t par, uint16_t dist) { - MfClassicNestedNonce* new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); - if (new_nonces == NULL) return false; +static bool add_nested_nonce( + MfClassicNestedNonceArray* array, + uint32_t cuid, + uint8_t key_idx, + uint32_t nt, + uint32_t nt_enc, + uint8_t par, + uint16_t dist) { + MfClassicNestedNonce* new_nonces = + realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); + if(new_nonces == NULL) return false; array->nonces = new_nonces; array->nonces[array->count].cuid = cuid; @@ -905,11 +891,11 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan // Analyze PRNG by collecting nt uint8_t nonce_limit = 10; - if (dict_attack_ctx->nt_count > 0) { + if(dict_attack_ctx->nt_count > 0) { if(!validate_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; } - if(dict_attack_ctx->nt_count <= nonce_limit) { + if(dict_attack_ctx->nt_count < nonce_limit) { instance->state = MfClassicPollerStateNestedCollectNt; return command; } @@ -933,6 +919,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; // TODO: Check for Fudan backdoor dict_attack_ctx->backdoor = MfClassicBackdoorNone; + instance->state = MfClassicPollerStateNestedController; return command; } @@ -962,11 +949,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Collect parity - // TODO: FURI_CRITICAL_ENTER - // xTickCount - // Modify targets/f7/furi_hal/furi_hal_rtc.c to expose sub-second time (https://stackoverflow.com/questions/55534873/how-to-setup-the-stm32f4-real-time-clockrtc-to-get-valid-values-in-the-sub-sec) - // furi_hal_rtc_get_timestamp isn't precise enough - // TODO: Measure ticks with https://github.com/flipperdevices/flipperzero-firmware/blob/bec6bd381f222cf14658fd862a2c7f6e620bbf00/targets/f7/furi_hal/furi_hal_idle_timer.h#L51 + // TODO: Use the mode (most frequently observed) value and parity to reliably get a single candidate state /* uint32_t ncount = 0; uint32_t nttest = prng_successor(nt1, dmin - 1); @@ -986,16 +969,17 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // TODO: Target sector, key A/B do { - if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { FURI_LOG_E(TAG, "Hard PRNG, skipping calibration"); // TODO: Collect hardnested nonces (cuid, sec, par, nt_enc, A/B) // https://github.com/AloneLiberty/FlipperNested/blob/eba6163d7ef22adef5f9fe4d77a78ccfcc27a952/lib/nested/nested.c#L564-L645 break; } - uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); - + MfClassicAuthContext auth_ctx = {}; MfClassicError error; @@ -1005,7 +989,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; bool static_encrypted = false; - // Store last timestamp // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -1015,7 +998,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst dict_attack_ctx->current_key_type, &auth_ctx); - if (error != MfClassicErrorNone) { + if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); break; } @@ -1025,7 +1008,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); // Step 2: Perform nested authentication multiple times - for (uint32_t round_no = 0; round_no < rounds; round_no++) { + for(uint32_t round_no = 0; round_no < rounds; round_no++) { error = mf_classic_poller_auth_nested( instance, block, @@ -1033,15 +1016,15 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst dict_attack_ctx->current_key_type, &auth_ctx); - if (error != MfClassicErrorNone) { + if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform nested authentication %lu", round_no); continue; } uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); - if (nt_enc == nt_enc_prev) { + if(nt_enc == nt_enc_prev) { same_nt_enc_cnt++; - if (same_nt_enc_cnt > 3) { + if(same_nt_enc_cnt > 3) { static_encrypted = true; break; } @@ -1052,15 +1035,22 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst nt_enc_arr[round_no] = nt_enc; } - if (static_encrypted) { + if(static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if (dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { // TODO: Backdoor static nested attack break; } else { // TODO: If not present, just log nonces with parity bits - bool success = add_nested_nonce(&result, cuid, dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, 0, 65535); - if (!success) { + bool success = add_nested_nonce( + &result, + cuid, + dict_attack_ctx->reuse_key_sector, + nt_prev, + nt_enc_prev, + 0, + UINT16_MAX); + if(!success) { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } break; @@ -1069,44 +1059,57 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Find the distance between each nonce // TODO: Distance calculation + // TODO: Avoid repeated calibration + FURI_LOG_E(TAG, "Calibrating distance between nonces"); + // Checking for d_min == 0 is more of a workaround for not having a proper calibration process + if(dict_attack_ctx->d_min == 0) dict_attack_ctx->d_min = UINT16_MAX; uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); Crypto1 crypto; crypto1_init(&crypto, known_key); - // Skip the first round (typically an outlier) - for (uint32_t rtr = 1; rtr < rounds; rtr++) { + for(uint32_t rtr = 0; rtr < rounds; rtr++) { bool found = false; - for (int i = 0; i < 65535; i++) { + for(int i = 0; i < 65535; i++) { Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even}; uint32_t nth_successor = prng_successor(nt_prev, i); - if ((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) == nt_enc_arr[rtr]) { + if((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) != + nt_enc_arr[rtr]) { + continue; + } + if(rtr > 0) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); - if (i > dict_attack_ctx->d_max) { + if(i > dict_attack_ctx->d_max) { dict_attack_ctx->d_max = i; } - if (i < dict_attack_ctx->d_min) { + if(i < dict_attack_ctx->d_min) { dict_attack_ctx->d_min = i; } - nt_prev = nth_successor; - found = true; - break; } + nt_prev = nth_successor; + found = true; + break; } - if (!found) { + if(!found) { FURI_LOG_E(TAG, "Failed to find distance for nt_enc %08lx", nt_enc_arr[rtr]); - FURI_LOG_E(TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev); + FURI_LOG_E( + TAG, + "using key %06llx and uid %08lx, nt_prev is %08lx", + known_key, + cuid, + nt_prev); } } bool is_static = (dict_attack_ctx->d_min == dict_attack_ctx->d_max); FURI_LOG_E( TAG, - "Calibration completed: min=%u max=%u static: %s", - dict_attack_ctx->d_min, - dict_attack_ctx->d_max, - is_static ? "true" : "false"); + "Calibration completed: min=%u max=%u static: %s", + dict_attack_ctx->d_min, + dict_attack_ctx->d_max, + is_static ? "true" : "false"); //printf("ks: %08x\n", nth_successor ^ nt_enc); + //dict_attack_ctx->nested_target_key } while(false); instance->state = MfClassicPollerStateNestedController; @@ -1135,18 +1138,31 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { FuriString* temp_str = furi_string_alloc(); do { - if ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && (dict_attack_ctx->nested_nonce.count != 2)) { - FURI_LOG_E(TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %u", dict_attack_ctx->nested_nonce.count); + if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (dict_attack_ctx->nested_nonce.count != 2)) { + FURI_LOG_E( + TAG, + "MfClassicPollerStateNestedLog expected 2 nonces, received %u", + dict_attack_ctx->nested_nonce.count); break; } - if(!buffered_file_stream_open(stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) break; + if(!buffered_file_stream_open( + stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) + break; bool params_write_success = true; for(size_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; - furi_string_printf(temp_str, "Sec %d key %c cuid %08lx", (nonce->key_idx / 2), (nonce->key_idx % 2 == 0) ? 'A' : 'B', nonce->cuid); - for(uint8_t nt_idx = 0; nt_idx < ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? 2 : 1); nt_idx++) { + furi_string_printf( + temp_str, + "Sec %d key %c cuid %08lx", + (nonce->key_idx / 2), + (nonce->key_idx % 2 == 0) ? 'A' : 'B', + nonce->cuid); + for(uint8_t nt_idx = 0; + nt_idx < ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? 2 : 1); + nt_idx++) { furi_string_cat_printf( temp_str, " nt%u %08lx ks%u %08lx par%u ", @@ -1159,7 +1175,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { furi_string_cat_printf(temp_str, "%u", (nonce->par >> (3 - pb)) & 1); } } - if (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { furi_string_cat_printf(temp_str, " dist %u\n", nonce->dist); } else { furi_string_cat_printf(temp_str, "\n"); @@ -1189,21 +1205,21 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if (dict_attack_ctx->nested_nonce.count > 0) { - if (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + if(dict_attack_ctx->nested_nonce.count > 0) { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedDictAttack; return command; - } else if (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + } else if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { instance->state = MfClassicPollerStateNestedLog; return command; } } - if (dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } // Target all sectors, key A and B - for (uint8_t key_idx = 0; key_idx <= (instance->sectors_total * 2); key_idx++) { + for(uint8_t key_idx = 0; key_idx <= (instance->sectors_total * 2); key_idx++) { dict_attack_ctx->nested_target_key = key_idx; instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; @@ -1253,15 +1269,12 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b, [MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector, [MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng, - [MfClassicPollerStateNestedAnalyzeBackdoor] = mf_classic_poller_handler_nested_analyze_backdoor, - [MfClassicPollerStateNestedCollectNt] = - mf_classic_poller_handler_nested_collect_nt, - [MfClassicPollerStateNestedController] = - mf_classic_poller_handler_nested_controller, - [MfClassicPollerStateNestedCollectNtEnc] = - mf_classic_poller_handler_nested_collect_nt_enc, - [MfClassicPollerStateNestedDictAttack] = - mf_classic_poller_handler_nested_dict_attack, + [MfClassicPollerStateNestedAnalyzeBackdoor] = + mf_classic_poller_handler_nested_analyze_backdoor, + [MfClassicPollerStateNestedCollectNt] = mf_classic_poller_handler_nested_collect_nt, + [MfClassicPollerStateNestedController] = mf_classic_poller_handler_nested_controller, + [MfClassicPollerStateNestedCollectNtEnc] = mf_classic_poller_handler_nested_collect_nt_enc, + [MfClassicPollerStateNestedDictAttack] = mf_classic_poller_handler_nested_dict_attack, [MfClassicPollerStateNestedLog] = mf_classic_poller_handler_nested_log, [MfClassicPollerStateSuccess] = mf_classic_poller_handler_success, [MfClassicPollerStateFail] = mf_classic_poller_handler_fail, @@ -1348,4 +1361,4 @@ const NfcPollerBase mf_classic_poller = { .run = (NfcPollerRun)mf_classic_poller_run, .detect = (NfcPollerDetect)mf_classic_poller_detect, .get_data = (NfcPollerGetData)mf_classic_poller_get_data, -}; \ No newline at end of file +}; From 6d666389fb7e09b332e53339729c27724251b7e4 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 4 Aug 2024 08:30:02 -0400 Subject: [PATCH 006/236] Use EXT_PATH instead of ANY_PATH --- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 2a53411974..d29b92921d 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -9,8 +9,8 @@ extern "C" { #endif -#define MF_CLASSIC_FWT_FC (60000) -#define NFC_FOLDER ANY_PATH("nfc") +#define MF_CLASSIC_FWT_FC (60000) +#define NFC_FOLDER EXT_PATH("nfc") #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" #define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) From 09f8a7375105e748a09b11421e95a6b33b03466c Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 4 Aug 2024 23:53:21 -0400 Subject: [PATCH 007/236] Use median calibrated distance, collect parity bits --- .../protocols/mf_classic/mf_classic_poller.c | 249 +++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 5 +- 2 files changed, 191 insertions(+), 63 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index c0daffe758..3f7f083a78 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -889,7 +889,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; // Analyze PRNG by collecting nt - uint8_t nonce_limit = 10; + uint8_t nonce_limit = 5; if(dict_attack_ctx->nt_count > 0) { if(!validate_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; @@ -900,7 +900,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan return command; } - if(dict_attack_ctx->hard_nt_count > 3) { + if(dict_attack_ctx->hard_nt_count >= 3) { dict_attack_ctx->prng_type = MfClassicPrngTypeHard; // FIXME: E -> D FURI_LOG_E(TAG, "Detected Hard PRNG"); @@ -947,9 +947,162 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance return command; } +void bubble_sort(uint16_t arr[], int n) { + for(int i = 0; i < n - 1; i++) { + for(int j = 0; j < n - i - 1; j++) { + if(arr[j] > arr[j + 1]) { + uint16_t temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + } + } + } +} + +uint16_t get_median(uint16_t arr[], int n) { + bubble_sort(arr, n); + return arr[n / 2]; +} + +NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { + // TODO: Calibrate backdoored tags too + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + uint8_t nt_enc_calibration_cnt = 11; + uint32_t nt_enc_temp_arr[nt_enc_calibration_cnt]; + + uint16_t d_min = UINT16_MAX; + uint16_t d_max = 0; + uint16_t d_all[nt_enc_calibration_cnt - 1]; + uint8_t d_all_cnt = 0; + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error; + + uint32_t nt_prev = 0; + uint32_t nt_enc_prev = 0; + uint32_t same_nt_enc_cnt = 0; + bool static_encrypted = false; + + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } + + FURI_LOG_E(TAG, "Full authentication successful"); + + nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + + // Step 2: Perform nested authentication multiple times + for(uint8_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; + collection_cycle++) { + error = mf_classic_poller_auth_nested( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle); + continue; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + if(nt_enc == nt_enc_prev) { + same_nt_enc_cnt++; + if(same_nt_enc_cnt > 3) { + static_encrypted = true; + break; + } + } else { + same_nt_enc_cnt = 0; + nt_enc_prev = nt_enc; + } + nt_enc_temp_arr[collection_cycle] = nt_enc; + } + + if(static_encrypted) { + FURI_LOG_E(TAG, "Static encrypted nonce detected"); + if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + // TODO: Backdoor static nested attack calibration + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + return command; + } else { + // TODO: Log these + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + return command; + } + } + + // Find the distance between each nonce + FURI_LOG_E(TAG, "Calculating distance between nonces"); + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); + Crypto1 crypto; + crypto1_init(&crypto, known_key); + for(uint32_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; + collection_cycle++) { + bool found = false; + for(int i = 0; i < 65535; i++) { + Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even}; + uint32_t nth_successor = prng_successor(nt_prev, i); + if((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) != + nt_enc_temp_arr[collection_cycle]) { + continue; + } + if(collection_cycle > 0) { + FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); + FURI_LOG_E(TAG, "dist from nt prev: %i", i); + d_all[d_all_cnt++] = i; + if(i < d_min) d_min = i; + if(i > d_max) d_max = i; + } + nt_prev = nth_successor; + found = true; + break; + } + if(!found) { + FURI_LOG_E( + TAG, + "Failed to find distance for nt_enc %08lx", + nt_enc_temp_arr[collection_cycle]); + FURI_LOG_E( + TAG, "using key %06llx and uid %08lx, nt_prev is %08lx", known_key, cuid, nt_prev); + } + } + + dict_attack_ctx->d_median = get_median(d_all, d_all_cnt); + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + + mf_classic_poller_halt(instance); + FURI_LOG_E( + TAG, + "Calibration completed: med=%u min=%u max=%u static=%s", + dict_attack_ctx->d_median, + d_min, + d_max, + (d_min == d_max) ? "true" : "false"); + + return command; +} + NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Collect parity - // TODO: Use the mode (most frequently observed) value and parity to reliably get a single candidate state + // TODO: Use d_median value and parity to get candidate states. d_median is the center, keep adding +/- 1 until a parity match is found. /* uint32_t ncount = 0; uint32_t nttest = prng_successor(nt1, dmin - 1); @@ -966,11 +1119,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicNestedNonceArray result = {NULL, 0}; - // TODO: Target sector, key A/B do { if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { - FURI_LOG_E(TAG, "Hard PRNG, skipping calibration"); + FURI_LOG_E(TAG, "Hard PRNG, skipping"); // TODO: Collect hardnested nonces (cuid, sec, par, nt_enc, A/B) // https://github.com/AloneLiberty/FlipperNested/blob/eba6163d7ef22adef5f9fe4d77a78ccfcc27a952/lib/nested/nested.c#L564-L645 break; @@ -983,8 +1135,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - const uint32_t rounds = 11; - uint32_t nt_enc_arr[rounds]; + uint8_t nt_enc_per_collection = 2; + uint32_t nt_enc_arr[nt_enc_per_collection]; + uint8_t par_arr[nt_enc_per_collection]; + uint8_t parity = 0; uint32_t nt_prev = 0; uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; @@ -1007,8 +1161,16 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); - // Step 2: Perform nested authentication multiple times - for(uint32_t round_no = 0; round_no < rounds; round_no++) { + // Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset + // eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the + // most commonly observed nonce from 4 auths to known sector and 5th to target. This gets us a nonce pair, + // at a known distance (confirmed by parity bits) telling us the nt_enc plain. + // We also have backup values for the distance in case of rare issues. + //printf("ks: %08x\n", nth_successor ^ nt_enc); + //MfClassicKeyType target_key_type = + // (dict_attack_ctx->nested_target_key % 2 == 0) ? MfClassicKeyTypeA : MfClassicKeyTypeB; + //uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 2)) + 3; + for(uint32_t round_no = 0; round_no < nt_enc_per_collection; round_no++) { error = mf_classic_poller_auth_nested( instance, block, @@ -1032,6 +1194,12 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst same_nt_enc_cnt = 0; nt_enc_prev = nt_enc; } + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_encrypted_buffer); + for(int i = 0; i < 4; i++) { + parity |= ((parity_data[i / 8] >> (i % 8)) & 0x01) << i; + } + par_arr[round_no] = parity; nt_enc_arr[round_no] = nt_enc; } @@ -1057,59 +1225,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } } - // Find the distance between each nonce - // TODO: Distance calculation - // TODO: Avoid repeated calibration - FURI_LOG_E(TAG, "Calibrating distance between nonces"); - // Checking for d_min == 0 is more of a workaround for not having a proper calibration process - if(dict_attack_ctx->d_min == 0) dict_attack_ctx->d_min = UINT16_MAX; - uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); - Crypto1 crypto; - crypto1_init(&crypto, known_key); - for(uint32_t rtr = 0; rtr < rounds; rtr++) { - bool found = false; - for(int i = 0; i < 65535; i++) { - Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even}; - uint32_t nth_successor = prng_successor(nt_prev, i); - if((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) != - nt_enc_arr[rtr]) { - continue; - } - if(rtr > 0) { - FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); - FURI_LOG_E(TAG, "dist from nt prev: %i", i); - if(i > dict_attack_ctx->d_max) { - dict_attack_ctx->d_max = i; - } - if(i < dict_attack_ctx->d_min) { - dict_attack_ctx->d_min = i; - } - } - nt_prev = nth_successor; - found = true; - break; - } - if(!found) { - FURI_LOG_E(TAG, "Failed to find distance for nt_enc %08lx", nt_enc_arr[rtr]); - FURI_LOG_E( - TAG, - "using key %06llx and uid %08lx, nt_prev is %08lx", - known_key, - cuid, - nt_prev); - } + for(uint8_t i = 0; i < nt_enc_per_collection; i++) { + FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc_arr[i]); + FURI_LOG_E(TAG, "parity: %02x", par_arr[i]); } - bool is_static = (dict_attack_ctx->d_min == dict_attack_ctx->d_max); - - FURI_LOG_E( - TAG, - "Calibration completed: min=%u max=%u static: %s", - dict_attack_ctx->d_min, - dict_attack_ctx->d_max, - is_static ? "true" : "false"); - - //printf("ks: %08x\n", nth_successor ^ nt_enc); - //dict_attack_ctx->nested_target_key } while(false); instance->state = MfClassicPollerStateNestedController; @@ -1203,6 +1322,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys + // TODO: Fix infinite loop somewhere NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; if(dict_attack_ctx->nested_nonce.count > 0) { @@ -1218,6 +1338,12 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } + // TODO: Need to think about how this works for Fudan backdoored tags. + // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak && !dict_attack_ctx->calibrated) { + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } // Target all sectors, key A and B for(uint8_t key_idx = 0; key_idx <= (instance->sectors_total * 2); key_idx++) { dict_attack_ctx->nested_target_key = key_idx; @@ -1271,6 +1397,7 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng, [MfClassicPollerStateNestedAnalyzeBackdoor] = mf_classic_poller_handler_nested_analyze_backdoor, + [MfClassicPollerStateNestedCalibrate] = mf_classic_poller_handler_nested_calibrate, [MfClassicPollerStateNestedCollectNt] = mf_classic_poller_handler_nested_collect_nt, [MfClassicPollerStateNestedController] = mf_classic_poller_handler_nested_controller, [MfClassicPollerStateNestedCollectNtEnc] = mf_classic_poller_handler_nested_collect_nt_enc, diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index d29b92921d..f24f3496e2 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -82,6 +82,7 @@ typedef enum { // Enhanced dictionary attack states MfClassicPollerStateNestedAnalyzePRNG, MfClassicPollerStateNestedAnalyzeBackdoor, + MfClassicPollerStateNestedCalibrate, MfClassicPollerStateNestedCollectNt, MfClassicPollerStateNestedController, MfClassicPollerStateNestedCollectNtEnc, @@ -119,8 +120,8 @@ typedef struct { uint8_t hard_nt_count; uint8_t nested_target_key; MfClassicNestedNonceArray nested_nonce; - uint16_t d_min; - uint16_t d_max; + bool calibrated; + uint16_t d_median; } MfClassicPollerDictAttackContext; typedef struct { From 7d2cab5d77c7b07225e0b026768fe5c812b9c94d Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 5 Aug 2024 10:29:54 -0400 Subject: [PATCH 008/236] Modify parity collection --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3f7f083a78..87986b5fd9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1101,7 +1101,6 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { - // TODO: Collect parity // TODO: Use d_median value and parity to get candidate states. d_median is the center, keep adding +/- 1 until a parity match is found. /* uint32_t ncount = 0; @@ -1197,7 +1196,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Collect parity bits const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_encrypted_buffer); for(int i = 0; i < 4; i++) { - parity |= ((parity_data[i / 8] >> (i % 8)) & 0x01) << i; + parity |= ((parity_data[i] & 0x01) << (3 - i)); } par_arr[round_no] = parity; nt_enc_arr[round_no] = nt_enc; @@ -1216,7 +1215,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst dict_attack_ctx->reuse_key_sector, nt_prev, nt_enc_prev, - 0, + parity, UINT16_MAX); if(!success) { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); @@ -1227,7 +1226,13 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst for(uint8_t i = 0; i < nt_enc_per_collection; i++) { FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc_arr[i]); - FURI_LOG_E(TAG, "parity: %02x", par_arr[i]); + FURI_LOG_E( + TAG, + "parity: %u%u%u%u", + ((par_arr[i] >> 3) & 1), + ((par_arr[i] >> 2) & 1), + ((par_arr[i] >> 1) & 1), + (par_arr[i] & 1)); } } while(false); From 8dd3daf6257a1047a31d5ef30b3efb1d24620524 Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 9 Aug 2024 20:22:19 -0400 Subject: [PATCH 009/236] Fixed parity bit collection --- lib/nfc/helpers/crypto1.c | 43 +++ lib/nfc/helpers/crypto1.h | 4 + .../protocols/mf_classic/mf_classic_poller.c | 289 +++++++++++------- .../protocols/mf_classic/mf_classic_poller.h | 4 +- .../mf_classic/mf_classic_poller_i.c | 12 +- .../mf_classic/mf_classic_poller_i.h | 2 + lib/toolbox/bit_buffer.c | 2 +- targets/f7/api_symbols.csv | 6 +- 8 files changed, 252 insertions(+), 110 deletions(-) diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index bd4fc8d619..97b92edfd0 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -177,3 +177,46 @@ void crypto1_encrypt_reader_nonce( bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); } } + +static uint8_t lfsr_rollback_bit(Crypto1* crypto1, uint32_t in, int fb) { + int out; + uint8_t ret; + uint32_t t; + + crypto1->odd &= 0xffffff; + t = crypto1->odd; + crypto1->odd = crypto1->even; + crypto1->even = t; + + out = crypto1->even & 1; + out ^= LF_POLY_EVEN & (crypto1->even >>= 1); + out ^= LF_POLY_ODD & crypto1->odd; + out ^= !!in; + out ^= (ret = crypto1_filter(crypto1->odd)) & (!!fb); + + crypto1->even |= (nfc_util_even_parity32(out)) << 23; + return ret; +} + +uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { + uint32_t ret = 0; + for(int i = 31; i >= 0; i--) { + ret |= lfsr_rollback_bit(crypto1, BEBIT(in, i), fb) << (24 ^ i); + } + return ret; +} + +// Return true if the nonce is invalid else return false +bool valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t parity) { + return ((nfc_util_odd_parity8((Nt >> 24) & 0xFF) == + (((parity >> 3) & 1) ^ nfc_util_odd_parity8((NtEnc >> 24) & 0xFF) ^ + FURI_BIT(Ks1, 16))) && + (nfc_util_odd_parity8((Nt >> 16) & 0xFF) == + (((parity >> 2) & 1) ^ nfc_util_odd_parity8((NtEnc >> 16) & 0xFF) ^ + FURI_BIT(Ks1, 8))) && + (nfc_util_odd_parity8((Nt >> 8) & 0xFF) == + (((parity >> 1) & 1) ^ nfc_util_odd_parity8((NtEnc >> 8) & 0xFF) ^ + FURI_BIT(Ks1, 0)))) ? + true : + false; +} diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index e71ab9a408..ed71bc3e12 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -38,6 +38,10 @@ void crypto1_encrypt_reader_nonce( BitBuffer* out, bool is_nested); +uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); + +bool valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t parity); + uint32_t prng_successor(uint32_t x, uint32_t n); #ifdef __cplusplus diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 87986b5fd9..3b7c499a50 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -849,17 +849,6 @@ bool validate_prng_nonce(uint32_t nonce) { return x == (nonce & 0xFFFF); } -// Return 1 if the nonce is invalid else return 0 -/* -uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { - return ( - (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ - (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ - (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) - ) ? 1 : 0; -} -*/ - // Helper function to add a nonce to the array static bool add_nested_nonce( MfClassicNestedNonceArray* array, @@ -918,6 +907,28 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; // TODO: Check for Fudan backdoor + // Can use on more than S variant as a free key for Nested + /* + do { + uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_cmd[2] = {auth_type, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + + if(is_nested) { + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth + MF_CLASSIC_FWT_FC); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + */ dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateNestedController; return command; @@ -966,6 +977,7 @@ uint16_t get_median(uint16_t arr[], int n) { NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { // TODO: Calibrate backdoored tags too + // TODO: Check if we have already identified the tag as static encrypted NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint8_t nt_enc_calibration_cnt = 11; @@ -984,7 +996,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) uint32_t nt_prev = 0; uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; - bool static_encrypted = false; + uint8_t nt_enc_collected = 0; // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -1012,28 +1024,33 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) block, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle); continue; } - uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); - if(nt_enc == nt_enc_prev) { + nt_enc_temp_arr[collection_cycle] = + bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + nt_enc_collected++; + } + + for(int i = 0; i < nt_enc_collected; i++) { + if(nt_enc_temp_arr[i] == nt_enc_prev) { same_nt_enc_cnt++; if(same_nt_enc_cnt > 3) { - static_encrypted = true; + dict_attack_ctx->static_encrypted = true; break; } } else { same_nt_enc_cnt = 0; - nt_enc_prev = nt_enc; + nt_enc_prev = nt_enc_temp_arr[i]; } - nt_enc_temp_arr[collection_cycle] = nt_enc; } - if(static_encrypted) { + if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { // TODO: Backdoor static nested attack calibration @@ -1101,23 +1118,10 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { - // TODO: Use d_median value and parity to get candidate states. d_median is the center, keep adding +/- 1 until a parity match is found. - /* - uint32_t ncount = 0; - uint32_t nttest = prng_successor(nt1, dmin - 1); - - for(j = dmin; j < dmax + 1; j++) { - nttest = prng_successor(nttest, 1); - ks1 = nt2 ^ nttest; - - if(valid_nonce(nttest, nt2, ks1, par_array)) { - if(ncount > 0) { // we are only interested in disambiguous nonces, try again - FURI_LOG_D(TAG, "Nonce#%lu: dismissed (ambiguous), ntdist=%lu", i + 1, j); - (..) - */ - NfcCommand command = NfcCommandContinue; + // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) + NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - MfClassicNestedNonceArray result = {NULL, 0}; + bool collection_success = false; do { if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { @@ -1127,6 +1131,30 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst break; } + if(dict_attack_ctx->static_encrypted) { + FURI_LOG_E(TAG, "Static encrypted nonce detected"); + if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + // TODO: Backdoor static nested attack with calibrated distance + break; + } else { + // TODO: If not present, just log nonces with parity bits, e.g. + /* + bool success = add_nested_nonce( + &result, + cuid, + dict_attack_ctx->reuse_key_sector, + nt_prev, + nt_enc_prev, + parity, + UINT16_MAX); + if(!success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + } + */ + break; + } + } + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); @@ -1134,14 +1162,15 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - uint8_t nt_enc_per_collection = 2; - uint32_t nt_enc_arr[nt_enc_per_collection]; - uint8_t par_arr[nt_enc_per_collection]; + uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; + uint8_t nt_enc_per_collection = 2 + nonce_pair_index; + MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; + uint32_t nt_enc_temp_arr[nt_enc_per_collection]; + uint8_t nt_enc_collected = 0; uint8_t parity = 0; - uint32_t nt_prev = 0; - uint32_t nt_enc_prev = 0; - uint32_t same_nt_enc_cnt = 0; - bool static_encrypted = false; // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -1158,84 +1187,132 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst FURI_LOG_E(TAG, "Full authentication successful"); - nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); - // Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset // eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the // most commonly observed nonce from 4 auths to known sector and 5th to target. This gets us a nonce pair, // at a known distance (confirmed by parity bits) telling us the nt_enc plain. - // We also have backup values for the distance in case of rare issues. - //printf("ks: %08x\n", nth_successor ^ nt_enc); - //MfClassicKeyType target_key_type = - // (dict_attack_ctx->nested_target_key % 2 == 0) ? MfClassicKeyTypeA : MfClassicKeyTypeB; - //uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 2)) + 3; - for(uint32_t round_no = 0; round_no < nt_enc_per_collection; round_no++) { + for(uint8_t collection_cycle = 0; collection_cycle < (nt_enc_per_collection - 1); + collection_cycle++) { + // This loop must match the calibrated loop error = mf_classic_poller_auth_nested( instance, block, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { - FURI_LOG_E(TAG, "Failed to perform nested authentication %lu", round_no); - continue; + FURI_LOG_E(TAG, "Failed to perform nested authentication %u", collection_cycle); + break; } - uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); - if(nt_enc == nt_enc_prev) { - same_nt_enc_cnt++; - if(same_nt_enc_cnt > 3) { - static_encrypted = true; - break; - } - } else { - same_nt_enc_cnt = 0; - nt_enc_prev = nt_enc; - } - // Collect parity bits - const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_encrypted_buffer); - for(int i = 0; i < 4; i++) { - parity |= ((parity_data[i] & 0x01) << (3 - i)); - } - par_arr[round_no] = parity; - nt_enc_arr[round_no] = nt_enc; + nt_enc_temp_arr[collection_cycle] = + bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + nt_enc_collected++; } + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->current_key, + target_key_type, + &auth_ctx, + true); - if(static_encrypted) { - FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { - // TODO: Backdoor static nested attack + if(nt_enc_collected != (nt_enc_per_collection - 1)) { + FURI_LOG_E(TAG, "Failed to collect sufficient nt_enc values"); + break; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); + for(int i = 0; i < 4; i++) { + parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + } + // Decrypt the previous nonce + uint32_t nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); + Crypto1 crypto_temp; + crypto1_init(&crypto_temp, known_key); + crypto1_word(&crypto_temp, nt_prev ^ cuid, 1); + uint32_t decrypted_nt_prev = + (nt_prev ^ lfsr_rollback_word(&crypto_temp, nt_prev ^ cuid, 1)); + + // Find matching nt_enc plain at expected distance + bool found_matching_nt = false; + uint32_t found_nt = 0; + uint16_t current_dist = 0; + const uint16_t max_dist = 16; // 32 would work too + // TODO: Better handling of overflows (allow wrap instead of stopping?) + while(!found_matching_nt && current_dist < max_dist && + ((dict_attack_ctx->d_median - current_dist) != 0) && + ((dict_attack_ctx->d_median + current_dist) != UINT16_MAX)) { + uint32_t nth_successor_positive = + prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median + current_dist); + if(valid_nonce( + nth_successor_positive, nt_enc, nth_successor_positive ^ nt_enc, parity)) { + found_matching_nt = true; + found_nt = nth_successor_positive; break; - } else { - // TODO: If not present, just log nonces with parity bits - bool success = add_nested_nonce( - &result, - cuid, - dict_attack_ctx->reuse_key_sector, - nt_prev, - nt_enc_prev, - parity, - UINT16_MAX); - if(!success) { - FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + } + if(current_dist > 0) { + uint32_t nth_successor_negative = + prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median - current_dist); + if(valid_nonce( + nth_successor_negative, nt_enc, nth_successor_negative ^ nt_enc, parity)) { + found_matching_nt = true; + found_nt = nth_successor_negative; + break; } - break; } + current_dist++; } - for(uint8_t i = 0; i < nt_enc_per_collection; i++) { - FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc_arr[i]); - FURI_LOG_E( - TAG, - "parity: %u%u%u%u", - ((par_arr[i] >> 3) & 1), - ((par_arr[i] >> 2) & 1), - ((par_arr[i] >> 1) & 1), - (par_arr[i] & 1)); + // Add the nonce to the array + if(found_matching_nt) { + collection_success = add_nested_nonce( + &dict_attack_ctx->nested_nonce, + cuid, + dict_attack_ctx->nested_target_key, + found_nt, + nt_enc, + parity, + 0); + if(!collection_success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + } + } else { + FURI_LOG_E(TAG, "Failed to find matching nonce"); } + + FURI_LOG_E( + TAG, + "Target: %u (nonce pair %u, key type %s, block %u)", + dict_attack_ctx->nested_target_key, + nonce_pair_index, + (target_key_type == MfClassicKeyTypeA) ? "A" : "B", + target_block); + FURI_LOG_E(TAG, "cuid: %08lx", cuid); + FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); + FURI_LOG_E( + TAG, + "parity: %u%u%u%u", + ((parity >> 3) & 1), + ((parity >> 2) & 1), + ((parity >> 1) & 1), + (parity & 1)); + FURI_LOG_E(TAG, "nt_enc prev: %08lx", nt_prev); + FURI_LOG_E(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); } while(false); + // Probably belongs in the controller + if(collection_success) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->retry_counter = 0; + } else { + dict_attack_ctx->retry_counter++; + } instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); @@ -1327,10 +1404,10 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys - // TODO: Fix infinite loop somewhere NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if(dict_attack_ctx->nested_nonce.count > 0) { + if((dict_attack_ctx->nested_nonce.count > 0) && + (dict_attack_ctx->nested_target_key % 2 == 0)) { if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedDictAttack; return command; @@ -1349,9 +1426,17 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedCalibrate; return command; } - // Target all sectors, key A and B - for(uint8_t key_idx = 0; key_idx <= (instance->sectors_total * 2); key_idx++) { - dict_attack_ctx->nested_target_key = key_idx; + // Target all sectors, key A and B, first and second nonce + if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { + if(dict_attack_ctx->retry_counter > 3) { + // Bad sector, skip + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.count = 0; + } + dict_attack_ctx->nested_target_key += 4; + dict_attack_ctx->retry_counter = 0; + } instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 518d029d07..0434249899 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -231,6 +231,7 @@ MfClassicError mf_classic_poller_auth( * @param[in] key key to be used for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @param[in] early_ret return immediately after receiving encrypted nonce. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_auth_nested( @@ -238,7 +239,8 @@ MfClassicError mf_classic_poller_auth_nested( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data); + MfClassicAuthContext* data, + bool early_ret); /** * @brief Halt the tag. diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 949ef8e66e..d3a3882c18 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -111,7 +111,8 @@ static MfClassicError mf_classic_poller_auth_common( MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, - bool is_nested) { + bool is_nested, + bool early_ret) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -130,6 +131,7 @@ static MfClassicError mf_classic_poller_auth_common( if(data) { data->nt = nt; } + if(early_ret) break; uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); uint64_t key_num = bit_lib_bytes_to_num_be(key->data, sizeof(MfClassicKey)); @@ -185,7 +187,7 @@ MfClassicError mf_classic_poller_auth( MfClassicAuthContext* data) { furi_check(instance); furi_check(key); - return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false); + return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false, false); } MfClassicError mf_classic_poller_auth_nested( @@ -193,10 +195,12 @@ MfClassicError mf_classic_poller_auth_nested( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data) { + MfClassicAuthContext* data, + bool early_ret) { furi_check(instance); furi_check(key); - return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, true); + return mf_classic_poller_auth_common( + instance, block_num, key, key_type, data, true, early_ret); } MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index f24f3496e2..0614f99c1f 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -120,8 +120,10 @@ typedef struct { uint8_t hard_nt_count; uint8_t nested_target_key; MfClassicNestedNonceArray nested_nonce; + bool static_encrypted; bool calibrated; uint16_t d_median; + uint8_t retry_counter; } MfClassicPollerDictAttackContext; typedef struct { diff --git a/lib/toolbox/bit_buffer.c b/lib/toolbox/bit_buffer.c index 85a52e79d6..e261e80d4f 100644 --- a/lib/toolbox/bit_buffer.c +++ b/lib/toolbox/bit_buffer.c @@ -113,7 +113,7 @@ void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size uint8_t bit = FURI_BIT(data[bits_processed / BITS_IN_BYTE + 1], bits_processed % BITS_IN_BYTE); - if(bits_processed % BITS_IN_BYTE) { + if((bits_processed % BITS_IN_BYTE) == 0) { buf->parity[curr_byte / BITS_IN_BYTE] = bit; } else { buf->parity[curr_byte / BITS_IN_BYTE] |= bit << (bits_processed % BITS_IN_BYTE); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 4bfe37abb5..b2e818fb8f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,71.0,, +Version,+,72.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -2211,6 +2211,7 @@ Function,+,lfrfid_worker_start_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_stop,void,LFRFIDWorker* Function,+,lfrfid_worker_stop_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_write_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" +Function,+,lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,lgamma,double,double Function,-,lgamma_r,double,"double, int*" Function,-,lgammaf,float,float @@ -2506,7 +2507,7 @@ Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" -Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" +Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool" Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* @@ -3525,6 +3526,7 @@ Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" +Function,+,valid_nonce,_Bool,"uint32_t, uint32_t, uint32_t, uint8_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* From 4b44288c960576ec0671d1d450630b9700eb9a0e Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 9 Aug 2024 20:38:45 -0400 Subject: [PATCH 010/236] Add note to fix nonce logging --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3b7c499a50..80cf8aed54 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1328,6 +1328,15 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc } NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { + // TODO: Fix this logging the same nonce twice, and there should only be 16 sectors (1K) + /* + uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; + uint8_t nt_enc_per_collection = 2 + nonce_pair_index; + MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; + */ furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); From 5feeae8972295aec3923c16e4a01d794bf0dd208 Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 9 Aug 2024 22:01:43 -0400 Subject: [PATCH 011/236] Fix nonce logging --- .../protocols/mf_classic/mf_classic_poller.c | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 80cf8aed54..35909a632b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1328,15 +1328,6 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc } NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { - // TODO: Fix this logging the same nonce twice, and there should only be 16 sectors (1K) - /* - uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; - uint8_t nt_enc_per_collection = 2 + nonce_pair_index; - MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; - */ furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.count > 0); furi_assert(instance->mode_ctx.dict_attack_ctx.nested_nonce.nonces); @@ -1346,10 +1337,10 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { Storage* storage = furi_record_open(RECORD_STORAGE); Stream* stream = buffered_file_stream_alloc(storage); FuriString* temp_str = furi_string_alloc(); + bool weak_prng = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; do { - if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (dict_attack_ctx->nested_nonce.count != 2)) { + if(weak_prng && (dict_attack_ctx->nested_nonce.count != 2)) { FURI_LOG_E( TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %u", @@ -1357,22 +1348,27 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { break; } + uint32_t nonce_pair_count = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak ? + 1 : + dict_attack_ctx->nested_nonce.count; + if(!buffered_file_stream_open( stream, MF_CLASSIC_NESTED_LOGS_FILE_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) break; bool params_write_success = true; - for(size_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { + for(size_t i = 0; i < nonce_pair_count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; furi_string_printf( temp_str, "Sec %d key %c cuid %08lx", - (nonce->key_idx / 2), - (nonce->key_idx % 2 == 0) ? 'A' : 'B', + (nonce->key_idx / 4), + ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', nonce->cuid); - for(uint8_t nt_idx = 0; - nt_idx < ((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? 2 : 1); - nt_idx++) { + for(uint8_t nt_idx = 0; nt_idx < (weak_prng ? 2 : 1); nt_idx++) { + if(weak_prng && nt_idx == 1) { + nonce = &dict_attack_ctx->nested_nonce.nonces[i + 1]; + } furi_string_cat_printf( temp_str, " nt%u %08lx ks%u %08lx par%u ", From 3acba77070a3a114b1bb49471fd5a1f14b0b6028 Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 9 Aug 2024 22:08:35 -0400 Subject: [PATCH 012/236] Clean redundant code --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 35909a632b..d6d1428758 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1366,7 +1366,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', nonce->cuid); for(uint8_t nt_idx = 0; nt_idx < (weak_prng ? 2 : 1); nt_idx++) { - if(weak_prng && nt_idx == 1) { + if(nt_idx == 1) { nonce = &dict_attack_ctx->nested_nonce.nonces[i + 1]; } furi_string_cat_printf( From 6332ec7478ce377c073da63c30d50c950e6b2ccd Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 11 Aug 2024 14:30:26 -0400 Subject: [PATCH 013/236] Fix valid_nonce --- lib/nfc/helpers/crypto1.c | 19 +++++++------------ lib/nfc/helpers/crypto1.h | 2 +- lib/nfc/helpers/nfc_util.c | 4 ++++ lib/nfc/helpers/nfc_util.h | 2 ++ .../protocols/mf_classic/mf_classic_poller.c | 6 ++---- targets/f7/api_symbols.csv | 3 ++- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index 97b92edfd0..a228e3f098 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -207,16 +207,11 @@ uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { } // Return true if the nonce is invalid else return false -bool valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t parity) { - return ((nfc_util_odd_parity8((Nt >> 24) & 0xFF) == - (((parity >> 3) & 1) ^ nfc_util_odd_parity8((NtEnc >> 24) & 0xFF) ^ - FURI_BIT(Ks1, 16))) && - (nfc_util_odd_parity8((Nt >> 16) & 0xFF) == - (((parity >> 2) & 1) ^ nfc_util_odd_parity8((NtEnc >> 16) & 0xFF) ^ - FURI_BIT(Ks1, 8))) && - (nfc_util_odd_parity8((Nt >> 8) & 0xFF) == - (((parity >> 1) & 1) ^ nfc_util_odd_parity8((NtEnc >> 8) & 0xFF) ^ - FURI_BIT(Ks1, 0)))) ? - true : - false; +bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { + return (nfc_util_even_parity8((nt >> 24) & 0xFF) == + (((nt_par_enc >> 3) & 1) ^ FURI_BIT(ks, 16))) && + (nfc_util_even_parity8((nt >> 16) & 0xFF) == + (((nt_par_enc >> 2) & 1) ^ FURI_BIT(ks, 8))) && + (nfc_util_even_parity8((nt >> 8) & 0xFF) == + (((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0))); } diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index ed71bc3e12..ef9bd69b51 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -40,7 +40,7 @@ void crypto1_encrypt_reader_nonce( uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); -bool valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t parity); +bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); uint32_t prng_successor(uint32_t x, uint32_t n); diff --git a/lib/nfc/helpers/nfc_util.c b/lib/nfc/helpers/nfc_util.c index f502b4bfb4..80af5cf118 100644 --- a/lib/nfc/helpers/nfc_util.c +++ b/lib/nfc/helpers/nfc_util.c @@ -13,6 +13,10 @@ static const uint8_t nfc_util_odd_byte_parity[256] = { 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1}; +uint8_t nfc_util_even_parity8(uint8_t data) { + return !nfc_util_odd_byte_parity[data]; +} + uint8_t nfc_util_even_parity32(uint32_t data) { // data ^= data >> 16; // data ^= data >> 8; diff --git a/lib/nfc/helpers/nfc_util.h b/lib/nfc/helpers/nfc_util.h index f8e86d8658..4abde4521f 100644 --- a/lib/nfc/helpers/nfc_util.h +++ b/lib/nfc/helpers/nfc_util.h @@ -6,6 +6,8 @@ extern "C" { #endif +uint8_t nfc_util_even_parity8(uint8_t data); + uint8_t nfc_util_even_parity32(uint32_t data); uint8_t nfc_util_odd_parity8(uint8_t data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index d6d1428758..b35f6d2df9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1250,8 +1250,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst ((dict_attack_ctx->d_median + current_dist) != UINT16_MAX)) { uint32_t nth_successor_positive = prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median + current_dist); - if(valid_nonce( - nth_successor_positive, nt_enc, nth_successor_positive ^ nt_enc, parity)) { + if(valid_nonce(nth_successor_positive, nth_successor_positive ^ nt_enc, parity)) { found_matching_nt = true; found_nt = nth_successor_positive; break; @@ -1259,8 +1258,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst if(current_dist > 0) { uint32_t nth_successor_negative = prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median - current_dist); - if(valid_nonce( - nth_successor_negative, nt_enc, nth_successor_negative ^ nt_enc, parity)) { + if(valid_nonce(nth_successor_negative, nth_successor_negative ^ nt_enc, parity)) { found_matching_nt = true; found_nt = nth_successor_negative; break; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index b2e818fb8f..7dae5837ae 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2805,6 +2805,7 @@ Function,+,nfc_set_mask_receive_time_fc,void,"Nfc*, uint32_t" Function,+,nfc_start,void,"Nfc*, NfcEventCallback, void*" Function,+,nfc_stop,void,Nfc* Function,+,nfc_util_even_parity32,uint8_t,uint32_t +Function,+,nfc_util_even_parity8,uint8_t,uint8_t Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" @@ -3526,7 +3527,7 @@ Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,+,valid_nonce,_Bool,"uint32_t, uint32_t, uint32_t, uint8_t" +Function,+,valid_nonce,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* From 01b19483c59af70819d994bf2ce18eefa13ae244 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 14 Aug 2024 02:25:57 -0400 Subject: [PATCH 014/236] First attempt disambiguous nonce implementation --- lib/nfc/helpers/crypto1.c | 23 ++- lib/nfc/helpers/crypto1.h | 7 +- .../protocols/mf_classic/mf_classic_poller.c | 177 +++++++----------- .../mf_classic/mf_classic_poller_i.h | 14 +- targets/f7/api_symbols.csv | 4 +- 5 files changed, 108 insertions(+), 117 deletions(-) diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index a228e3f098..e59657a409 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -206,8 +206,7 @@ uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { return ret; } -// Return true if the nonce is invalid else return false -bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { +bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { return (nfc_util_even_parity8((nt >> 24) & 0xFF) == (((nt_par_enc >> 3) & 1) ^ FURI_BIT(ks, 16))) && (nfc_util_even_parity8((nt >> 16) & 0xFF) == @@ -215,3 +214,23 @@ bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { (nfc_util_even_parity8((nt >> 8) & 0xFF) == (((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0))); } + +bool is_weak_prng_nonce(uint32_t nonce) { + if(nonce == 0) return false; + uint16_t x = nonce >> 16; + x = (x & 0xff) << 8 | x >> 8; + for(uint8_t i = 0; i < 16; i++) { + x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; + } + x = (x & 0xff) << 8 | x >> 8; + return x == (nonce & 0xFFFF); +} + +uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { + uint64_t known_key_int = bit_lib_bytes_to_num_be(known_key.data, 6); + Crypto1 crypto_temp; + crypto1_init(&crypto_temp, known_key_int); + crypto1_word(&crypto_temp, nt_enc ^ cuid, 1); + uint32_t decrypted_nt_enc = (nt_enc ^ lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); + return decrypted_nt_enc; +} diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index ef9bd69b51..26862ab498 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -1,5 +1,6 @@ #pragma once +#include "protocols/mf_classic/mf_classic.h" #include #ifdef __cplusplus @@ -40,7 +41,11 @@ void crypto1_encrypt_reader_nonce( uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); -bool valid_nonce(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); +bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); + +bool is_weak_prng_nonce(uint32_t nonce); + +uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); uint32_t prng_successor(uint32_t x, uint32_t n); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index b35f6d2df9..f96590344b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -3,8 +3,6 @@ #include #include -#include -#include #define TAG "MfClassicPoller" @@ -838,17 +836,6 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst return command; } -bool validate_prng_nonce(uint32_t nonce) { - if(nonce == 0) return false; - uint16_t x = nonce >> 16; - x = (x & 0xff) << 8 | x >> 8; - for(uint8_t i = 0; i < 16; i++) { - x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15; - } - x = (x & 0xff) << 8 | x >> 8; - return x == (nonce & 0xFFFF); -} - // Helper function to add a nonce to the array static bool add_nested_nonce( MfClassicNestedNonceArray* array, @@ -881,7 +868,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan uint8_t nonce_limit = 5; if(dict_attack_ctx->nt_count > 0) { - if(!validate_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; + if(!is_weak_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; } if(dict_attack_ctx->nt_count < nonce_limit) { @@ -958,35 +945,16 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance return command; } -void bubble_sort(uint16_t arr[], int n) { - for(int i = 0; i < n - 1; i++) { - for(int j = 0; j < n - i - 1; j++) { - if(arr[j] > arr[j + 1]) { - uint16_t temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - } - } - } -} - -uint16_t get_median(uint16_t arr[], int n) { - bubble_sort(arr, n); - return arr[n / 2]; -} - NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { // TODO: Calibrate backdoored tags too // TODO: Check if we have already identified the tag as static encrypted NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - uint8_t nt_enc_calibration_cnt = 11; + uint8_t nt_enc_calibration_cnt = 21; uint32_t nt_enc_temp_arr[nt_enc_calibration_cnt]; - uint16_t d_min = UINT16_MAX; - uint16_t d_max = 0; - uint16_t d_all[nt_enc_calibration_cnt - 1]; - uint8_t d_all_cnt = 0; + dict_attack_ctx->d_min = UINT16_MAX; + dict_attack_ctx->d_max = 0; uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); @@ -1068,24 +1036,21 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); - Crypto1 crypto; - crypto1_init(&crypto, known_key); for(uint32_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; collection_cycle++) { bool found = false; + uint32_t decrypted_nt_enc = + decrypt_nt_enc(cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->current_key); for(int i = 0; i < 65535; i++) { - Crypto1 crypto_temp = {.odd = crypto.odd, .even = crypto.even}; uint32_t nth_successor = prng_successor(nt_prev, i); - if((nth_successor ^ crypto1_word(&crypto_temp, cuid ^ nth_successor, 0)) != - nt_enc_temp_arr[collection_cycle]) { + if(nth_successor != decrypted_nt_enc) { continue; } if(collection_cycle > 0) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); - d_all[d_all_cnt++] = i; - if(i < d_min) d_min = i; - if(i > d_max) d_max = i; + if(i < dict_attack_ctx->d_min) dict_attack_ctx->d_min = i; + if(i > dict_attack_ctx->d_max) dict_attack_ctx->d_max = i; } nt_prev = nth_successor; found = true; @@ -1101,27 +1066,30 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } } - dict_attack_ctx->d_median = get_median(d_all, d_all_cnt); + // Some breathing room, doesn't account for overflows or static nested (FIXME) + dict_attack_ctx->d_min -= 3; + dict_attack_ctx->d_max += 3; + + furi_assert(dict_attack_ctx->d_min <= dict_attack_ctx->d_max); dict_attack_ctx->calibrated = true; instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); FURI_LOG_E( TAG, - "Calibration completed: med=%u min=%u max=%u static=%s", - dict_attack_ctx->d_median, - d_min, - d_max, - (d_min == d_max) ? "true" : "false"); + "Calibration completed: min=%u max=%u static=%s", + dict_attack_ctx->d_min, + dict_attack_ctx->d_max, + (dict_attack_ctx->d_min == dict_attack_ctx->d_max) ? "true" : "false"); return command; } NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) + // TODO: Look into using MfClassicNt more NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - bool collection_success = false; do { if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { @@ -1163,7 +1131,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicError error; uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; - uint8_t nt_enc_per_collection = 2 + nonce_pair_index; + uint8_t nt_enc_per_collection = (dict_attack_ctx->attempt_count + 2) + nonce_pair_index; MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; @@ -1232,56 +1200,40 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } // Decrypt the previous nonce uint32_t nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); - Crypto1 crypto_temp; - crypto1_init(&crypto_temp, known_key); - crypto1_word(&crypto_temp, nt_prev ^ cuid, 1); - uint32_t decrypted_nt_prev = - (nt_prev ^ lfsr_rollback_word(&crypto_temp, nt_prev ^ cuid, 1)); + uint32_t decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->current_key); // Find matching nt_enc plain at expected distance - bool found_matching_nt = false; uint32_t found_nt = 0; - uint16_t current_dist = 0; - const uint16_t max_dist = 16; // 32 would work too - // TODO: Better handling of overflows (allow wrap instead of stopping?) - while(!found_matching_nt && current_dist < max_dist && - ((dict_attack_ctx->d_median - current_dist) != 0) && - ((dict_attack_ctx->d_median + current_dist) != UINT16_MAX)) { - uint32_t nth_successor_positive = - prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median + current_dist); - if(valid_nonce(nth_successor_positive, nth_successor_positive ^ nt_enc, parity)) { - found_matching_nt = true; - found_nt = nth_successor_positive; - break; - } - if(current_dist > 0) { - uint32_t nth_successor_negative = - prng_successor(decrypted_nt_prev, dict_attack_ctx->d_median - current_dist); - if(valid_nonce(nth_successor_negative, nth_successor_negative ^ nt_enc, parity)) { - found_matching_nt = true; - found_nt = nth_successor_negative; + uint8_t found_nt_cnt = 0; + uint16_t current_dist = dict_attack_ctx->d_min; + while(current_dist <= dict_attack_ctx->d_max) { + uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); + if(nonce_matches_encrypted_parity_bits(nth_successor, nth_successor ^ nt_enc, parity)) { + found_nt_cnt++; + if(found_nt_cnt > 1) { + FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt"); break; } + found_nt = nth_successor; } current_dist++; } + if(found_nt_cnt != 1) { + break; + } // Add the nonce to the array - if(found_matching_nt) { - collection_success = add_nested_nonce( - &dict_attack_ctx->nested_nonce, - cuid, - dict_attack_ctx->nested_target_key, - found_nt, - nt_enc, - parity, - 0); - if(!collection_success) { - FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); - } + if(add_nested_nonce( + &dict_attack_ctx->nested_nonce, + cuid, + dict_attack_ctx->nested_target_key, + found_nt, + nt_enc, + parity, + 0)) { + dict_attack_ctx->nested_state = MfClassicNestedStatePassed; } else { - FURI_LOG_E(TAG, "Failed to find matching nonce"); + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } FURI_LOG_E( @@ -1304,13 +1256,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst FURI_LOG_E(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); } while(false); - // Probably belongs in the controller - if(collection_success) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->retry_counter = 0; - } else { - dict_attack_ctx->retry_counter++; - } instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); @@ -1409,15 +1354,16 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if((dict_attack_ctx->nested_nonce.count > 0) && - (dict_attack_ctx->nested_target_key % 2 == 0)) { - if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { - instance->state = MfClassicPollerStateNestedDictAttack; - return command; - } else if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { - instance->state = MfClassicPollerStateNestedLog; - return command; - } + if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (dict_attack_ctx->nested_nonce.count == 2)) { + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } else if( + (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) && + (dict_attack_ctx->nested_nonce.count > 0)) { + // TODO: Need to think about the meaning of nested_target_key for hard PRNG + instance->state = MfClassicPollerStateNestedLog; + return command; } if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; @@ -1430,15 +1376,24 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance return command; } // Target all sectors, key A and B, first and second nonce + // TODO: Missing weak condition, target_key logic doesn't apply the same to hard if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { - if(dict_attack_ctx->retry_counter > 3) { - // Bad sector, skip + if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + dict_attack_ctx->attempt_count++; + } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->nested_state = MfClassicNestedStateNone; + if(dict_attack_ctx->attempt_count >= 20) { + // Unpredictable, skip + FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); if(dict_attack_ctx->nested_nonce.nonces) { free(dict_attack_ctx->nested_nonce.nonces); dict_attack_ctx->nested_nonce.count = 0; } - dict_attack_ctx->nested_target_key += 4; - dict_attack_ctx->retry_counter = 0; + dict_attack_ctx->nested_target_key += 2; + dict_attack_ctx->attempt_count = 0; } instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 0614f99c1f..d10401e763 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -24,6 +26,12 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; +typedef enum { + MfClassicNestedStateNone, + MfClassicNestedStateFailed, + MfClassicNestedStatePassed, +} MfClassicNestedState; + typedef enum { MfClassicPrngTypeUnknown, // Tag not yet tested MfClassicPrngTypeNoTag, // No tag detected during test @@ -122,8 +130,10 @@ typedef struct { MfClassicNestedNonceArray nested_nonce; bool static_encrypted; bool calibrated; - uint16_t d_median; - uint8_t retry_counter; + uint16_t d_min; + uint16_t d_max; + uint8_t attempt_count; + MfClassicNestedState nested_state; } MfClassicPollerDictAttackContext; typedef struct { diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7dae5837ae..cc3c45b950 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -901,6 +901,7 @@ Function,+,datetime_get_days_per_year,uint16_t,uint16_t Function,+,datetime_is_leap_year,_Bool,uint16_t Function,+,datetime_timestamp_to_datetime,void,"uint32_t, DateTime*" Function,+,datetime_validate_datetime,_Bool,DateTime* +Function,+,decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -2028,6 +2029,7 @@ Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType Function,-,iprintf,int,"const char*, ..." +Function,+,is_weak_prng_nonce,_Bool,uint32_t Function,-,isalnum,int,int Function,-,isalnum_l,int,"int, locale_t" Function,-,isalpha,int,int @@ -2808,6 +2810,7 @@ Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_even_parity8,uint8_t,uint8_t Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t +Function,+,nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -3527,7 +3530,6 @@ Function,-,ungetc,int,"int, FILE*" Function,-,unsetenv,int,const char* Function,-,usbd_poll,void,usbd_device* Function,-,utoa,char*,"unsigned, char*, int" -Function,+,valid_nonce,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,validator_is_file_alloc_init,ValidatorIsFile*,"const char*, const char*, const char*" Function,+,validator_is_file_callback,_Bool,"const char*, FuriString*, void*" Function,+,validator_is_file_free,void,ValidatorIsFile* From cc8cae770f0e9d1c4eec420a37c764ed8c3eaa15 Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 15 Aug 2024 17:58:37 -0400 Subject: [PATCH 015/236] FM11RF08S backdoor detection --- lib/nfc/protocols/mf_classic/mf_classic.h | 18 ++-- .../protocols/mf_classic/mf_classic_poller.c | 84 ++++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 1 + 3 files changed, 72 insertions(+), 31 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 801ec1764d..53dcf1a540 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -6,14 +6,16 @@ extern "C" { #endif -#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) -#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) -#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) -#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) -#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) -#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) -#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) -#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) +#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) +#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) +#define MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_A (0x64U) +#define MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_B (0x65U) +#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) +#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) +#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) +#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) +#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) +#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) #define MF_CLASSIC_CMD_HALT_MSB (0x50) #define MF_CLASSIC_CMD_HALT_LSB (0x00) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index f96590344b..519958325a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -891,32 +891,69 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan } NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) { - NfcCommand command = NfcCommandContinue; - MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - // TODO: Check for Fudan backdoor // Can use on more than S variant as a free key for Nested - /* + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicNt nt = {}; + MfClassicKey fm11rf08s_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; + MfClassicError error; + Iso14443_3aError iso_error; + bool backdoor_found = false; + do { - uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : - MF_CLASSIC_CMD_AUTH_KEY_A; - uint8_t auth_cmd[2] = {auth_type, block_num}; - bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); - if(is_nested) { - iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); - crypto1_encrypt( - instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); - error = iso14443_3a_poller_txrx_custom_parity( - instance->iso14443_3a_poller, - instance->tx_encrypted_buffer, - instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth - MF_CLASSIC_FWT_FC); - if(error != Iso14443_3aErrorNone) { - ret = mf_classic_process_error(error); - break; - } - */ - dict_attack_ctx->backdoor = MfClassicBackdoorNone; + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + break; + } + + FURI_LOG_E(TAG, "Full authentication successful"); + + // Step 2: Attempt backdoor authentication + uint8_t auth_type = (dict_attack_ctx->current_key_type == MfClassicKeyTypeB) ? + MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_B : + MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_A; + uint8_t auth_cmd[2] = {auth_type, block}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + iso_error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_plain_buffer, + MF_CLASSIC_FWT_FC); + if(iso_error != Iso14443_3aErrorNone) { + FURI_LOG_E(TAG, "Error during nested authentication"); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { + break; + } + bit_buffer_write_bytes(instance->rx_plain_buffer, nt.data, sizeof(MfClassicNt)); + uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); + // Ensure the encrypted nt can be generated by the backdoor + uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, fm11rf08s_backdoor_key); + backdoor_found = is_weak_prng_nonce(decrypted_nt_enc); + } while(false); + if(backdoor_found) { + FURI_LOG_E(TAG, "Backdoor identified"); + dict_attack_ctx->backdoor = MfClassicBackdoorFM11RF08S; + } else { + dict_attack_ctx->backdoor = MfClassicBackdoorNone; + } instance->state = MfClassicPollerStateNestedController; return command; } @@ -1041,6 +1078,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->current_key); + // TODO: Make sure we're not off-by-one here for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); if(nth_successor != decrypted_nt_enc) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index d10401e763..0d797b1dcd 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,6 +3,7 @@ #include "mf_classic_poller.h" #include #include +#include "nfc/helpers/iso14443_crc.h" #include #include #include From 79bc887f951ad190499ff5b2bce5f1caeec40c6e Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 18 Aug 2024 20:38:24 -0400 Subject: [PATCH 016/236] Initial accelerated dictionary attack for weak PRNGs --- applications/main/nfc/nfc_app_i.h | 6 +- .../scenes/nfc_scene_mf_classic_dict_attack.c | 14 ++ .../protocols/mf_classic/mf_classic_poller.c | 170 +++++++++++++++++- .../mf_classic/mf_classic_poller_i.h | 28 ++- 4 files changed, 207 insertions(+), 11 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 295a75a4e7..798c3a5a6c 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -74,8 +74,12 @@ #define NFC_APP_MFKEY32_LOGS_FILE_NAME ".mfkey32.log" #define NFC_APP_MFKEY32_LOGS_FILE_PATH (NFC_APP_FOLDER "/" NFC_APP_MFKEY32_LOGS_FILE_NAME) -#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH \ + (NFC_APP_FOLDER "/assets/mf_classic_dict_user_nested.nfc") #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") +#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \ + (NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc") typedef enum { NfcRpcStateIdle, diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 328e39132f..5aee8ddd85 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -130,6 +130,13 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { break; } + // TODO: Check for errors + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_USER_PATH, + NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + instance->nfc_dict_context.dict = keys_dict_alloc( NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { @@ -142,6 +149,13 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { } while(false); } if(state == DictAttackStateSystemDictInProgress) { + // TODO: Check for errors + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + instance->nfc_dict_context.dict = keys_dict_alloc( NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary"); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 519958325a..23ca4a326d 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1300,11 +1300,125 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst return command; } +MfClassicKey* search_dicts_for_weak_nonce_key( + KeysDict* system_dict, + KeysDict* user_dict, + uint32_t cuid, + uint32_t nt_enc) { + MfClassicKey stack_key; + KeysDict* dicts[] = {system_dict, user_dict}; + + for(int i = 0; i < 2; i++) { + keys_dict_rewind(dicts[i]); + while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { + if(is_weak_prng_nonce(decrypt_nt_enc(cuid, nt_enc, stack_key))) { + MfClassicKey* heap_key = malloc(sizeof(MfClassicKey)); + if(heap_key) { + memcpy(heap_key, &stack_key, sizeof(MfClassicKey)); + return heap_key; + } + return NULL; // malloc failed + } + } + } + + return NULL; // No matching key found +} + NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { - NfcCommand command = NfcCommandContinue; - //MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - // TODO: Nested dictionary attack with ks1 - instance->state = MfClassicPollerStateNestedLog; + // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) + // TODO: Look into using MfClassicNt more + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + do { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { + // TODO: We can do this by collecting enough nonces (e.g. 10 per key) with the parity bits, decrypt and ensure they + // all match against a known key before trying it. + // Not a failed situation + FURI_LOG_E(TAG, "Hard PRNG, skipping"); + break; + } + + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error; + + MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_dict_target_key % 2) == 0) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = (4 * (dict_attack_ctx->nested_dict_target_key / 2)) + 3; + uint8_t parity = 0; + + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + break; + } + + FURI_LOG_E(TAG, "Full authentication successful"); + + // Step 2: Collect nested nt and parity + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->current_key, + target_key_type, + &auth_ctx, + true); + + // TODO: Check error? If there is one, return MfClassicNestedStateFailed + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); + for(int i = 0; i < 4; i++) { + parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + } + MfClassicKey* found_key = search_dicts_for_weak_nonce_key( + dict_attack_ctx->mf_classic_system_dict, + dict_attack_ctx->mf_classic_user_dict, + cuid, + nt_enc); + if(found_key) { + uint64_t k = bit_lib_bytes_to_num_be(found_key->data, sizeof(MfClassicKey)); + FURI_LOG_E(TAG, "Found key %06llx for nt_enc %08lx", k, nt_enc); + // TODO: Add to found keys in dictionary attack struct + free(found_key); + } + + FURI_LOG_E( + TAG, + "Target: %u (key type %s, block %u)", + dict_attack_ctx->nested_target_key, + (target_key_type == MfClassicKeyTypeA) ? "A" : "B", + target_block); + FURI_LOG_E(TAG, "cuid: %08lx", cuid); + FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); + FURI_LOG_E( + TAG, + "parity: %u%u%u%u", + ((parity >> 3) & 1), + ((parity >> 2) & 1), + ((parity >> 1) & 1), + (parity & 1)); + } while(false); + + dict_attack_ctx->nested_state = MfClassicNestedStatePassed; + instance->state = MfClassicPollerStateNestedController; + + mf_classic_poller_halt(instance); return command; } @@ -1407,6 +1521,54 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } + // Accelerated Nested dictionary attack + if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (dict_attack_ctx->nested_dict_target_key <= (instance->sectors_total * 2))) { + if(dict_attack_ctx->nested_dict_target_key == (instance->sectors_total * 2)) { + if(dict_attack_ctx->mf_classic_system_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + } + if(dict_attack_ctx->mf_classic_user_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + } + dict_attack_ctx->nested_dict_target_key++; + instance->state = MfClassicPollerStateNestedController; + return command; + } + if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + dict_attack_ctx->attempt_count++; + } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->nested_state = MfClassicNestedStateNone; + if(dict_attack_ctx->attempt_count >= 3) { + // Unpredictable, skip + FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); + dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->attempt_count = 0; + } + if(dict_attack_ctx->nested_dict_target_key == 0) { + // Note: System dict should always exist + bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); + bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); + if(system_dict_exists) { + dict_attack_ctx->mf_classic_system_dict = keys_dict_alloc( + MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + } + if(user_dict_exists) { + dict_attack_ctx->mf_classic_user_dict = keys_dict_alloc( + MF_CLASSIC_NESTED_USER_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + } + } + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } + // TODO: Skip all remaining phases if we have collected all keys // TODO: Need to think about how this works for Fudan backdoored tags. // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak && !dict_attack_ctx->calibrated) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 0d797b1dcd..f86b843cdb 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -7,15 +7,23 @@ #include #include #include +#include "keys_dict.h" #ifdef __cplusplus extern "C" { #endif -#define MF_CLASSIC_FWT_FC (60000) -#define NFC_FOLDER EXT_PATH("nfc") -#define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" -#define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) +#define MF_CLASSIC_FWT_FC (60000) +#define NFC_FOLDER EXT_PATH("nfc") +#define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") +#define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" +#define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" +#define MF_CLASSIC_NESTED_USER_DICT_FILE_NAME "mf_classic_dict_user_nested.nfc" +#define MF_CLASSIC_NESTED_LOGS_FILE_PATH (NFC_FOLDER "/" MF_CLASSIC_NESTED_LOGS_FILE_NAME) +#define MF_CLASSIC_NESTED_SYSTEM_DICT_PATH \ + (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME) +#define MF_CLASSIC_NESTED_USER_DICT_PATH \ + (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_USER_DICT_FILE_NAME) typedef enum { MfClassicAuthStateIdle, @@ -55,6 +63,11 @@ typedef struct { uint16_t dist; // Distance } MfClassicNestedNonce; +typedef struct { + MfClassicKey* key_candidates; + size_t count; +} MfClassicNestedKeyCandidateArray; + typedef struct { MfClassicNestedNonce* nonces; size_t count; @@ -112,7 +125,7 @@ typedef struct { MfClassicBlock tag_block; } MfClassicPollerWriteContext; -// TODO: Investigate reducing the number of members of this struct +// TODO: Investigate reducing the number of members of this struct by moving into a separate struct dedicated to nested dict attack typedef struct { uint8_t current_sector; MfClassicKey current_key; @@ -120,13 +133,14 @@ typedef struct { bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; - // Enhanced dictionary attack + // Enhanced dictionary attack and nested nonce collection MfClassicPrngType prng_type; MfClassicBackdoor backdoor; uint32_t nt_prev; uint32_t nt_next; uint8_t nt_count; uint8_t hard_nt_count; + uint8_t nested_dict_target_key; uint8_t nested_target_key; MfClassicNestedNonceArray nested_nonce; bool static_encrypted; @@ -135,6 +149,8 @@ typedef struct { uint16_t d_max; uint8_t attempt_count; MfClassicNestedState nested_state; + KeysDict* mf_classic_system_dict; + KeysDict* mf_classic_user_dict; } MfClassicPollerDictAttackContext; typedef struct { From 0af28fb221ee43f9371c264ec2487c27e3465a8e Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 19 Aug 2024 07:33:17 -0400 Subject: [PATCH 017/236] Refactor to nested dictionary attack --- .../protocols/mf_classic/mf_classic_poller.c | 380 ++++++++++++------ .../mf_classic/mf_classic_poller_i.h | 26 +- 2 files changed, 275 insertions(+), 131 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 23ca4a326d..f4c5a6f601 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -681,8 +681,9 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - if(!dict_attack_ctx->prng_type) { - instance->state = MfClassicPollerStateNestedAnalyzePRNG; + // Nested entrypoint + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { + instance->state = MfClassicPollerStateNestedController; break; } @@ -840,13 +841,17 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst static bool add_nested_nonce( MfClassicNestedNonceArray* array, uint32_t cuid, - uint8_t key_idx, + uint16_t key_idx, uint32_t nt, uint32_t nt_enc, uint8_t par, uint16_t dist) { - MfClassicNestedNonce* new_nonces = - realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); + MfClassicNestedNonce* new_nonces; + if(array->count == 0) { + new_nonces = malloc(sizeof(MfClassicNestedNonce)); + } else { + new_nonces = realloc(array->nonces, (array->count + 1) * sizeof(MfClassicNestedNonce)); + } if(new_nonces == NULL) return false; array->nonces = new_nonces; @@ -860,23 +865,34 @@ static bool add_nested_nonce( return true; } +// Helper function to add key candidate to the array +static bool + add_nested_key_candidate(MfClassicNestedKeyCandidateArray* array, MfClassicKey key_candidate) { + MfClassicKey* new_candidates; + if(array->count == 0) { + new_candidates = malloc(sizeof(MfClassicKey)); + } else { + new_candidates = realloc(array->key_candidates, (array->count + 1) * sizeof(MfClassicKey)); + } + if(new_candidates == NULL) return false; + + array->key_candidates = new_candidates; + array->key_candidates[array->count] = key_candidate; + array->count++; + return true; +} + NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + uint8_t hard_nt_count = 0; - // Analyze PRNG by collecting nt - uint8_t nonce_limit = 5; - - if(dict_attack_ctx->nt_count > 0) { - if(!is_weak_prng_nonce(dict_attack_ctx->nt_prev)) dict_attack_ctx->hard_nt_count++; + for(uint8_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { + MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; + if(!is_weak_prng_nonce(nonce->nt)) hard_nt_count++; } - if(dict_attack_ctx->nt_count < nonce_limit) { - instance->state = MfClassicPollerStateNestedCollectNt; - return command; - } - - if(dict_attack_ctx->hard_nt_count >= 3) { + if(hard_nt_count >= MF_CLASSIC_NESTED_HARD_MINIMUM) { dict_attack_ctx->prng_type = MfClassicPrngTypeHard; // FIXME: E -> D FURI_LOG_E(TAG, "Detected Hard PRNG"); @@ -900,7 +916,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in MfClassicAuthContext auth_ctx = {}; MfClassicNt nt = {}; - MfClassicKey fm11rf08s_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; + MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; MfClassicError error; Iso14443_3aError iso_error; bool backdoor_found = false; @@ -945,12 +961,12 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in bit_buffer_write_bytes(instance->rx_plain_buffer, nt.data, sizeof(MfClassicNt)); uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); // Ensure the encrypted nt can be generated by the backdoor - uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, fm11rf08s_backdoor_key); + uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, auth2_backdoor_key); backdoor_found = is_weak_prng_nonce(decrypted_nt_enc); } while(false); if(backdoor_found) { FURI_LOG_E(TAG, "Backdoor identified"); - dict_attack_ctx->backdoor = MfClassicBackdoorFM11RF08S; + dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; } else { dict_attack_ctx->backdoor = MfClassicBackdoorNone; } @@ -966,7 +982,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt); if(error != MfClassicErrorNone) { - instance->state = MfClassicPollerStateKeyReuseStart; dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; // FIXME: E -> D FURI_LOG_E(TAG, "Failed to collect nt"); @@ -974,11 +989,19 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance // FIXME: E -> D FURI_LOG_E(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]); uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); - dict_attack_ctx->nt_prev = nt_data; - dict_attack_ctx->nt_count++; - instance->state = MfClassicPollerStateNestedAnalyzePRNG; + if(!add_nested_nonce( + &dict_attack_ctx->nested_nonce, + iso14443_3a_get_cuid(instance->data->iso14443_3a_data), + 0, + nt_data, + 0, + 0, + 0)) { + dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; + } } + instance->state = MfClassicPollerStateNestedController; return command; } @@ -1057,7 +1080,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { // TODO: Backdoor static nested attack calibration dict_attack_ctx->calibrated = true; instance->state = MfClassicPollerStateNestedController; @@ -1139,7 +1162,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorFM11RF08S) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { // TODO: Backdoor static nested attack with calibrated distance break; } else { @@ -1300,46 +1323,50 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst return command; } -MfClassicKey* search_dicts_for_weak_nonce_key( +static void search_dicts_for_nonce_key( + MfClassicNestedKeyCandidateArray* key_candidates, + MfClassicNestedNonceArray* nonce_array, KeysDict* system_dict, KeysDict* user_dict, - uint32_t cuid, - uint32_t nt_enc) { + bool is_weak) { MfClassicKey stack_key; - KeysDict* dicts[] = {system_dict, user_dict}; + KeysDict* dicts[] = {user_dict, system_dict}; for(int i = 0; i < 2; i++) { keys_dict_rewind(dicts[i]); while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { - if(is_weak_prng_nonce(decrypt_nt_enc(cuid, nt_enc, stack_key))) { - MfClassicKey* heap_key = malloc(sizeof(MfClassicKey)); - if(heap_key) { - memcpy(heap_key, &stack_key, sizeof(MfClassicKey)); - return heap_key; + bool full_match = true; + for(uint8_t j = 0; j < nonce_array->count; j++) { + // Verify nonce matches encrypted parity bits for all nonces + uint32_t nt_enc_plain = decrypt_nt_enc( + nonce_array->nonces[j].cuid, nonce_array->nonces[j].nt_enc, stack_key); + if(is_weak) { + full_match &= is_weak_prng_nonce(nt_enc_plain); + if(!full_match) break; } - return NULL; // malloc failed + full_match &= nonce_matches_encrypted_parity_bits( + nt_enc_plain, + nt_enc_plain ^ nonce_array->nonces[j].nt_enc, + nonce_array->nonces[j].par); + if(!full_match) break; + } + if(full_match && !add_nested_key_candidate(key_candidates, stack_key)) { + return; // malloc failed } } } - return NULL; // No matching key found + return; } NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) // TODO: Look into using MfClassicNt more + // TODO: A method to try the key candidates when we've collected sufficient nonces NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { - // TODO: We can do this by collecting enough nonces (e.g. 10 per key) with the parity bits, decrypt and ensure they - // all match against a known key before trying it. - // Not a failed situation - FURI_LOG_E(TAG, "Hard PRNG, skipping"); - break; - } - uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); @@ -1347,72 +1374,114 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc MfClassicAuthContext auth_ctx = {}; MfClassicError error; - MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_dict_target_key % 2) == 0) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_block = (4 * (dict_attack_ctx->nested_dict_target_key / 2)) + 3; + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + bool is_last_iter_for_hard_key = + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = (is_weak) ? (4 * (dict_attack_ctx->nested_target_key / 2)) + 3 : + (4 * (dict_attack_ctx->nested_target_key / 16)) + 3; uint8_t parity = 0; - // Step 1: Perform full authentication once - error = mf_classic_poller_auth( - instance, - block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, - &auth_ctx); + if(((is_weak) && (dict_attack_ctx->nested_key_candidates.count == 0)) || + ((!is_weak) && (!is_last_iter_for_hard_key))) { + // Step 1: Perform full authentication once + error = mf_classic_poller_auth( + instance, + block, + &dict_attack_ctx->current_key, + dict_attack_ctx->current_key_type, + &auth_ctx); - if(error != MfClassicErrorNone) { - FURI_LOG_E(TAG, "Failed to perform full authentication"); - dict_attack_ctx->nested_state = MfClassicNestedStateFailed; - break; - } + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform full authentication"); + dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + break; + } - FURI_LOG_E(TAG, "Full authentication successful"); + FURI_LOG_E(TAG, "Full authentication successful"); - // Step 2: Collect nested nt and parity - error = mf_classic_poller_auth_nested( - instance, - target_block, - &dict_attack_ctx->current_key, - target_key_type, - &auth_ctx, - true); + // Step 2: Collect nested nt and parity + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->current_key, + target_key_type, + &auth_ctx, + true); - // TODO: Check error? If there is one, return MfClassicNestedStateFailed + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication"); + dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + break; + } - uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); - // Collect parity bits - const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); - for(int i = 0; i < 4; i++) { - parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Collect parity bits + const uint8_t* parity_data = bit_buffer_get_parity(instance->rx_plain_buffer); + for(int i = 0; i < 4; i++) { + parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); + } + + bool success = add_nested_nonce( + &dict_attack_ctx->nested_nonce, + cuid, + dict_attack_ctx->nested_target_key, + 0, + nt_enc, + parity, + 0); + if(!success) { + FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); + dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + break; + } + + if(!is_weak) { + dict_attack_ctx->nested_state = MfClassicNestedStatePassed; + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } } - MfClassicKey* found_key = search_dicts_for_weak_nonce_key( - dict_attack_ctx->mf_classic_system_dict, - dict_attack_ctx->mf_classic_user_dict, - cuid, - nt_enc); - if(found_key) { - uint64_t k = bit_lib_bytes_to_num_be(found_key->data, sizeof(MfClassicKey)); - FURI_LOG_E(TAG, "Found key %06llx for nt_enc %08lx", k, nt_enc); - // TODO: Add to found keys in dictionary attack struct - free(found_key); + // If we have sufficient nonces, search the dictionaries for the key + if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || + (is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) { + // Identify candidate keys (there may be multiple) and validate them to the currently tested sector + // stopping on the first valid key + search_dicts_for_nonce_key( + &dict_attack_ctx->nested_key_candidates, + &dict_attack_ctx->nested_nonce, + dict_attack_ctx->mf_classic_system_dict, + dict_attack_ctx->mf_classic_user_dict, + is_weak); + for(uint8_t i = 0; i < dict_attack_ctx->nested_key_candidates.count; i++) { + FURI_LOG_E( + TAG, + "Found key candidate %06llx", + bit_lib_bytes_to_num_be( + dict_attack_ctx->nested_key_candidates.key_candidates[i].data, + sizeof(MfClassicKey))); + // TODO: Add to found keys in dictionary attack struct ONCE AUTH IS VALIDATED + } + // FIXME + free(dict_attack_ctx->nested_key_candidates.key_candidates); + dict_attack_ctx->nested_key_candidates.key_candidates = NULL; + dict_attack_ctx->nested_key_candidates.count = 0; + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; } FURI_LOG_E( TAG, - "Target: %u (key type %s, block %u)", + "Target: %u (key type %s, block %u) cuid: %08lx", dict_attack_ctx->nested_target_key, (target_key_type == MfClassicKeyTypeA) ? "A" : "B", - target_block); - FURI_LOG_E(TAG, "cuid: %08lx", cuid); - FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); - FURI_LOG_E( - TAG, - "parity: %u%u%u%u", - ((parity >> 3) & 1), - ((parity >> 2) & 1), - ((parity >> 1) & 1), - (parity & 1)); + target_block, + cuid); } while(false); dict_attack_ctx->nested_state = MfClassicNestedStatePassed; @@ -1493,6 +1562,7 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { furi_assert(params_saved); free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; furi_string_free(temp_str); buffered_file_stream_close(stream); @@ -1502,53 +1572,95 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { return command; } +static bool mf_classic_all_keys_collected(const MfClassicData* data) { + uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); + + for(uint8_t sector = 0; sector < total_sectors; sector++) { + if(!mf_classic_is_key_found(data, sector, MfClassicKeyTypeA) || + !mf_classic_is_key_found(data, sector, MfClassicKeyTypeB)) { + return false; + } + } + + return true; +} + NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (dict_attack_ctx->nested_nonce.count == 2)) { - instance->state = MfClassicPollerStateNestedDictAttack; - return command; - } else if( - (dict_attack_ctx->prng_type == MfClassicPrngTypeHard) && - (dict_attack_ctx->nested_nonce.count > 0)) { - // TODO: Need to think about the meaning of nested_target_key for hard PRNG - instance->state = MfClassicPollerStateNestedLog; - return command; + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } - if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { - instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; - return command; + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { + if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { + instance->state = MfClassicPollerStateNestedCollectNt; + return command; + } else if( + (dict_attack_ctx->nested_nonce.count == MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) && + (dict_attack_ctx->prng_type == MfClassicPrngTypeUnknown)) { + instance->state = MfClassicPollerStateNestedAnalyzePRNG; + return command; + } else if(dict_attack_ctx->prng_type == MfClassicPrngTypeNoTag) { + FURI_LOG_E(TAG, "No tag detected"); + // Free nonce array + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + instance->state = MfClassicPollerStateKeyReuseStart; + return command; + } + if(dict_attack_ctx->nested_nonce.nonces) { + // Free nonce array + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } // Accelerated Nested dictionary attack - if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (dict_attack_ctx->nested_dict_target_key <= (instance->sectors_total * 2))) { - if(dict_attack_ctx->nested_dict_target_key == (instance->sectors_total * 2)) { + uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? + (instance->sectors_total * 2) : + (instance->sectors_total * 16); + if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && + (dict_attack_ctx->nested_target_key <= dict_target_key_max)) { + FURI_LOG_E(TAG, "Targeting key %u", dict_attack_ctx->nested_target_key); // DEBUG + if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); } if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); } - dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->nested_target_key = 0; + if(mf_classic_all_keys_collected(instance->data)) { + // TODO: Ensure this works + // All keys have been collected, skip to reading blocks + FURI_LOG_E(TAG, "All keys collected and sectors read"); + dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; + instance->state = MfClassicPollerStateSuccess; + return command; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; instance->state = MfClassicPollerStateNestedController; return command; } if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { dict_attack_ctx->attempt_count++; } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { - dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->nested_state = MfClassicNestedStateNone; if(dict_attack_ctx->attempt_count >= 3) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); - dict_attack_ctx->nested_dict_target_key++; + dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } - if(dict_attack_ctx->nested_dict_target_key == 0) { + if(dict_attack_ctx->nested_target_key == 0) { // Note: System dict should always exist bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); @@ -1568,15 +1680,36 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedDictAttack; return command; } - // TODO: Skip all remaining phases if we have collected all keys - // TODO: Need to think about how this works for Fudan backdoored tags. - // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. - if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak && !dict_attack_ctx->calibrated) { - instance->state = MfClassicPollerStateNestedCalibrate; + // Analyze tag for NXP/Fudan backdoor + if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; + instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } + // TODO: Need to think about how this works for NXP/Fudan backdoored tags. + // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. + // Calibration + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { + if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (!dict_attack_ctx->calibrated)) { + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } else { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } + } + // Log collected nonces + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + if(((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && + (dict_attack_ctx->nested_nonce.count == 2)) || + ((dict_attack_ctx->prng_type == MfClassicPrngTypeHard) && + (dict_attack_ctx->nested_nonce.count > 0))) { + instance->state = MfClassicPollerStateNestedLog; + return command; + } + } // Target all sectors, key A and B, first and second nonce - // TODO: Missing weak condition, target_key logic doesn't apply the same to hard + // TODO: Hardnested nonces logic if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { dict_attack_ctx->attempt_count++; @@ -1585,11 +1718,12 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->nested_state = MfClassicNestedStateNone; - if(dict_attack_ctx->attempt_count >= 20) { + if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); if(dict_attack_ctx->nested_nonce.nonces) { free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; } dict_attack_ctx->nested_target_key += 2; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index f86b843cdb..8ce3e6bfbf 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -16,6 +16,9 @@ extern "C" { #define MF_CLASSIC_FWT_FC (60000) #define NFC_FOLDER EXT_PATH("nfc") #define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") +#define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) +#define MF_CLASSIC_NESTED_HARD_MINIMUM (3) +#define MF_CLASSIC_NESTED_RETRY_MAXIMUM (20) #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" #define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" #define MF_CLASSIC_NESTED_USER_DICT_FILE_NAME "mf_classic_dict_user_nested.nfc" @@ -41,6 +44,16 @@ typedef enum { MfClassicNestedStatePassed, } MfClassicNestedState; +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseAnalyzeBackdoor, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + typedef enum { MfClassicPrngTypeUnknown, // Tag not yet tested MfClassicPrngTypeNoTag, // No tag detected during test @@ -51,7 +64,8 @@ typedef enum { typedef enum { MfClassicBackdoorUnknown, // Tag not yet tested MfClassicBackdoorNone, // No observed backdoor - MfClassicBackdoorFM11RF08S, // Tag responds to Fudan FM11RF08S backdoor (static encrypted nonce tags) + MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) } MfClassicBackdoor; typedef struct { @@ -125,7 +139,6 @@ typedef struct { MfClassicBlock tag_block; } MfClassicPollerWriteContext; -// TODO: Investigate reducing the number of members of this struct by moving into a separate struct dedicated to nested dict attack typedef struct { uint8_t current_sector; MfClassicKey current_key; @@ -134,14 +147,11 @@ typedef struct { uint16_t current_block; uint8_t reuse_key_sector; // Enhanced dictionary attack and nested nonce collection + MfClassicNestedPhase nested_phase; MfClassicPrngType prng_type; MfClassicBackdoor backdoor; - uint32_t nt_prev; - uint32_t nt_next; - uint8_t nt_count; - uint8_t hard_nt_count; - uint8_t nested_dict_target_key; - uint8_t nested_target_key; + uint16_t nested_target_key; + MfClassicNestedKeyCandidateArray nested_key_candidates; MfClassicNestedNonceArray nested_nonce; bool static_encrypted; bool calibrated; From 08ca794b7de61e263d5f7a55d00746af3259081d Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 19 Aug 2024 07:37:21 -0400 Subject: [PATCH 018/236] Renaming some variables --- lib/nfc/protocols/mf_classic/mf_classic.h | 20 +++++++++---------- .../protocols/mf_classic/mf_classic_poller.c | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 53dcf1a540..38c3e9ba7e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -6,16 +6,16 @@ extern "C" { #endif -#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) -#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) -#define MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_A (0x64U) -#define MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_B (0x65U) -#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) -#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) -#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) -#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) -#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) -#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) +#define MF_CLASSIC_CMD_AUTH_KEY_A (0x60U) +#define MF_CLASSIC_CMD_AUTH_KEY_B (0x61U) +#define MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A (0x64U) +#define MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B (0x65U) +#define MF_CLASSIC_CMD_READ_BLOCK (0x30U) +#define MF_CLASSIC_CMD_WRITE_BLOCK (0xA0U) +#define MF_CLASSIC_CMD_VALUE_DEC (0xC0U) +#define MF_CLASSIC_CMD_VALUE_INC (0xC1U) +#define MF_CLASSIC_CMD_VALUE_RESTORE (0xC2U) +#define MF_CLASSIC_CMD_VALUE_TRANSFER (0xB0U) #define MF_CLASSIC_CMD_HALT_MSB (0x50) #define MF_CLASSIC_CMD_HALT_LSB (0x00) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index f4c5a6f601..6997a3e8f6 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -939,8 +939,8 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in // Step 2: Attempt backdoor authentication uint8_t auth_type = (dict_attack_ctx->current_key_type == MfClassicKeyTypeB) ? - MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_B : - MF_CLASSIC_CMD_SE_BACKDOOR_AUTH_KEY_A; + MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : + MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; uint8_t auth_cmd[2] = {auth_type, block}; bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); From b7e63bf499ca76a33fd5b3879d81897acccdcdff Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 19 Aug 2024 23:00:36 -0400 Subject: [PATCH 019/236] Hard PRNG support for accelerated dictionary attack --- .../protocols/mf_classic/mf_classic_poller.c | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 6997a3e8f6..bd8f7cf2cf 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1387,7 +1387,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc uint8_t parity = 0; if(((is_weak) && (dict_attack_ctx->nested_key_candidates.count == 0)) || - ((!is_weak) && (!is_last_iter_for_hard_key))) { + ((!is_weak) && (dict_attack_ctx->nested_key_candidates.count < 8))) { // Step 1: Perform full authentication once error = mf_classic_poller_auth( instance, @@ -1440,11 +1440,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc break; } - if(!is_weak) { - dict_attack_ctx->nested_state = MfClassicNestedStatePassed; - instance->state = MfClassicPollerStateNestedDictAttack; - return command; - } + dict_attack_ctx->nested_state = MfClassicNestedStatePassed; } // If we have sufficient nonces, search the dictionaries for the key if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || @@ -1484,7 +1480,6 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc cuid); } while(false); - dict_attack_ctx->nested_state = MfClassicNestedStatePassed; instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); @@ -1625,8 +1620,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && - (dict_attack_ctx->nested_target_key <= dict_target_key_max)) { - FURI_LOG_E(TAG, "Targeting key %u", dict_attack_ctx->nested_target_key); // DEBUG + (dict_attack_ctx->nested_target_key < dict_target_key_max)) { + if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + dict_attack_ctx->attempt_count++; + } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + } + dict_attack_ctx->nested_state = MfClassicNestedStateNone; if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); @@ -1647,13 +1648,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedController; return command; } - if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { - dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->attempt_count = 0; - } - dict_attack_ctx->nested_state = MfClassicNestedStateNone; if(dict_attack_ctx->attempt_count >= 3) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); From bbc10cdfafc40747aef5bc218f358ae43bab890d Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 20 Aug 2024 16:31:39 -0400 Subject: [PATCH 020/236] Update found keys, initial attempt --- .../protocols/mf_classic/mf_classic_poller.c | 225 ++++++++++-------- .../mf_classic/mf_classic_poller_i.h | 22 +- 2 files changed, 135 insertions(+), 112 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index bd8f7cf2cf..ef1af4c56d 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -681,12 +681,6 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - // Nested entrypoint - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { - instance->state = MfClassicPollerStateNestedController; - break; - } - if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { dict_attack_ctx->current_key_type = MfClassicKeyTypeB; instance->state = MfClassicPollerStateKeyReuseAuthKeyB; @@ -695,6 +689,12 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) if(dict_attack_ctx->reuse_key_sector == instance->sectors_total) { instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; command = instance->callback(instance->general_event, instance->context); + // Nested entrypoint + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || + dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished) { + instance->state = MfClassicPollerStateNestedController; + break; + } instance->state = MfClassicPollerStateRequestKey; } else { instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; @@ -729,6 +729,9 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + dict_attack_ctx->reuse_success = true; + } mf_classic_set_key_found( instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key); @@ -765,6 +768,9 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + dict_attack_ctx->reuse_success = true; + } mf_classic_set_key_found( instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key); @@ -865,23 +871,6 @@ static bool add_nested_nonce( return true; } -// Helper function to add key candidate to the array -static bool - add_nested_key_candidate(MfClassicNestedKeyCandidateArray* array, MfClassicKey key_candidate) { - MfClassicKey* new_candidates; - if(array->count == 0) { - new_candidates = malloc(sizeof(MfClassicKey)); - } else { - new_candidates = realloc(array->key_candidates, (array->count + 1) * sizeof(MfClassicKey)); - } - if(new_candidates == NULL) return false; - - array->key_candidates = new_candidates; - array->key_candidates[array->count] = key_candidate; - array->count++; - return true; -} - NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -911,7 +900,8 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); MfClassicAuthContext auth_ctx = {}; @@ -926,8 +916,8 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in error = mf_classic_poller_auth( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx); if(error != MfClassicErrorNone) { @@ -938,7 +928,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in FURI_LOG_E(TAG, "Full authentication successful"); // Step 2: Attempt backdoor authentication - uint8_t auth_type = (dict_attack_ctx->current_key_type == MfClassicKeyTypeB) ? + uint8_t auth_type = (dict_attack_ctx->nested_known_key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; uint8_t auth_cmd[2] = {auth_type, block}; @@ -1015,7 +1005,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) dict_attack_ctx->d_min = UINT16_MAX; dict_attack_ctx->d_max = 0; - uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + uint8_t block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); MfClassicAuthContext auth_ctx = {}; @@ -1030,8 +1021,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) error = mf_classic_poller_auth( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx); if(error != MfClassicErrorNone) { @@ -1050,8 +1041,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) error = mf_classic_poller_auth_nested( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx, false); @@ -1095,12 +1086,12 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); - uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, 6); + uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); for(uint32_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; collection_cycle++) { bool found = false; - uint32_t decrypted_nt_enc = - decrypt_nt_enc(cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->current_key); + uint32_t decrypted_nt_enc = decrypt_nt_enc( + cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); // TODO: Make sure we're not off-by-one here for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); @@ -1171,7 +1162,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst bool success = add_nested_nonce( &result, cuid, - dict_attack_ctx->reuse_key_sector, + dict_attack_ctx->nested_known_key_sector, nt_prev, nt_enc_prev, parity, @@ -1185,7 +1176,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } uint8_t block = - mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); MfClassicAuthContext auth_ctx = {}; @@ -1196,6 +1187,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; + // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; uint32_t nt_enc_temp_arr[nt_enc_per_collection]; uint8_t nt_enc_collected = 0; @@ -1205,8 +1197,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst error = mf_classic_poller_auth( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx); if(error != MfClassicErrorNone) { @@ -1226,8 +1218,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst error = mf_classic_poller_auth_nested( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx, false); @@ -1243,7 +1235,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst error = mf_classic_poller_auth_nested( instance, target_block, - &dict_attack_ctx->current_key, + &dict_attack_ctx->nested_known_key, target_key_type, &auth_ctx, true); @@ -1261,7 +1253,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } // Decrypt the previous nonce uint32_t nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - uint32_t decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->current_key); + uint32_t decrypted_nt_prev = + decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); // Find matching nt_enc plain at expected distance uint32_t found_nt = 0; @@ -1292,7 +1285,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst nt_enc, parity, 0)) { - dict_attack_ctx->nested_state = MfClassicNestedStatePassed; + dict_attack_ctx->auth_passed = true; } else { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } @@ -1323,18 +1316,27 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst return command; } -static void search_dicts_for_nonce_key( - MfClassicNestedKeyCandidateArray* key_candidates, +static MfClassicKey* search_dicts_for_nonce_key( + MfClassicPollerDictAttackContext* dict_attack_ctx, MfClassicNestedNonceArray* nonce_array, KeysDict* system_dict, KeysDict* user_dict, bool is_weak) { MfClassicKey stack_key; KeysDict* dicts[] = {user_dict, system_dict}; + bool is_resumed = dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume; + bool found_resume_point = false; for(int i = 0; i < 2; i++) { keys_dict_rewind(dicts[i]); while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { + if(is_resumed && !(found_resume_point) && + memcmp(dict_attack_ctx->current_key.data, stack_key.data, sizeof(MfClassicKey)) == + 0) { + found_resume_point = true; + } else { + continue; + } bool full_match = true; for(uint8_t j = 0; j < nonce_array->count; j++) { // Verify nonce matches encrypted parity bits for all nonces @@ -1350,25 +1352,27 @@ static void search_dicts_for_nonce_key( nonce_array->nonces[j].par); if(!full_match) break; } - if(full_match && !add_nested_key_candidate(key_candidates, stack_key)) { - return; // malloc failed + if(full_match) { + MfClassicKey* new_candidate = malloc(sizeof(MfClassicKey)); + if(new_candidate == NULL) return NULL; // malloc failed + memcpy(new_candidate, &stack_key, sizeof(MfClassicKey)); + return new_candidate; } } } - return; + return NULL; } NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) // TODO: Look into using MfClassicNt more - // TODO: A method to try the key candidates when we've collected sufficient nonces NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { uint8_t block = - mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); MfClassicAuthContext auth_ctx = {}; @@ -1382,23 +1386,24 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? MfClassicKeyTypeA : MfClassicKeyTypeB; + // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? uint8_t target_block = (is_weak) ? (4 * (dict_attack_ctx->nested_target_key / 2)) + 3 : (4 * (dict_attack_ctx->nested_target_key / 16)) + 3; uint8_t parity = 0; - if(((is_weak) && (dict_attack_ctx->nested_key_candidates.count == 0)) || - ((!is_weak) && (dict_attack_ctx->nested_key_candidates.count < 8))) { + if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 0)) || + ((!is_weak) && (dict_attack_ctx->nested_nonce.count < 8))) { // Step 1: Perform full authentication once error = mf_classic_poller_auth( instance, block, - &dict_attack_ctx->current_key, - dict_attack_ctx->current_key_type, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, &auth_ctx); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); - dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + dict_attack_ctx->auth_passed = false; break; } @@ -1408,14 +1413,14 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc error = mf_classic_poller_auth_nested( instance, target_block, - &dict_attack_ctx->current_key, + &dict_attack_ctx->nested_known_key, target_key_type, &auth_ctx, true); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform nested authentication"); - dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + dict_attack_ctx->auth_passed = false; break; } @@ -1436,39 +1441,36 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc 0); if(!success) { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); - dict_attack_ctx->nested_state = MfClassicNestedStateFailed; + dict_attack_ctx->auth_passed = false; break; } - dict_attack_ctx->nested_state = MfClassicNestedStatePassed; + dict_attack_ctx->auth_passed = true; } // If we have sufficient nonces, search the dictionaries for the key if((is_weak && (dict_attack_ctx->nested_nonce.count == 1)) || (is_last_iter_for_hard_key && (dict_attack_ctx->nested_nonce.count == 8))) { - // Identify candidate keys (there may be multiple) and validate them to the currently tested sector - // stopping on the first valid key - search_dicts_for_nonce_key( - &dict_attack_ctx->nested_key_candidates, + // Identify key candidates + MfClassicKey* key_candidate = search_dicts_for_nonce_key( + dict_attack_ctx, &dict_attack_ctx->nested_nonce, dict_attack_ctx->mf_classic_system_dict, dict_attack_ctx->mf_classic_user_dict, is_weak); - for(uint8_t i = 0; i < dict_attack_ctx->nested_key_candidates.count; i++) { + if(key_candidate != NULL) { FURI_LOG_E( TAG, "Found key candidate %06llx", - bit_lib_bytes_to_num_be( - dict_attack_ctx->nested_key_candidates.key_candidates[i].data, - sizeof(MfClassicKey))); - // TODO: Add to found keys in dictionary attack struct ONCE AUTH IS VALIDATED + bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); + dict_attack_ctx->current_key = *key_candidate; + dict_attack_ctx->reuse_key_sector = (target_block / 4); + free(key_candidate); + break; + } else { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; } - // FIXME - free(dict_attack_ctx->nested_key_candidates.key_candidates); - dict_attack_ctx->nested_key_candidates.key_candidates = NULL; - dict_attack_ctx->nested_key_candidates.count = 0; - free(dict_attack_ctx->nested_nonce.nonces); - dict_attack_ctx->nested_nonce.nonces = NULL; - dict_attack_ctx->nested_nonce.count = 0; } FURI_LOG_E( @@ -1567,24 +1569,15 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { return command; } -static bool mf_classic_all_keys_collected(const MfClassicData* data) { - uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); - - for(uint8_t sector = 0; sector < total_sectors; sector++) { - if(!mf_classic_is_key_found(data, sector, MfClassicKeyTypeA) || - !mf_classic_is_key_found(data, sector, MfClassicKeyTypeB)) { - return false; - } - } - - return true; -} - NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { + dict_attack_ctx->auth_passed = true; + dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; + dict_attack_ctx->nested_known_key_type = dict_attack_ctx->current_key_type; + dict_attack_ctx->nested_known_key_sector = dict_attack_ctx->reuse_key_sector; dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { @@ -1604,7 +1597,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; } - instance->state = MfClassicPollerStateKeyReuseStart; + instance->state = MfClassicPollerStateFail; return command; } if(dict_attack_ctx->nested_nonce.nonces) { @@ -1619,15 +1612,39 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? (instance->sectors_total * 2) : (instance->sectors_total * 16); + if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { + if(!(dict_attack_ctx->reuse_success)) { + instance->state = MfClassicPollerStateNestedDictAttack; + return command; + } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + } if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && (dict_attack_ctx->nested_target_key < dict_target_key_max)) { - if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + bool is_last_iter_for_hard_key = + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); + if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { + // Key reuse + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; + dict_attack_ctx->auth_passed = false; + instance->state = MfClassicPollerStateKeyReuseStart; + return command; + } + if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + } else if(dict_attack_ctx->auth_passed || dict_attack_ctx->reuse_success) { + if(dict_attack_ctx->reuse_success) { + furi_assert(dict_attack_ctx->nested_nonce.nonces); + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + dict_attack_ctx->reuse_success = false; + } dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } - dict_attack_ctx->nested_state = MfClassicNestedStateNone; + dict_attack_ctx->auth_passed = true; if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); @@ -1636,7 +1653,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance keys_dict_free(dict_attack_ctx->mf_classic_user_dict); } dict_attack_ctx->nested_target_key = 0; - if(mf_classic_all_keys_collected(instance->data)) { + if(mf_classic_is_card_read(instance->data)) { // TODO: Ensure this works // All keys have been collected, skip to reading blocks FURI_LOG_E(TAG, "All keys collected and sectors read"); @@ -1648,6 +1665,20 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedController; return command; } + // Check if the nested target key is a known key + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : + (dict_attack_ctx->nested_target_key / 16); + if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + instance->state = MfClassicPollerStateNestedController; + return command; + } if(dict_attack_ctx->attempt_count >= 3) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); @@ -1705,13 +1736,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Target all sectors, key A and B, first and second nonce // TODO: Hardnested nonces logic if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { - if(dict_attack_ctx->nested_state == MfClassicNestedStateFailed) { + if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->nested_state == MfClassicNestedStatePassed) { + } else { dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } - dict_attack_ctx->nested_state = MfClassicNestedStateNone; + dict_attack_ctx->auth_passed = true; if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 8ce3e6bfbf..2331019047 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -38,16 +38,11 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; -typedef enum { - MfClassicNestedStateNone, - MfClassicNestedStateFailed, - MfClassicNestedStatePassed, -} MfClassicNestedState; - typedef enum { MfClassicNestedPhaseNone, MfClassicNestedPhaseAnalyzePRNG, MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseAnalyzeBackdoor, MfClassicNestedPhaseCalibrate, MfClassicNestedPhaseCollectNtEnc, @@ -77,11 +72,6 @@ typedef struct { uint16_t dist; // Distance } MfClassicNestedNonce; -typedef struct { - MfClassicKey* key_candidates; - size_t count; -} MfClassicNestedKeyCandidateArray; - typedef struct { MfClassicNestedNonce* nonces; size_t count; @@ -144,21 +134,23 @@ typedef struct { MfClassicKey current_key; MfClassicKeyType current_key_type; bool auth_passed; + bool reuse_success; uint16_t current_block; uint8_t reuse_key_sector; // Enhanced dictionary attack and nested nonce collection MfClassicNestedPhase nested_phase; - MfClassicPrngType prng_type; - MfClassicBackdoor backdoor; + MfClassicKey nested_known_key; + MfClassicKeyType nested_known_key_type; + uint8_t nested_known_key_sector; uint16_t nested_target_key; - MfClassicNestedKeyCandidateArray nested_key_candidates; MfClassicNestedNonceArray nested_nonce; + MfClassicPrngType prng_type; + MfClassicBackdoor backdoor; bool static_encrypted; bool calibrated; uint16_t d_min; uint16_t d_max; uint8_t attempt_count; - MfClassicNestedState nested_state; KeysDict* mf_classic_system_dict; KeysDict* mf_classic_user_dict; } MfClassicPollerDictAttackContext; From 75a0e4bc9dbfa902b0b867078782f858f89172df Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 20 Aug 2024 16:33:13 -0400 Subject: [PATCH 021/236] Update found keys, second attempt --- .../protocols/mf_classic/mf_classic_poller.c | 68 ++++++++++--------- .../mf_classic/mf_classic_poller_i.h | 1 - 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index ef1af4c56d..e0886aff83 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -729,9 +729,6 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - dict_attack_ctx->reuse_success = true; - } mf_classic_set_key_found( instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeA, key); @@ -768,9 +765,6 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - dict_attack_ctx->reuse_success = true; - } mf_classic_set_key_found( instance->data, dict_attack_ctx->reuse_key_sector, MfClassicKeyTypeB, key); @@ -1613,11 +1607,25 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - if(!(dict_attack_ctx->reuse_success)) { + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : + (dict_attack_ctx->nested_target_key / 16); + if(!(mf_classic_is_key_found(instance->data, target_sector, target_key_type))) { instance->state = MfClassicPollerStateNestedDictAttack; return command; + } else { + dict_attack_ctx->auth_passed = true; + furi_assert(dict_attack_ctx->nested_nonce.nonces); + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } - dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && (dict_attack_ctx->nested_target_key < dict_target_key_max)) { @@ -1633,14 +1641,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->auth_passed || dict_attack_ctx->reuse_success) { - if(dict_attack_ctx->reuse_success) { - furi_assert(dict_attack_ctx->nested_nonce.nonces); - free(dict_attack_ctx->nested_nonce.nonces); - dict_attack_ctx->nested_nonce.nonces = NULL; - dict_attack_ctx->nested_nonce.count = 0; - dict_attack_ctx->reuse_success = false; - } + } else if(dict_attack_ctx->auth_passed) { dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } @@ -1654,8 +1655,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { - // TODO: Ensure this works - // All keys have been collected, skip to reading blocks + // All keys have been collected FURI_LOG_E(TAG, "All keys collected and sectors read"); dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; @@ -1665,19 +1665,21 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedController; return command; } - // Check if the nested target key is a known key - MfClassicKeyType target_key_type = - (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || - ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : - (dict_attack_ctx->nested_target_key / 16); - if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->attempt_count = 0; - instance->state = MfClassicPollerStateNestedController; - return command; + if(dict_attack_ctx->attempt_count == 0) { + // Check if the nested target key is a known key + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : + (dict_attack_ctx->nested_target_key / 16); + if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; + instance->state = MfClassicPollerStateNestedController; + return command; + } } if(dict_attack_ctx->attempt_count >= 3) { // Unpredictable, skip @@ -1757,8 +1759,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; } - // TODO: If we've recovered all keys, read blocks and go to complete - instance->state = MfClassicPollerStateKeyReuseStart; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; + instance->state = MfClassicPollerStateSuccess; return command; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 2331019047..d9d1d31cc2 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -134,7 +134,6 @@ typedef struct { MfClassicKey current_key; MfClassicKeyType current_key_type; bool auth_passed; - bool reuse_success; uint16_t current_block; uint8_t reuse_key_sector; // Enhanced dictionary attack and nested nonce collection From c1f01ce66af9f09c4a2c7b7fb84956157c8cd1ce Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 20 Aug 2024 18:04:07 -0400 Subject: [PATCH 022/236] Code cleanup --- .../protocols/mf_classic/mf_classic_poller.c | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index e0886aff83..8a874b0869 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1574,6 +1574,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_known_key_sector = dict_attack_ctx->reuse_key_sector; dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } + bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { instance->state = MfClassicPollerStateNestedCollectNt; @@ -1601,13 +1602,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_nonce.count = 0; } dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + initial_dict_attack_iter = true; } - // Accelerated Nested dictionary attack + // Accelerated nested dictionary attack + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; uint16_t dict_target_key_max = (dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) ? (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; MfClassicKeyType target_key_type = (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? @@ -1629,9 +1631,16 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if((dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttack) && (dict_attack_ctx->nested_target_key < dict_target_key_max)) { - bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; bool is_last_iter_for_hard_key = ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); + if(initial_dict_attack_iter) { + uint8_t nested_target_key_offset = + (dict_attack_ctx->current_key_type == MfClassicKeyTypeA) ? 0 : 1; + dict_attack_ctx->nested_target_key = + (is_weak) ? + ((dict_attack_ctx->current_sector * 2) + nested_target_key_offset) : + ((dict_attack_ctx->current_sector * 16) + (nested_target_key_offset * 8)); + } if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { // Key reuse dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; @@ -1641,7 +1650,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; - } else if(dict_attack_ctx->auth_passed) { + } else if(dict_attack_ctx->auth_passed && !(initial_dict_attack_iter)) { dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } @@ -1725,39 +1734,37 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } } - // Log collected nonces + // Collect and log nonces if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - if(((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (dict_attack_ctx->nested_nonce.count == 2)) || - ((dict_attack_ctx->prng_type == MfClassicPrngTypeHard) && - (dict_attack_ctx->nested_nonce.count > 0))) { + if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || + ((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) { instance->state = MfClassicPollerStateNestedLog; return command; } - } - // Target all sectors, key A and B, first and second nonce - // TODO: Hardnested nonces logic - if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { - if(!(dict_attack_ctx->auth_passed)) { - dict_attack_ctx->attempt_count++; - } else { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->attempt_count = 0; - } - dict_attack_ctx->auth_passed = true; - if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { - // Unpredictable, skip - FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); - if(dict_attack_ctx->nested_nonce.nonces) { - free(dict_attack_ctx->nested_nonce.nonces); - dict_attack_ctx->nested_nonce.nonces = NULL; - dict_attack_ctx->nested_nonce.count = 0; + // Target all sectors, key A and B, first and second nonce + // TODO: Hardnested nonces logic + if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { + if(!(dict_attack_ctx->auth_passed)) { + dict_attack_ctx->attempt_count++; + } else { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->attempt_count = 0; } - dict_attack_ctx->nested_target_key += 2; - dict_attack_ctx->attempt_count = 0; + dict_attack_ctx->auth_passed = true; + if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { + // Unpredictable, skip + FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + dict_attack_ctx->nested_target_key += 2; + dict_attack_ctx->attempt_count = 0; + } + instance->state = MfClassicPollerStateNestedCollectNtEnc; + return command; } - instance->state = MfClassicPollerStateNestedCollectNtEnc; - return command; } dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; From 26845cbdc5b4a7844495b48e130bd4313b915c08 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 21 Aug 2024 00:50:27 -0400 Subject: [PATCH 023/236] Misc bugfixes --- .../protocols/mf_classic/mf_classic_poller.c | 85 ++++++++++++------- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 8a874b0869..025046e354 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -711,6 +711,22 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) return command; } +NfcCommand mf_classic_poller_handler_key_reuse_start_no_offset(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStart; + instance->mfc_event_data.key_attack_data.current_sector = dict_attack_ctx->reuse_key_sector; + command = instance->callback(instance->general_event, instance->context); + if(dict_attack_ctx->current_key_type == MfClassicKeyTypeA) { + instance->state = MfClassicPollerStateKeyReuseAuthKeyA; + } else { + instance->state = MfClassicPollerStateKeyReuseAuthKeyB; + } + + return command; +} + NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -1324,11 +1340,12 @@ static MfClassicKey* search_dicts_for_nonce_key( for(int i = 0; i < 2; i++) { keys_dict_rewind(dicts[i]); while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { - if(is_resumed && !(found_resume_point) && - memcmp(dict_attack_ctx->current_key.data, stack_key.data, sizeof(MfClassicKey)) == - 0) { - found_resume_point = true; - } else { + if(is_resumed && !found_resume_point) { + found_resume_point = + (memcmp( + dict_attack_ctx->current_key.data, + stack_key.data, + sizeof(MfClassicKey)) == 0); continue; } bool full_match = true; @@ -1458,6 +1475,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); dict_attack_ctx->current_key = *key_candidate; dict_attack_ctx->reuse_key_sector = (target_block / 4); + dict_attack_ctx->current_key_type = target_key_type; free(key_candidate); break; } else { @@ -1570,8 +1588,15 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { dict_attack_ctx->auth_passed = true; dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; - dict_attack_ctx->nested_known_key_type = dict_attack_ctx->current_key_type; - dict_attack_ctx->nested_known_key_sector = dict_attack_ctx->reuse_key_sector; + for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { + for(uint8_t key_type = 0; key_type < 2; key_type++) { + if(mf_classic_is_key_found(instance->data, sector, key_type)) { + dict_attack_ctx->nested_known_key_sector = sector; + dict_attack_ctx->nested_known_key_type = key_type; + break; + } + } + } dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } bool initial_dict_attack_iter = false; @@ -1634,18 +1659,28 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance bool is_last_iter_for_hard_key = ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); if(initial_dict_attack_iter) { - uint8_t nested_target_key_offset = - (dict_attack_ctx->current_key_type == MfClassicKeyTypeA) ? 0 : 1; - dict_attack_ctx->nested_target_key = - (is_weak) ? - ((dict_attack_ctx->current_sector * 2) + nested_target_key_offset) : - ((dict_attack_ctx->current_sector * 16) + (nested_target_key_offset * 8)); + // Initialize dictionaries + // Note: System dict should always exist + bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); + bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); + if(system_dict_exists) { + dict_attack_ctx->mf_classic_system_dict = keys_dict_alloc( + MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + } + if(user_dict_exists) { + dict_attack_ctx->mf_classic_user_dict = keys_dict_alloc( + MF_CLASSIC_NESTED_USER_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + } } if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { // Key reuse dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttackResume; dict_attack_ctx->auth_passed = false; - instance->state = MfClassicPollerStateKeyReuseStart; + instance->state = MfClassicPollerStateKeyReuseStartNoOffset; return command; } if(!(dict_attack_ctx->auth_passed)) { @@ -1684,8 +1719,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : (dict_attack_ctx->nested_target_key / 16); if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { - dict_attack_ctx->nested_target_key++; - dict_attack_ctx->attempt_count = 0; + // Continue to next key instance->state = MfClassicPollerStateNestedController; return command; } @@ -1696,23 +1730,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_target_key++; dict_attack_ctx->attempt_count = 0; } - if(dict_attack_ctx->nested_target_key == 0) { - // Note: System dict should always exist - bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); - bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); - if(system_dict_exists) { - dict_attack_ctx->mf_classic_system_dict = keys_dict_alloc( - MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); - } - if(user_dict_exists) { - dict_attack_ctx->mf_classic_user_dict = keys_dict_alloc( - MF_CLASSIC_NESTED_USER_DICT_PATH, - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); - } - } instance->state = MfClassicPollerStateNestedDictAttack; return command; } @@ -1807,6 +1824,8 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateAuthKeyB] = mf_classic_poller_handler_auth_b, [MfClassicPollerStateReadSector] = mf_classic_poller_handler_read_sector, [MfClassicPollerStateKeyReuseStart] = mf_classic_poller_handler_key_reuse_start, + [MfClassicPollerStateKeyReuseStartNoOffset] = + mf_classic_poller_handler_key_reuse_start_no_offset, [MfClassicPollerStateKeyReuseAuthKeyA] = mf_classic_poller_handler_key_reuse_auth_key_a, [MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b, [MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector, diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index d9d1d31cc2..ab6426b382 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -99,6 +99,7 @@ typedef enum { MfClassicPollerStateAuthKeyA, MfClassicPollerStateAuthKeyB, MfClassicPollerStateKeyReuseStart, + MfClassicPollerStateKeyReuseStartNoOffset, MfClassicPollerStateKeyReuseAuthKeyA, MfClassicPollerStateKeyReuseAuthKeyB, MfClassicPollerStateKeyReuseReadSector, From 523559205487cf86de5a280d1651c66361965662 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 27 Aug 2024 06:32:00 -0400 Subject: [PATCH 024/236] Only use dicts in search_dicts_for_nonce_key if we have them --- .../protocols/mf_classic/mf_classic_poller.c | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 025046e354..c670ed9ed0 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1338,6 +1338,7 @@ static MfClassicKey* search_dicts_for_nonce_key( bool found_resume_point = false; for(int i = 0; i < 2; i++) { + if(!dicts[i]) continue; keys_dict_rewind(dicts[i]); while(keys_dict_get_next_key(dicts[i], stack_key.data, sizeof(MfClassicKey))) { if(is_resumed && !found_resume_point) { @@ -1661,20 +1662,21 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(initial_dict_attack_iter) { // Initialize dictionaries // Note: System dict should always exist - bool system_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH); - bool user_dict_exists = keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH); - if(system_dict_exists) { - dict_attack_ctx->mf_classic_system_dict = keys_dict_alloc( - MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); - } - if(user_dict_exists) { - dict_attack_ctx->mf_classic_user_dict = keys_dict_alloc( - MF_CLASSIC_NESTED_USER_DICT_PATH, - KeysDictModeOpenExisting, - sizeof(MfClassicKey)); - } + dict_attack_ctx->mf_classic_system_dict = + keys_dict_check_presence(MF_CLASSIC_NESTED_SYSTEM_DICT_PATH) ? + keys_dict_alloc( + MF_CLASSIC_NESTED_SYSTEM_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)) : + NULL; + + dict_attack_ctx->mf_classic_user_dict = + keys_dict_check_presence(MF_CLASSIC_NESTED_USER_DICT_PATH) ? + keys_dict_alloc( + MF_CLASSIC_NESTED_USER_DICT_PATH, + KeysDictModeOpenExisting, + sizeof(MfClassicKey)) : + NULL; } if((is_weak || is_last_iter_for_hard_key) && dict_attack_ctx->nested_nonce.count > 0) { // Key reuse From 0b33c85b8b6752dc4913cc43b96e358f659ab799 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 27 Aug 2024 16:16:38 -0400 Subject: [PATCH 025/236] Collect nonces again --- .../protocols/mf_classic/mf_classic_poller.c | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index c670ed9ed0..8b690cf1b3 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1582,6 +1582,22 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { return command; } +bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance) { + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + MfClassicKeyType target_key_type = + (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || + ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : + (dict_attack_ctx->nested_target_key / 16); + if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { + return true; + } + return false; +} + NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys NfcCommand command = NfcCommandContinue; @@ -1636,14 +1652,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - MfClassicKeyType target_key_type = - (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || - ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : - (dict_attack_ctx->nested_target_key / 16); - if(!(mf_classic_is_key_found(instance->data, target_sector, target_key_type))) { + if(!(mf_classic_nested_is_target_key_found(instance))) { instance->state = MfClassicPollerStateNestedDictAttack; return command; } else { @@ -1707,20 +1716,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateSuccess; return command; } - dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; instance->state = MfClassicPollerStateNestedController; return command; } if(dict_attack_ctx->attempt_count == 0) { // Check if the nested target key is a known key - MfClassicKeyType target_key_type = - (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || - ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : - (dict_attack_ctx->nested_target_key / 16); - if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { + if(mf_classic_nested_is_target_key_found(instance)) { // Continue to next key instance->state = MfClassicPollerStateNestedController; return command; @@ -1737,21 +1739,22 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Analyze tag for NXP/Fudan backdoor if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { - dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzeBackdoor) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; } // TODO: Need to think about how this works for NXP/Fudan backdoored tags. // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. // Calibration - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { - if((dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) && - (!dict_attack_ctx->calibrated)) { + if(!(dict_attack_ctx->calibrated)) { + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedCalibrate; return command; - } else { - dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { @@ -1760,9 +1763,9 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedLog; return command; } - // Target all sectors, key A and B, first and second nonce - // TODO: Hardnested nonces logic - if(dict_attack_ctx->nested_target_key < (instance->sectors_total * 4)) { + // Target all remaining sectors, key A and B, first and second nonce + // TODO: Hardnested nonces logic, will likely be similar to dict attack has dict_target_key_max + if(is_weak && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 4))) { if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; } else { @@ -1770,6 +1773,14 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; + if(dict_attack_ctx->attempt_count == 0) { + // Check if the nested target key is a known key + if(mf_classic_nested_is_target_key_found(instance)) { + // Continue to next key + instance->state = MfClassicPollerStateNestedController; + return command; + } + } if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); From c0331ba2e262bf1bedd9714a0133835d5d3f03b5 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 27 Aug 2024 19:35:33 -0400 Subject: [PATCH 026/236] Should be detecting both backdoors now --- .../protocols/mf_classic/mf_classic_poller.c | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 8b690cf1b3..04d92b4010 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -916,7 +916,14 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in MfClassicAuthContext auth_ctx = {}; MfClassicNt nt = {}; - MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; + MfClassicKey auth_backdoor_key; + if(dict_attack_ctx->nested_target_key == 1) { + auth_backdoor_key = (MfClassicKey){ + .data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; // auth2 backdoor key, more common + } else { + auth_backdoor_key = + (MfClassicKey){.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; // auth1 backdoor key + } MfClassicError error; Iso14443_3aError iso_error; bool backdoor_found = false; @@ -961,13 +968,17 @@ NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* in bit_buffer_write_bytes(instance->rx_plain_buffer, nt.data, sizeof(MfClassicNt)); uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); // Ensure the encrypted nt can be generated by the backdoor - uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, auth2_backdoor_key); + uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, auth_backdoor_key); backdoor_found = is_weak_prng_nonce(decrypted_nt_enc); } while(false); if(backdoor_found) { FURI_LOG_E(TAG, "Backdoor identified"); - dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; - } else { + if(dict_attack_ctx->nested_target_key == 1) { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; + } else { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; + } + } else if(dict_attack_ctx->nested_target_key == 0) { dict_attack_ctx->backdoor = MfClassicBackdoorNone; } instance->state = MfClassicPollerStateNestedController; @@ -1708,14 +1719,15 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); } - dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { // All keys have been collected FURI_LOG_E(TAG, "All keys collected and sectors read"); + dict_attack_ctx->nested_target_key = 0; dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; return command; } + dict_attack_ctx->nested_target_key = 2; // Backdoor keys dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; instance->state = MfClassicPollerStateNestedController; return command; @@ -1739,9 +1751,11 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Analyze tag for NXP/Fudan backdoor if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { + dict_attack_ctx->nested_target_key--; instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; return command; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzeBackdoor) { + dict_attack_ctx->nested_target_key = 0; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; } // TODO: Need to think about how this works for NXP/Fudan backdoored tags. From 4c14594ebb6cfdee1a72f13fca7198842d84d1c5 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 28 Aug 2024 09:26:59 -0400 Subject: [PATCH 027/236] Relocate backdoor detection --- .../debug/unit_tests/tests/nfc/nfc_test.c | 2 +- .../protocols/mf_classic/mf_classic_poller.c | 196 +++++++----------- .../protocols/mf_classic/mf_classic_poller.h | 16 +- .../mf_classic/mf_classic_poller_i.c | 41 ++-- .../mf_classic/mf_classic_poller_i.h | 8 +- .../mf_classic/mf_classic_poller_sync.c | 18 +- targets/f7/api_symbols.csv | 10 +- 7 files changed, 144 insertions(+), 147 deletions(-) diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index 0898ac8eda..4ba934b6d5 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -496,7 +496,7 @@ NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* MfClassicKey key = { .data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }; - error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL); + error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL, false); frame_test->state = (error == MfClassicErrorNone) ? NfcTestMfClassicSendFrameTestStateReadBlock : NfcTestMfClassicSendFrameTestStateFail; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 04d92b4010..3e4fa35aac 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -8,6 +8,9 @@ #define MF_CLASSIC_MAX_BUFF_SIZE (64) +const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; +const MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; + typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance); MfClassicPoller* mf_classic_poller_alloc(Iso14443_3aPoller* iso14443_3a_poller) { @@ -86,7 +89,8 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { iso14443_3a_copy( instance->data->iso14443_3a_data, iso14443_3a_poller_get_data(instance->iso14443_3a_poller)); - MfClassicError error = mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL); + MfClassicError error = + mf_classic_poller_get_nt(instance, 254, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType4k; instance->state = MfClassicPollerStateStart; @@ -96,7 +100,8 @@ NfcCommand mf_classic_poller_handler_detect_type(MfClassicPoller* instance) { instance->current_type_check = MfClassicType1k; } } else if(instance->current_type_check == MfClassicType1k) { - MfClassicError error = mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL); + MfClassicError error = + mf_classic_poller_get_nt(instance, 62, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { instance->data->type = MfClassicType1k; FURI_LOG_D(TAG, "1K detected"); @@ -122,7 +127,7 @@ NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) { mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); - instance->state = MfClassicPollerStateRequestKey; + instance->state = MfClassicPollerStateAnalyzeBackdoor; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) { instance->state = MfClassicPollerStateRequestReadSector; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeWrite) { @@ -236,7 +241,7 @@ NfcCommand mf_classic_poller_handler_read_block(MfClassicPoller* instance) { do { // Authenticate to sector error = mf_classic_poller_auth( - instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL); + instance, write_ctx->current_block, auth_key, write_ctx->key_type_read, NULL, false); if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to auth to block %d", write_ctx->current_block); instance->state = MfClassicPollerStateFail; @@ -294,7 +299,12 @@ NfcCommand mf_classic_poller_handler_write_block(MfClassicPoller* instance) { // Reauth if necessary if(write_ctx->need_halt_before_write) { error = mf_classic_poller_auth( - instance, write_ctx->current_block, auth_key, write_ctx->key_type_write, NULL); + instance, + write_ctx->current_block, + auth_key, + write_ctx->key_type_write, + NULL, + false); if(error != MfClassicErrorNone) { FURI_LOG_D( TAG, "Failed to auth to block %d for writing", write_ctx->current_block); @@ -403,8 +413,8 @@ NfcCommand mf_classic_poller_handler_write_value_block(MfClassicPoller* instance MfClassicKey* key = (auth_key_type == MfClassicKeyTypeA) ? &write_ctx->sec_tr.key_a : &write_ctx->sec_tr.key_b; - MfClassicError error = - mf_classic_poller_auth(instance, write_ctx->current_block, key, auth_key_type, NULL); + MfClassicError error = mf_classic_poller_auth( + instance, write_ctx->current_block, key, auth_key_type, NULL, false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_value_cmd(instance, write_ctx->current_block, value_cmd, diff); @@ -468,7 +478,8 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* sec_read_ctx->current_block, &sec_read_ctx->key, sec_read_ctx->key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; sec_read_ctx->auth_passed = true; @@ -505,6 +516,42 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* return command; } +NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { + NfcCommand command = NfcCommandReset; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + bool current_key_is_auth1 = + memcmp(dict_attack_ctx->current_key.data, auth1_backdoor_key.data, sizeof(MfClassicKey)) == + 0; + bool current_key_is_auth2 = + memcmp(dict_attack_ctx->current_key.data, auth2_backdoor_key.data, sizeof(MfClassicKey)) == + 0; + + if(!current_key_is_auth1) { + dict_attack_ctx->current_key = auth2_backdoor_key; + } else if(current_key_is_auth2) { + dict_attack_ctx->current_key = auth1_backdoor_key; + } + + // Attempt backdoor authentication + MfClassicError error = mf_classic_poller_auth( + instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); + bool backdoor_found = (error == MfClassicErrorNone) ? true : false; + + if(backdoor_found) { + FURI_LOG_E(TAG, "Backdoor identified"); + if(!current_key_is_auth1) { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; + } else { + dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; + } + instance->state = MfClassicPollerStateRequestKey; + } else if(current_key_is_auth2) { + dict_attack_ctx->backdoor = MfClassicBackdoorNone; + instance->state = MfClassicPollerStateRequestKey; + } + return command; +} + NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -535,7 +582,7 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); mf_classic_set_key_found( @@ -574,7 +621,7 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); mf_classic_set_key_found( @@ -629,7 +676,8 @@ NfcCommand mf_classic_poller_handler_read_sector(MfClassicPoller* instance) { block_num, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) { instance->state = MfClassicPollerStateNextSector; FURI_LOG_W(TAG, "Failed to re-auth. Go to next sector"); @@ -742,7 +790,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key A found"); mf_classic_set_key_found( @@ -778,7 +826,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( - instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL); + instance, block, &dict_attack_ctx->current_key, MfClassicKeyTypeB, NULL, false); if(error == MfClassicErrorNone) { FURI_LOG_I(TAG, "Key B found"); mf_classic_set_key_found( @@ -817,7 +865,8 @@ NfcCommand mf_classic_poller_handler_key_reuse_read_sector(MfClassicPoller* inst block_num, &dict_attack_ctx->current_key, dict_attack_ctx->current_key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) { instance->state = MfClassicPollerStateKeyReuseStart; break; @@ -905,92 +954,12 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan return command; } -NfcCommand mf_classic_poller_handler_nested_analyze_backdoor(MfClassicPoller* instance) { - // Can use on more than S variant as a free key for Nested - NfcCommand command = NfcCommandReset; - MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - - uint8_t block = - mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); - uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); - - MfClassicAuthContext auth_ctx = {}; - MfClassicNt nt = {}; - MfClassicKey auth_backdoor_key; - if(dict_attack_ctx->nested_target_key == 1) { - auth_backdoor_key = (MfClassicKey){ - .data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; // auth2 backdoor key, more common - } else { - auth_backdoor_key = - (MfClassicKey){.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; // auth1 backdoor key - } - MfClassicError error; - Iso14443_3aError iso_error; - bool backdoor_found = false; - - do { - // Step 1: Perform full authentication once - error = mf_classic_poller_auth( - instance, - block, - &dict_attack_ctx->nested_known_key, - dict_attack_ctx->nested_known_key_type, - &auth_ctx); - - if(error != MfClassicErrorNone) { - FURI_LOG_E(TAG, "Failed to perform full authentication"); - break; - } - - FURI_LOG_E(TAG, "Full authentication successful"); - - // Step 2: Attempt backdoor authentication - uint8_t auth_type = (dict_attack_ctx->nested_known_key_type == MfClassicKeyTypeB) ? - MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : - MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; - uint8_t auth_cmd[2] = {auth_type, block}; - bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); - iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); - crypto1_encrypt( - instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); - iso_error = iso14443_3a_poller_txrx_custom_parity( - instance->iso14443_3a_poller, - instance->tx_encrypted_buffer, - instance->rx_plain_buffer, - MF_CLASSIC_FWT_FC); - if(iso_error != Iso14443_3aErrorNone) { - FURI_LOG_E(TAG, "Error during nested authentication"); - break; - } - if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { - break; - } - bit_buffer_write_bytes(instance->rx_plain_buffer, nt.data, sizeof(MfClassicNt)); - uint32_t nt_enc = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); - // Ensure the encrypted nt can be generated by the backdoor - uint32_t decrypted_nt_enc = decrypt_nt_enc(cuid, nt_enc, auth_backdoor_key); - backdoor_found = is_weak_prng_nonce(decrypted_nt_enc); - } while(false); - if(backdoor_found) { - FURI_LOG_E(TAG, "Backdoor identified"); - if(dict_attack_ctx->nested_target_key == 1) { - dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; - } else { - dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; - } - } else if(dict_attack_ctx->nested_target_key == 0) { - dict_attack_ctx->backdoor = MfClassicBackdoorNone; - } - instance->state = MfClassicPollerStateNestedController; - return command; -} - NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance) { NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicNt nt = {}; - MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt); + MfClassicError error = mf_classic_poller_get_nt(instance, 0, MfClassicKeyTypeA, &nt, false); if(error != MfClassicErrorNone) { dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; @@ -1021,8 +990,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // TODO: Check if we have already identified the tag as static encrypted NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - uint8_t nt_enc_calibration_cnt = 21; - uint32_t nt_enc_temp_arr[nt_enc_calibration_cnt]; + uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; dict_attack_ctx->d_min = UINT16_MAX; dict_attack_ctx->d_max = 0; @@ -1044,7 +1012,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) block, &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1057,7 +1026,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); // Step 2: Perform nested authentication multiple times - for(uint8_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; + for(uint8_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { error = mf_classic_poller_auth_nested( instance, @@ -1065,6 +1034,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, + false, false); if(error != MfClassicErrorNone) { @@ -1108,7 +1078,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); - for(uint32_t collection_cycle = 0; collection_cycle < nt_enc_calibration_cnt; + for(uint32_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc( @@ -1220,7 +1190,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst block, &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1242,6 +1213,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, + false, false); if(error != MfClassicErrorNone) { @@ -1259,6 +1231,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, target_key_type, &auth_ctx, + false, true); if(nt_enc_collected != (nt_enc_per_collection - 1)) { @@ -1422,7 +1395,8 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc block, &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, - &auth_ctx); + &auth_ctx, + false); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1439,6 +1413,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc &dict_attack_ctx->nested_known_key, target_key_type, &auth_ctx, + false, true); if(error != MfClassicErrorNone) { @@ -1719,16 +1694,15 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); } + dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { // All keys have been collected FURI_LOG_E(TAG, "All keys collected and sectors read"); - dict_attack_ctx->nested_target_key = 0; dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; return command; } - dict_attack_ctx->nested_target_key = 2; // Backdoor keys - dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzeBackdoor; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; instance->state = MfClassicPollerStateNestedController; return command; } @@ -1749,15 +1723,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedDictAttack; return command; } - // Analyze tag for NXP/Fudan backdoor - if(dict_attack_ctx->backdoor == MfClassicBackdoorUnknown) { - dict_attack_ctx->nested_target_key--; - instance->state = MfClassicPollerStateNestedAnalyzeBackdoor; - return command; - } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzeBackdoor) { - dict_attack_ctx->nested_target_key = 0; - dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; - } // TODO: Need to think about how this works for NXP/Fudan backdoored tags. // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. // Calibration @@ -1843,6 +1808,7 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateWriteBlock] = mf_classic_poller_handler_write_block, [MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block, [MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector, + [MfClassicPollerStateAnalyzeBackdoor] = mf_classic_poller_handler_analyze_backdoor, [MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key, [MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector, [MfClassicPollerStateReadSectorBlocks] = @@ -1857,8 +1823,6 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateKeyReuseAuthKeyB] = mf_classic_poller_handler_key_reuse_auth_key_b, [MfClassicPollerStateKeyReuseReadSector] = mf_classic_poller_handler_key_reuse_read_sector, [MfClassicPollerStateNestedAnalyzePRNG] = mf_classic_poller_handler_nested_analyze_prng, - [MfClassicPollerStateNestedAnalyzeBackdoor] = - mf_classic_poller_handler_nested_analyze_backdoor, [MfClassicPollerStateNestedCalibrate] = mf_classic_poller_handler_nested_calibrate, [MfClassicPollerStateNestedCollectNt] = mf_classic_poller_handler_nested_collect_nt, [MfClassicPollerStateNestedController] = mf_classic_poller_handler_nested_controller, diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 0434249899..b26378d33e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -170,13 +170,15 @@ typedef struct { * @param[in] block_num block number for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_get_nt( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt); + MfClassicNt* nt, + bool backdoor_auth); /** * @brief Collect tag nonce during nested authentication. @@ -189,13 +191,15 @@ MfClassicError mf_classic_poller_get_nt( * @param[in] block_num block number for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] nt pointer to the MfClassicNt structure to be filled with nonce data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_get_nt_nested( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt); + MfClassicNt* nt, + bool backdoor_auth); /** * @brief Perform authentication. @@ -210,6 +214,7 @@ MfClassicError mf_classic_poller_get_nt_nested( * @param[in] key key to be used for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @return MfClassicErrorNone on success, an error code on failure. */ MfClassicError mf_classic_poller_auth( @@ -217,20 +222,22 @@ MfClassicError mf_classic_poller_auth( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data); + MfClassicAuthContext* data, + bool backdoor_auth); /** * @brief Perform nested authentication. * * Must ONLY be used inside the callback function. * - * Perform nested authentication as specified in Mf Classic protocol. + * Perform nested authentication as specified in Mf Classic protocol. * * @param[in, out] instance pointer to the instance to be used in the transaction. * @param[in] block_num block number for authentication. * @param[in] key key to be used for authentication. * @param[in] key_type key type to be used for authentication. * @param[out] data pointer to MfClassicAuthContext structure to be filled with authentication data. + * @param[in] backdoor_auth flag indicating if backdoor authentication is used. * @param[in] early_ret return immediately after receiving encrypted nonce. * @return MfClassicErrorNone on success, an error code on failure. */ @@ -240,6 +247,7 @@ MfClassicError mf_classic_poller_auth_nested( MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, + bool backdoor_auth, bool early_ret); /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index d3a3882c18..aac05748fd 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -38,13 +38,20 @@ static MfClassicError mf_classic_poller_get_nt_common( uint8_t block_num, MfClassicKeyType key_type, MfClassicNt* nt, - bool is_nested) { + bool is_nested, + bool backdoor_auth) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; do { - uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : - MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_type; + if(!backdoor_auth) { + auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + } else { + auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_B : + MF_CLASSIC_CMD_BACKDOOR_AUTH_KEY_A; + } uint8_t auth_cmd[2] = {auth_type, block_num}; bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); @@ -89,29 +96,33 @@ MfClassicError mf_classic_poller_get_nt( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt) { + MfClassicNt* nt, + bool backdoor_auth) { furi_check(instance); - return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, false); + return mf_classic_poller_get_nt_common( + instance, block_num, key_type, nt, false, backdoor_auth); } MfClassicError mf_classic_poller_get_nt_nested( MfClassicPoller* instance, uint8_t block_num, MfClassicKeyType key_type, - MfClassicNt* nt) { + MfClassicNt* nt, + bool backdoor_auth) { furi_check(instance); - return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, true); + return mf_classic_poller_get_nt_common(instance, block_num, key_type, nt, true, backdoor_auth); } -static MfClassicError mf_classic_poller_auth_common( +MfClassicError mf_classic_poller_auth_common( MfClassicPoller* instance, uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, bool is_nested, + bool backdoor_auth, bool early_ret) { MfClassicError ret = MfClassicErrorNone; Iso14443_3aError error = Iso14443_3aErrorNone; @@ -123,9 +134,10 @@ static MfClassicError mf_classic_poller_auth_common( MfClassicNt nt = {}; if(is_nested) { - ret = mf_classic_poller_get_nt_nested(instance, block_num, key_type, &nt); + ret = + mf_classic_poller_get_nt_nested(instance, block_num, key_type, &nt, backdoor_auth); } else { - ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt); + ret = mf_classic_poller_get_nt(instance, block_num, key_type, &nt, backdoor_auth); } if(ret != MfClassicErrorNone) break; if(data) { @@ -184,10 +196,12 @@ MfClassicError mf_classic_poller_auth( uint8_t block_num, MfClassicKey* key, MfClassicKeyType key_type, - MfClassicAuthContext* data) { + MfClassicAuthContext* data, + bool backdoor_auth) { furi_check(instance); furi_check(key); - return mf_classic_poller_auth_common(instance, block_num, key, key_type, data, false, false); + return mf_classic_poller_auth_common( + instance, block_num, key, key_type, data, false, backdoor_auth, false); } MfClassicError mf_classic_poller_auth_nested( @@ -196,11 +210,12 @@ MfClassicError mf_classic_poller_auth_nested( MfClassicKey* key, MfClassicKeyType key_type, MfClassicAuthContext* data, + bool backdoor_auth, bool early_ret) { furi_check(instance); furi_check(key); return mf_classic_poller_auth_common( - instance, block_num, key, key_type, data, true, early_ret); + instance, block_num, key, key_type, data, true, backdoor_auth, early_ret); } MfClassicError mf_classic_poller_halt(MfClassicPoller* instance) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index ab6426b382..df4f9b0f5e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -19,6 +19,7 @@ extern "C" { #define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) #define MF_CLASSIC_NESTED_HARD_MINIMUM (3) #define MF_CLASSIC_NESTED_RETRY_MAXIMUM (20) +#define MF_CLASSIC_NESTED_CALIBRATION_COUNT (21) #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" #define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" #define MF_CLASSIC_NESTED_USER_DICT_FILE_NAME "mf_classic_dict_user_nested.nfc" @@ -28,6 +29,9 @@ extern "C" { #define MF_CLASSIC_NESTED_USER_DICT_PATH \ (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_USER_DICT_FILE_NAME) +extern const MfClassicKey auth1_backdoor_key; +extern const MfClassicKey auth2_backdoor_key; + typedef enum { MfClassicAuthStateIdle, MfClassicAuthStatePassed, @@ -94,6 +98,7 @@ typedef enum { // Dict attack states MfClassicPollerStateNextSector, + MfClassicPollerStateAnalyzeBackdoor, MfClassicPollerStateRequestKey, MfClassicPollerStateReadSector, MfClassicPollerStateAuthKeyA, @@ -108,7 +113,6 @@ typedef enum { // Enhanced dictionary attack states MfClassicPollerStateNestedAnalyzePRNG, - MfClassicPollerStateNestedAnalyzeBackdoor, MfClassicPollerStateNestedCalibrate, MfClassicPollerStateNestedCollectNt, MfClassicPollerStateNestedController, @@ -137,6 +141,7 @@ typedef struct { bool auth_passed; uint16_t current_block; uint8_t reuse_key_sector; + MfClassicBackdoor backdoor; // Enhanced dictionary attack and nested nonce collection MfClassicNestedPhase nested_phase; MfClassicKey nested_known_key; @@ -145,7 +150,6 @@ typedef struct { uint16_t nested_target_key; MfClassicNestedNonceArray nested_nonce; MfClassicPrngType prng_type; - MfClassicBackdoor backdoor; bool static_encrypted; bool calibrated; uint16_t d_min; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c index cc6bc09085..13b51786f6 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c @@ -37,7 +37,8 @@ static MfClassicError mf_classic_poller_collect_nt_handler( poller, data->collect_nt_context.block, data->collect_nt_context.key_type, - &data->collect_nt_context.nt); + &data->collect_nt_context.nt, + false); } static MfClassicError @@ -47,7 +48,8 @@ static MfClassicError data->auth_context.block_num, &data->auth_context.key, data->auth_context.key_type, - &data->auth_context); + &data->auth_context, + false); } static MfClassicError mf_classic_poller_read_block_handler( @@ -61,7 +63,8 @@ static MfClassicError mf_classic_poller_read_block_handler( data->read_block_context.block_num, &data->read_block_context.key, data->read_block_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_read_block( @@ -87,7 +90,8 @@ static MfClassicError mf_classic_poller_write_block_handler( data->read_block_context.block_num, &data->read_block_context.key, data->read_block_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_write_block( @@ -113,7 +117,8 @@ static MfClassicError mf_classic_poller_read_value_handler( data->read_value_context.block_num, &data->read_value_context.key, data->read_value_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; MfClassicBlock block = {}; @@ -144,7 +149,8 @@ static MfClassicError mf_classic_poller_change_value_handler( data->change_value_context.block_num, &data->change_value_context.key, data->change_value_context.key_type, - NULL); + NULL, + false); if(error != MfClassicErrorNone) break; error = mf_classic_poller_value_cmd( diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 829dcf5bf7..c8e89f96ee 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,73.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -2515,10 +2515,10 @@ Function,+,mf_classic_is_sector_read,_Bool,"const MfClassicData*, uint8_t" Function,+,mf_classic_is_sector_trailer,_Bool,uint8_t Function,+,mf_classic_is_value_block,_Bool,"MfClassicSectorTrailer*, uint8_t" Function,+,mf_classic_load,_Bool,"MfClassicData*, FlipperFormat*, uint32_t" -Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" -Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool" -Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" -Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" +Function,+,mf_classic_poller_auth,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool" +Function,+,mf_classic_poller_auth_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*, _Bool, _Bool" +Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*, _Bool" +Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*, _Bool" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" Function,+,mf_classic_poller_send_custom_parity_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" From 90d0c3d095138b267d86bccccb57af3be4178452 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 1 Sep 2024 22:30:37 -0400 Subject: [PATCH 028/236] Hardnested support --- .../protocols/mf_classic/mf_classic_poller.c | 181 +++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 12 +- 2 files changed, 141 insertions(+), 52 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 3e4fa35aac..c99e54dc13 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -10,6 +10,8 @@ const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; const MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; +const uint16_t valid_sums[] = + {0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256}; typedef NfcCommand (*MfClassicPollerReadHandler)(MfClassicPoller* instance); @@ -940,7 +942,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan if(!is_weak_prng_nonce(nonce->nt)) hard_nt_count++; } - if(hard_nt_count >= MF_CLASSIC_NESTED_HARD_MINIMUM) { + if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) { dict_attack_ctx->prng_type = MfClassicPrngTypeHard; // FIXME: E -> D FURI_LOG_E(TAG, "Detected Hard PRNG"); @@ -1128,6 +1130,14 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) return command; } +static inline void set_byte_found(uint8_t* found, uint8_t byte) { + SET_PACKED_BIT(found, byte); +} + +static inline bool is_byte_found(uint8_t* found, uint8_t byte) { + return GET_PACKED_BIT(found, byte) != 0; +} + NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) // TODO: Look into using MfClassicNt more @@ -1135,13 +1145,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - if(dict_attack_ctx->prng_type == MfClassicPrngTypeHard) { - FURI_LOG_E(TAG, "Hard PRNG, skipping"); - // TODO: Collect hardnested nonces (cuid, sec, par, nt_enc, A/B) - // https://github.com/AloneLiberty/FlipperNested/blob/eba6163d7ef22adef5f9fe4d77a78ccfcc27a952/lib/nested/nested.c#L564-L645 - break; - } - if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { @@ -1173,8 +1176,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - uint8_t nonce_pair_index = dict_attack_ctx->nested_target_key % 2; - uint8_t nt_enc_per_collection = (dict_attack_ctx->attempt_count + 2) + nonce_pair_index; + bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0; + uint8_t nt_enc_per_collection = + is_weak ? ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : 1; MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; @@ -1245,29 +1250,41 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst for(int i = 0; i < 4; i++) { parity = (parity << 1) | (((parity_data[0] >> i) & 0x01) ^ 0x01); } - // Decrypt the previous nonce - uint32_t nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - uint32_t decrypted_nt_prev = - decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); - - // Find matching nt_enc plain at expected distance - uint32_t found_nt = 0; - uint8_t found_nt_cnt = 0; - uint16_t current_dist = dict_attack_ctx->d_min; - while(current_dist <= dict_attack_ctx->d_max) { - uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); - if(nonce_matches_encrypted_parity_bits(nth_successor, nth_successor ^ nt_enc, parity)) { - found_nt_cnt++; - if(found_nt_cnt > 1) { - FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt"); - break; + + uint32_t nt_prev = 0, decrypted_nt_prev = 0, found_nt = 0; + if(is_weak) { + // Decrypt the previous nonce + nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; + decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); + + // Find matching nt_enc plain at expected distance + found_nt = 0; + uint8_t found_nt_cnt = 0; + uint16_t current_dist = dict_attack_ctx->d_min; + while(current_dist <= dict_attack_ctx->d_max) { + uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); + if(nonce_matches_encrypted_parity_bits( + nth_successor, nth_successor ^ nt_enc, parity)) { + found_nt_cnt++; + if(found_nt_cnt > 1) { + FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt"); + break; + } + found_nt = nth_successor; } - found_nt = nth_successor; + current_dist++; } - current_dist++; - } - if(found_nt_cnt != 1) { - break; + if(found_nt_cnt != 1) { + break; + } + } else { + // Hardnested + if(!is_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF)) { + set_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF); + // Add unique parity to sum + dict_attack_ctx->msb_par_sum += nfc_util_even_parity32(parity & 0x08); + } + parity ^= 0x0F; } // Add the nonce to the array @@ -1568,18 +1585,45 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { return command; } -bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance) { +bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_dict_attack) { MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; - MfClassicKeyType target_key_type = - (((is_weak) && ((dict_attack_ctx->nested_target_key % 2) == 0)) || - ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - uint8_t target_sector = (is_weak) ? (dict_attack_ctx->nested_target_key / 2) : - (dict_attack_ctx->nested_target_key / 16); - if(mf_classic_is_key_found(instance->data, target_sector, target_key_type)) { - return true; + uint8_t nested_target_key = dict_attack_ctx->nested_target_key; + + MfClassicKeyType target_key_type; + uint8_t target_sector; + + if(is_dict_attack) { + target_key_type = (((is_weak) && ((nested_target_key % 2) == 0)) || + ((!is_weak) && ((nested_target_key % 16) < 8))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + target_sector = is_weak ? (nested_target_key / 2) : (nested_target_key / 16); + } else { + target_key_type = (((is_weak) && ((nested_target_key % 4) < 2)) || + ((!is_weak) && ((nested_target_key % 2) == 0))) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + target_sector = is_weak ? (nested_target_key / 4) : (nested_target_key / 2); + } + + return mf_classic_is_key_found(instance->data, target_sector, target_key_type); +} + +bool found_all_nt_enc_msb(const MfClassicPollerDictAttackContext* dict_attack_ctx) { + for(int i = 0; i < 32; i++) { + if(dict_attack_ctx->nt_enc_msb[i] != 0xFF) { + return false; + } + } + return true; +} + +bool is_valid_sum(uint16_t sum) { + for(size_t i = 0; i < 19; i++) { + if(sum == valid_sums[i]) { + return true; + } } return false; } @@ -1638,7 +1682,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - if(!(mf_classic_nested_is_target_key_found(instance))) { + if(!(mf_classic_nested_is_target_key_found(instance, true))) { instance->state = MfClassicPollerStateNestedDictAttack; return command; } else { @@ -1708,7 +1752,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if(dict_attack_ctx->attempt_count == 0) { // Check if the nested target key is a known key - if(mf_classic_nested_is_target_key_found(instance)) { + if(mf_classic_nested_is_target_key_found(instance, true)) { // Continue to next key instance->state = MfClassicPollerStateNestedController; return command; @@ -1742,25 +1786,52 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedLog; return command; } - // Target all remaining sectors, key A and B, first and second nonce - // TODO: Hardnested nonces logic, will likely be similar to dict attack has dict_target_key_max - if(is_weak && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 4))) { + // Target all remaining sectors, key A and B + if((is_weak && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 4))) || + (!(is_weak) && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 2)))) { + if((!(is_weak)) && found_all_nt_enc_msb(dict_attack_ctx)) { + if(is_valid_sum(dict_attack_ctx->msb_par_sum)) { + // All Hardnested nonces collected + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + instance->state = MfClassicPollerStateNestedController; + } else { + // Nonces do not match an expected sum + dict_attack_ctx->attempt_count++; + instance->state = MfClassicPollerStateNestedCollectNtEnc; + } + dict_attack_ctx->msb_par_sum = 0; + memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); + return command; + } if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; } else { - dict_attack_ctx->nested_target_key++; + if(is_weak) { + dict_attack_ctx->nested_target_key++; + if(dict_attack_ctx->nested_target_key % 2 == 0) { + dict_attack_ctx->current_key_checked = false; + } + } dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; - if(dict_attack_ctx->attempt_count == 0) { + if(!(dict_attack_ctx->current_key_checked)) { // Check if the nested target key is a known key - if(mf_classic_nested_is_target_key_found(instance)) { + if(mf_classic_nested_is_target_key_found(instance, false)) { // Continue to next key + if(!(is_weak)) { + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + } instance->state = MfClassicPollerStateNestedController; return command; } + dict_attack_ctx->current_key_checked = true; } - if(dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM) { + if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) || + (!(is_weak) && + (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) { // Unpredictable, skip FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); if(dict_attack_ctx->nested_nonce.nonces) { @@ -1768,7 +1839,15 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; } - dict_attack_ctx->nested_target_key += 2; + if(is_weak) { + dict_attack_ctx->nested_target_key += 2; + dict_attack_ctx->current_key_checked = false; + } else { + dict_attack_ctx->msb_par_sum = 0; + memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); + dict_attack_ctx->nested_target_key++; + dict_attack_ctx->current_key_checked = false; + } dict_attack_ctx->attempt_count = 0; } instance->state = MfClassicPollerStateNestedCollectNtEnc; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index df4f9b0f5e..9e4cfd728b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -8,6 +8,7 @@ #include #include #include "keys_dict.h" +#include "helpers/nfc_util.h" #ifdef __cplusplus extern "C" { @@ -17,8 +18,9 @@ extern "C" { #define NFC_FOLDER EXT_PATH("nfc") #define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") #define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) -#define MF_CLASSIC_NESTED_HARD_MINIMUM (3) +#define MF_CLASSIC_NESTED_NT_HARD_MINIMUM (3) #define MF_CLASSIC_NESTED_RETRY_MAXIMUM (20) +#define MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM (3) #define MF_CLASSIC_NESTED_CALIBRATION_COUNT (21) #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" #define MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME "mf_classic_dict_nested.nfc" @@ -28,9 +30,12 @@ extern "C" { (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_SYSTEM_DICT_FILE_NAME) #define MF_CLASSIC_NESTED_USER_DICT_PATH \ (NFC_ASSETS_FOLDER "/" MF_CLASSIC_NESTED_USER_DICT_FILE_NAME) +#define SET_PACKED_BIT(arr, bit) ((arr)[(bit) / 8] |= (1 << ((bit) % 8))) +#define GET_PACKED_BIT(arr, bit) ((arr)[(bit) / 8] & (1 << ((bit) % 8))) extern const MfClassicKey auth1_backdoor_key; extern const MfClassicKey auth2_backdoor_key; +extern const uint16_t valid_sums[19]; typedef enum { MfClassicAuthStateIdle, @@ -146,6 +151,7 @@ typedef struct { MfClassicNestedPhase nested_phase; MfClassicKey nested_known_key; MfClassicKeyType nested_known_key_type; + bool current_key_checked; uint8_t nested_known_key_sector; uint16_t nested_target_key; MfClassicNestedNonceArray nested_nonce; @@ -157,6 +163,10 @@ typedef struct { uint8_t attempt_count; KeysDict* mf_classic_system_dict; KeysDict* mf_classic_user_dict; + // Hardnested + uint8_t nt_enc_msb + [32]; // Bit-packed array to track which unique most significant bytes have been seen (256 bits = 32 bytes) + uint16_t msb_par_sum; // Sum of parity bits for each unique most significant byte } MfClassicPollerDictAttackContext; typedef struct { From 2abeb071fdb2df9b385b4ed7582aaa4fdd5aba8e Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 2 Sep 2024 10:28:50 -0400 Subject: [PATCH 029/236] Fix regression for regular nested attack --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 10 +++++++++- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index c99e54dc13..7234cc65c0 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1770,13 +1770,20 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // TODO: Need to think about how this works for NXP/Fudan backdoored tags. // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. // Calibration + bool initial_collect_nt_enc_iter = false; if(!(dict_attack_ctx->calibrated)) { if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedCalibrate; return command; } + initial_collect_nt_enc_iter = true; + dict_attack_ctx->auth_passed = true; + dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { + initial_collect_nt_enc_iter = true; + dict_attack_ctx->auth_passed = true; + dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces @@ -1807,7 +1814,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(!(dict_attack_ctx->auth_passed)) { dict_attack_ctx->attempt_count++; } else { - if(is_weak) { + if(is_weak && !(initial_collect_nt_enc_iter)) { dict_attack_ctx->nested_target_key++; if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; @@ -1850,6 +1857,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->attempt_count = 0; } + dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 9e4cfd728b..4726aa5bc0 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -19,7 +19,7 @@ extern "C" { #define NFC_ASSETS_FOLDER EXT_PATH("nfc/assets") #define MF_CLASSIC_NESTED_ANALYZE_NT_COUNT (5) #define MF_CLASSIC_NESTED_NT_HARD_MINIMUM (3) -#define MF_CLASSIC_NESTED_RETRY_MAXIMUM (20) +#define MF_CLASSIC_NESTED_RETRY_MAXIMUM (60) #define MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM (3) #define MF_CLASSIC_NESTED_CALIBRATION_COUNT (21) #define MF_CLASSIC_NESTED_LOGS_FILE_NAME ".nested.log" From ccc4326574eb96db1087d42159e225899c087300 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 2 Sep 2024 12:45:39 -0400 Subject: [PATCH 030/236] Backdoor read --- .../protocols/mf_classic/mf_classic_poller.c | 88 ++++++++++++++++++- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 7234cc65c0..b6de613f71 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,6 +6,9 @@ #define TAG "MfClassicPoller" +// TODO: Backdoor static nested +// TODO: Reflect status in NFC app + #define MF_CLASSIC_MAX_BUFF_SIZE (64) const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; @@ -546,7 +549,7 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) } else { dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; } - instance->state = MfClassicPollerStateRequestKey; + instance->state = MfClassicPollerStateBackdoorReadSector; } else if(current_key_is_auth2) { dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; @@ -554,6 +557,88 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) return command; } +NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* instance) { + NfcCommand command = NfcCommandContinue; + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + MfClassicError error = MfClassicErrorNone; + MfClassicBlock block = {}; + + uint8_t current_sector = mf_classic_get_sector_by_block(dict_attack_ctx->current_block); + uint8_t blocks_in_sector = mf_classic_get_blocks_num_in_sector(current_sector); + uint8_t first_block_in_sector = mf_classic_get_first_block_num_of_sector(current_sector); + + do { + if(dict_attack_ctx->current_block >= instance->sectors_total * 4) { + // We've read all blocks, reset current_block and move to next state + dict_attack_ctx->current_block = 0; + instance->state = MfClassicPollerStateNestedController; + break; + } + + // Use the appropriate backdoor key + const MfClassicKey* backdoor_key = (dict_attack_ctx->backdoor == MfClassicBackdoorAuth1) ? + &auth1_backdoor_key : + &auth2_backdoor_key; + + // Create a non-const copy of the backdoor key + MfClassicKey backdoor_key_copy; + memcpy(&backdoor_key_copy, backdoor_key, sizeof(MfClassicKey)); + + // Authenticate with the backdoor key + error = mf_classic_poller_auth( + instance, + first_block_in_sector, // Authenticate to the first block of the sector + &backdoor_key_copy, + MfClassicKeyTypeA, + NULL, + true); + + if(error != MfClassicErrorNone) { + FURI_LOG_E( + TAG, "Failed to authenticate with backdoor key for sector %d", current_sector); + break; + } + + // Read all blocks in the sector + for(uint8_t block_in_sector = 0; block_in_sector < blocks_in_sector; block_in_sector++) { + uint8_t block_to_read = first_block_in_sector + block_in_sector; + + error = mf_classic_poller_read_block(instance, block_to_read, &block); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to read block %d", block_to_read); + break; + } + + // Set the block as read in the data structure + mf_classic_set_block_read(instance->data, block_to_read, &block); + } + + if(error != MfClassicErrorNone) { + break; + } + + // Move to the next sector + current_sector++; + dict_attack_ctx->current_block = mf_classic_get_first_block_num_of_sector(current_sector); + + // Update blocks_in_sector and first_block_in_sector for the next sector + if(current_sector < instance->sectors_total) { + blocks_in_sector = mf_classic_get_blocks_num_in_sector(current_sector); + first_block_in_sector = mf_classic_get_first_block_num_of_sector(current_sector); + } + + // Halt the card after each sector to reset the authentication state + mf_classic_poller_halt(instance); + + // Send an event to the app that a sector has been read + command = mf_classic_poller_handle_data_update(instance); + + } while(false); + + return command; +} + NfcCommand mf_classic_poller_handler_request_key(MfClassicPoller* instance) { NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -1896,6 +1981,7 @@ static const MfClassicPollerReadHandler [MfClassicPollerStateWriteValueBlock] = mf_classic_poller_handler_write_value_block, [MfClassicPollerStateNextSector] = mf_classic_poller_handler_next_sector, [MfClassicPollerStateAnalyzeBackdoor] = mf_classic_poller_handler_analyze_backdoor, + [MfClassicPollerStateBackdoorReadSector] = mf_classic_poller_handler_backdoor_read_sector, [MfClassicPollerStateRequestKey] = mf_classic_poller_handler_request_key, [MfClassicPollerStateRequestReadSector] = mf_classic_poller_handler_request_read_sector, [MfClassicPollerStateReadSectorBlocks] = diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 4726aa5bc0..808a879f7e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -104,6 +104,7 @@ typedef enum { // Dict attack states MfClassicPollerStateNextSector, MfClassicPollerStateAnalyzeBackdoor, + MfClassicPollerStateBackdoorReadSector, MfClassicPollerStateRequestKey, MfClassicPollerStateReadSector, MfClassicPollerStateAuthKeyA, From 9c7120ec91338be00741cd9870f937ebc2ae0fcb Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 2 Sep 2024 18:53:39 -0400 Subject: [PATCH 031/236] Backdoor working up to calibration --- .../protocols/mf_classic/mf_classic_poller.c | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index b6de613f71..f8ec5ba5b8 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -575,20 +575,11 @@ NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* insta break; } - // Use the appropriate backdoor key - const MfClassicKey* backdoor_key = (dict_attack_ctx->backdoor == MfClassicBackdoorAuth1) ? - &auth1_backdoor_key : - &auth2_backdoor_key; - - // Create a non-const copy of the backdoor key - MfClassicKey backdoor_key_copy; - memcpy(&backdoor_key_copy, backdoor_key, sizeof(MfClassicKey)); - // Authenticate with the backdoor key error = mf_classic_poller_auth( instance, first_block_in_sector, // Authenticate to the first block of the sector - &backdoor_key_copy, + &(dict_attack_ctx->current_key), MfClassicKeyTypeA, NULL, true); @@ -1476,6 +1467,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc MfClassicAuthContext auth_ctx = {}; MfClassicError error; + bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; bool is_last_iter_for_hard_key = ((!is_weak) && ((dict_attack_ctx->nested_target_key % 8) == 7)); @@ -1498,7 +1490,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false); + use_backdoor_for_initial_auth); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1717,21 +1709,30 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // Iterate through keys NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { dict_attack_ctx->auth_passed = true; dict_attack_ctx->nested_known_key = dict_attack_ctx->current_key; - for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { - for(uint8_t key_type = 0; key_type < 2; key_type++) { - if(mf_classic_is_key_found(instance->data, sector, key_type)) { - dict_attack_ctx->nested_known_key_sector = sector; - dict_attack_ctx->nested_known_key_type = key_type; - break; + bool backdoor_present = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); + if(!(backdoor_present)) { + for(uint8_t sector = 0; sector < instance->sectors_total; sector++) { + for(uint8_t key_type = 0; key_type < 2; key_type++) { + if(mf_classic_is_key_found(instance->data, sector, key_type)) { + dict_attack_ctx->nested_known_key_sector = sector; + dict_attack_ctx->nested_known_key_type = key_type; + break; + } } } + dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; + } else { + dict_attack_ctx->nested_known_key_sector = 0; + dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; + dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; + initial_dict_attack_iter = true; } - dict_attack_ctx->nested_phase = MfClassicNestedPhaseAnalyzePRNG; } - bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { instance->state = MfClassicPollerStateNestedCollectNt; From 2cb2f05ea98c40970fdb5a18760c86c3db7c3aa5 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 2 Sep 2024 22:34:44 -0400 Subject: [PATCH 032/236] Backdoor nested calibration --- .../protocols/mf_classic/mf_classic_poller.c | 57 ++++++++++++++----- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index f8ec5ba5b8..2505909c93 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1064,8 +1064,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance } NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { - // TODO: Calibrate backdoored tags too - // TODO: Check if we have already identified the tag as static encrypted NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; @@ -1083,6 +1081,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) uint32_t nt_enc_prev = 0; uint32_t same_nt_enc_cnt = 0; uint8_t nt_enc_collected = 0; + bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); // Step 1: Perform full authentication once error = mf_classic_poller_auth( @@ -1091,7 +1090,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false); + use_backdoor); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1103,6 +1102,39 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + if((dict_attack_ctx->static_encrypted) && + (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2)) { + uint8_t target_block = + mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); + error = mf_classic_poller_auth_nested( + instance, + target_block, + &dict_attack_ctx->nested_known_key, + dict_attack_ctx->nested_known_key_type, + &auth_ctx, + use_backdoor, + false); + + if(error != MfClassicErrorNone) { + FURI_LOG_E(TAG, "Failed to perform nested authentication for static encrypted tag"); + instance->state = MfClassicPollerStateNestedCalibrate; + return command; + } + + uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); + // Store the decrypted static encrypted nonce + dict_attack_ctx->static_encrypted_nonce = + decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); + + dict_attack_ctx->calibrated = true; + + FURI_LOG_E(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc); + + instance->state = MfClassicPollerStateNestedController; + return command; + } + + // Original calibration logic for non-static encrypted tags // Step 2: Perform nested authentication multiple times for(uint8_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { @@ -1112,7 +1144,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false, + use_backdoor, false); if(error != MfClassicErrorNone) { @@ -1140,17 +1172,9 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) if(dict_attack_ctx->static_encrypted) { FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { - // TODO: Backdoor static nested attack calibration - dict_attack_ctx->calibrated = true; - instance->state = MfClassicPollerStateNestedController; - return command; - } else { - // TODO: Log these - dict_attack_ctx->calibrated = true; - instance->state = MfClassicPollerStateNestedController; - return command; - } + dict_attack_ctx->calibrated = true; + instance->state = MfClassicPollerStateNestedController; + return command; } // Find the distance between each nonce @@ -1729,6 +1753,9 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_known_key_sector = 0; dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + dict_attack_ctx->static_encrypted = true; + } dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; initial_dict_attack_iter = true; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 808a879f7e..41138144e1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -158,6 +158,7 @@ typedef struct { MfClassicNestedNonceArray nested_nonce; MfClassicPrngType prng_type; bool static_encrypted; + uint32_t static_encrypted_nonce; bool calibrated; uint16_t d_min; uint16_t d_max; From 6e9fe1edd820ae936996bd9b6c0f1d5cbdce7cc9 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 05:54:59 -0400 Subject: [PATCH 033/236] Don't recalibrate hard PRNG tags --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 2505909c93..0b20586c2b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -8,6 +8,8 @@ // TODO: Backdoor static nested // TODO: Reflect status in NFC app +// TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches +// TODO: Test not resetting the tag during Hardnested (single initial auth, repeated failed nested) #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -816,6 +818,7 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; command = instance->callback(instance->general_event, instance->context); // Nested entrypoint + // TODO: Ensure nested attack isn't run if tag is fully read if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished) { instance->state = MfClassicPollerStateNestedController; @@ -1891,6 +1894,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } initial_collect_nt_enc_iter = true; dict_attack_ctx->auth_passed = true; + dict_attack_ctx->calibrated = true; dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { From 2e0cd320c790fad650be8014aeb73c1234aa24e5 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 09:26:14 -0400 Subject: [PATCH 034/236] Static encrypted nonce collection --- .../protocols/mf_classic/mf_classic_poller.c | 78 ++++++++++--------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 0b20586c2b..6d784f9f01 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1107,6 +1107,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) if((dict_attack_ctx->static_encrypted) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2)) { + command = NfcCommandReset; uint8_t target_block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); error = mf_classic_poller_auth_nested( @@ -1248,30 +1249,6 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { - if(dict_attack_ctx->static_encrypted) { - FURI_LOG_E(TAG, "Static encrypted nonce detected"); - if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { - // TODO: Backdoor static nested attack with calibrated distance - break; - } else { - // TODO: If not present, just log nonces with parity bits, e.g. - /* - bool success = add_nested_nonce( - &result, - cuid, - dict_attack_ctx->nested_known_key_sector, - nt_prev, - nt_enc_prev, - parity, - UINT16_MAX); - if(!success) { - FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); - } - */ - break; - } - } - uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_known_key_sector); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); @@ -1279,14 +1256,17 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; + bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0; uint8_t nt_enc_per_collection = - is_weak ? ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : 1; + (is_weak && !(dict_attack_ctx->static_encrypted)) ? + ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : + 1; MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; - // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? + // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? Match calibrated? uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; uint32_t nt_enc_temp_arr[nt_enc_per_collection]; uint8_t nt_enc_collected = 0; @@ -1299,7 +1279,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false); + use_backdoor_for_initial_auth); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1355,7 +1335,8 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst } uint32_t nt_prev = 0, decrypted_nt_prev = 0, found_nt = 0; - if(is_weak) { + uint16_t dist = 0; + if(is_weak && !(dict_attack_ctx->static_encrypted)) { // Decrypt the previous nonce nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); @@ -1380,6 +1361,12 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst if(found_nt_cnt != 1) { break; } + } else if(dict_attack_ctx->static_encrypted) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + found_nt = dict_attack_ctx->static_encrypted_nonce; + } else { + dist = UINT16_MAX; + } } else { // Hardnested if(!is_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF)) { @@ -1398,7 +1385,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst found_nt, nt_enc, parity, - 0)) { + dist)) { dict_attack_ctx->auth_passed = true; } else { FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); @@ -1619,9 +1606,10 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { Stream* stream = buffered_file_stream_alloc(storage); FuriString* temp_str = furi_string_alloc(); bool weak_prng = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; + bool static_encrypted = dict_attack_ctx->static_encrypted; do { - if(weak_prng && (dict_attack_ctx->nested_nonce.count != 2)) { + if(weak_prng && (!(static_encrypted)) && (dict_attack_ctx->nested_nonce.count != 2)) { FURI_LOG_E( TAG, "MfClassicPollerStateNestedLog expected 2 nonces, received %u", @@ -1646,7 +1634,8 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { (nonce->key_idx / 4), ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', nonce->cuid); - for(uint8_t nt_idx = 0; nt_idx < (weak_prng ? 2 : 1); nt_idx++) { + for(uint8_t nt_idx = 0; nt_idx < ((weak_prng && (!(static_encrypted))) ? 2 : 1); + nt_idx++) { if(nt_idx == 1) { nonce = &dict_attack_ctx->nested_nonce.nonces[i + 1]; } @@ -1883,8 +1872,6 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateNestedDictAttack; return command; } - // TODO: Need to think about how this works for NXP/Fudan backdoored tags. - // We could reset the .calibration field every sector to re-calibrate. Calibration function handles backdoor calibration too. // Calibration bool initial_collect_nt_enc_iter = false; if(!(dict_attack_ctx->calibrated)) { @@ -1904,15 +1891,25 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces + // TODO: Calibrates too frequently for static encrypted backdoored tags if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || + ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + (dict_attack_ctx->nested_nonce.count == 1)) || ((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) { instance->state = MfClassicPollerStateNestedLog; return command; } + uint16_t nonce_collect_key_max; + if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { + nonce_collect_key_max = dict_attack_ctx->static_encrypted ? + ((instance->sectors_total * 4) - 2) : + (instance->sectors_total * 4); + } else { + nonce_collect_key_max = instance->sectors_total * 2; + } // Target all remaining sectors, key A and B - if((is_weak && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 4))) || - (!(is_weak) && (dict_attack_ctx->nested_target_key < (instance->sectors_total * 2)))) { + if(dict_attack_ctx->nested_target_key < nonce_collect_key_max) { if((!(is_weak)) && found_all_nt_enc_msb(dict_attack_ctx)) { if(is_valid_sum(dict_attack_ctx->msb_par_sum)) { // All Hardnested nonces collected @@ -1932,10 +1929,19 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count++; } else { if(is_weak && !(initial_collect_nt_enc_iter)) { - dict_attack_ctx->nested_target_key++; + if(!(dict_attack_ctx->static_encrypted)) { + dict_attack_ctx->nested_target_key++; + } else { + dict_attack_ctx->nested_target_key += 2; + } if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; } + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + (dict_attack_ctx->nested_target_key % 4 == 0) && + (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { + dict_attack_ctx->calibrated = false; + } } dict_attack_ctx->attempt_count = 0; } From 3cb3eab1185a01b7cd17801302f7a4f78e365afb Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 11:36:19 -0400 Subject: [PATCH 035/236] Update TODO --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 6d784f9f01..33973afe6b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,10 +6,10 @@ #define TAG "MfClassicPoller" -// TODO: Backdoor static nested // TODO: Reflect status in NFC app // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Test not resetting the tag during Hardnested (single initial auth, repeated failed nested) +// TODO: Load dictionaries specific to a CUID to not clutter the user dictionary #define MF_CLASSIC_MAX_BUFF_SIZE (64) From 92122b2cdf50da2182bf6a89abe047f2e2bd7db2 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 15:19:12 -0400 Subject: [PATCH 036/236] NFC app UI updates, MVP --- applications/main/nfc/nfc_app_i.h | 3 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 22 +++-- applications/main/nfc/views/dict_attack.c | 84 ++++++++++++++++++- applications/main/nfc/views/dict_attack.h | 6 ++ .../protocols/mf_classic/mf_classic_poller.c | 6 +- .../protocols/mf_classic/mf_classic_poller.h | 3 + .../mf_classic/mf_classic_poller_i.h | 1 - 7 files changed, 115 insertions(+), 10 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 798c3a5a6c..5310b89791 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -97,6 +97,9 @@ typedef struct { bool is_key_attack; uint8_t key_attack_current_sector; bool is_card_present; + uint8_t nested_phase; + uint8_t prng_type; + uint8_t backdoor; } NfcMfClassicDictAttackContext; struct NfcApp { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 5aee8ddd85..af329a67c0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -5,6 +5,8 @@ #define TAG "NfcMfClassicDictAttack" +// TODO: Update progress bar with nested attacks + typedef enum { DictAttackStateUserDictInProgress, DictAttackStateSystemDictInProgress, @@ -58,6 +60,9 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) instance->nfc_dict_context.sectors_read = data_update->sectors_read; instance->nfc_dict_context.keys_found = data_update->keys_found; instance->nfc_dict_context.current_sector = data_update->current_sector; + instance->nfc_dict_context.nested_phase = data_update->nested_phase; + instance->nfc_dict_context.prng_type = data_update->prng_type; + instance->nfc_dict_context.backdoor = data_update->backdoor; view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { @@ -117,6 +122,9 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found); dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current); dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector); + dict_attack_set_nested_phase(instance->dict_attack, mfc_dict->nested_phase); + dict_attack_set_prng_type(instance->dict_attack, mfc_dict->prng_type); + dict_attack_set_backdoor(instance->dict_attack, mfc_dict->backdoor); } } @@ -125,6 +133,13 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(state == DictAttackStateUserDictInProgress) { do { + // TODO: Check for errors + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { state = DictAttackStateSystemDictInProgress; break; @@ -149,13 +164,6 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { } while(false); } if(state == DictAttackStateSystemDictInProgress) { - // TODO: Check for errors - storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); - storage_common_copy( - instance->storage, - NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, - NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); - instance->nfc_dict_context.dict = keys_dict_alloc( NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary"); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 14298a6aa7..83cf9a4bf0 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -10,6 +10,30 @@ struct DictAttack { void* context; }; +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + +typedef enum { + MfClassicPrngTypeUnknown, // Tag not yet tested + MfClassicPrngTypeNoTag, // No tag detected during test + MfClassicPrngTypeWeak, // Weak PRNG, standard Nested + MfClassicPrngTypeHard, // Hard PRNG, Hardnested +} MfClassicPrngType; + +typedef enum { + MfClassicBackdoorUnknown, // Tag not yet tested + MfClassicBackdoorNone, // No observed backdoor + MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) +} MfClassicBackdoor; + typedef struct { FuriString* header; bool card_detected; @@ -21,6 +45,9 @@ typedef struct { size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; + MfClassicNestedPhase nested_phase; + MfClassicPrngType prng_type; + MfClassicBackdoor backdoor; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { @@ -34,8 +61,39 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { } else { char draw_str[32] = {}; canvas_set_font(canvas, FontSecondary); + + switch(m->nested_phase) { + case MfClassicNestedPhaseAnalyzePRNG: + furi_string_set(m->header, "PRNG Analysis"); + break; + case MfClassicNestedPhaseDictAttack: + case MfClassicNestedPhaseDictAttackResume: + furi_string_set(m->header, "Nested Dictionary"); + break; + case MfClassicNestedPhaseCalibrate: + furi_string_set(m->header, "Calibration"); + break; + case MfClassicNestedPhaseCollectNtEnc: + furi_string_set(m->header, "Nonce Collection"); + break; + default: + break; + } + + if(m->prng_type == MfClassicPrngTypeHard) { + furi_string_cat(m->header, " (Hard)"); + } + + if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { + if(m->nested_phase != MfClassicNestedPhaseNone) { + furi_string_cat(m->header, " (Backdoor)"); + } else { + furi_string_set(m->header, "Backdoor Read"); + } + } + canvas_draw_str_aligned( - canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); if(m->is_key_attack) { snprintf( draw_str, @@ -132,6 +190,9 @@ void dict_attack_reset(DictAttack* instance) { model->dict_keys_total = 0; model->dict_keys_current = 0; model->is_key_attack = false; + model->nested_phase = MfClassicNestedPhaseNone; + model->prng_type = MfClassicPrngTypeUnknown; + model->backdoor = MfClassicBackdoorUnknown; furi_string_reset(model->header); }, false); @@ -242,3 +303,24 @@ void dict_attack_reset_key_attack(DictAttack* instance) { with_view_model( instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true); } + +void dict_attack_set_nested_phase(DictAttack* instance, uint8_t nested_phase) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->nested_phase = nested_phase; }, true); +} + +void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->prng_type = prng_type; }, true); +} + +void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->backdoor = backdoor; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 30f3b3c44a..9afbeaf84b 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -45,6 +45,12 @@ void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector); void dict_attack_reset_key_attack(DictAttack* instance); +void dict_attack_set_nested_phase(DictAttack* instance, uint8_t nested_phase); + +void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type); + +void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 33973afe6b..de258b4d5d 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -68,6 +68,9 @@ static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance mf_classic_get_read_sectors_and_keys( instance->data, &data_update->sectors_read, &data_update->keys_found); data_update->current_sector = instance->mode_ctx.dict_attack_ctx.current_sector; + data_update->nested_phase = instance->mode_ctx.dict_attack_ctx.nested_phase; + data_update->prng_type = instance->mode_ctx.dict_attack_ctx.prng_type; + data_update->backdoor = instance->mode_ctx.dict_attack_ctx.backdoor; instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate; return instance->callback(instance->general_event, instance->context); } @@ -1723,7 +1726,8 @@ bool is_valid_sum(uint16_t sum) { NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { // Iterate through keys - NfcCommand command = NfcCommandContinue; + //NfcCommand command = NfcCommandContinue; + NfcCommand command = mf_classic_poller_handle_data_update(instance); MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool initial_dict_attack_iter = false; if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index b26378d33e..0501de21e3 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -77,6 +77,9 @@ typedef struct { uint8_t sectors_read; /**< Number of sectors read. */ uint8_t keys_found; /**< Number of keys found. */ uint8_t current_sector; /**< Current sector number. */ + uint8_t nested_phase; /**< Nested attack phase. */ + uint8_t prng_type; /**< PRNG (weak or hard). */ + uint8_t backdoor; /**< Backdoor type. */ } MfClassicPollerEventDataUpdate; /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 41138144e1..e17e13d13d 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -52,7 +52,6 @@ typedef enum { MfClassicNestedPhaseAnalyzePRNG, MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttackResume, - MfClassicNestedPhaseAnalyzeBackdoor, MfClassicNestedPhaseCalibrate, MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseFinished, From b09d5a0069e5c4d13c9ba434191320eec4837026 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 3 Sep 2024 20:20:49 -0400 Subject: [PATCH 037/236] Bump f18 API version (all functions are NFC related) --- targets/f18/api_symbols.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 57cbd1d62e..ecc3849c91 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,73.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, From cba58ed4372572c5214884b857889171059d247a Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 9 Sep 2024 20:50:15 -0400 Subject: [PATCH 038/236] Add new backdoor key, fix UI status update carrying over from previous read --- .../scenes/nfc_scene_mf_classic_dict_attack.c | 4 ++ applications/main/nfc/views/dict_attack.c | 24 ------- applications/main/nfc/views/dict_attack.h | 25 +++++++ .../protocols/mf_classic/mf_classic_poller.c | 65 +++++++++++-------- .../mf_classic/mf_classic_poller_i.h | 12 +++- 5 files changed, 79 insertions(+), 51 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index af329a67c0..e0050f4aef 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -6,6 +6,7 @@ #define TAG "NfcMfClassicDictAttack" // TODO: Update progress bar with nested attacks +// TODO: Zero the new values when skipping or stopping the attack typedef enum { DictAttackStateUserDictInProgress, @@ -297,6 +298,9 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.is_key_attack = false; instance->nfc_dict_context.key_attack_current_sector = 0; instance->nfc_dict_context.is_card_present = false; + instance->nfc_dict_context.nested_phase = MfClassicNestedPhaseNone; + instance->nfc_dict_context.prng_type = MfClassicPrngTypeUnknown; + instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown; nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 83cf9a4bf0..2fae91694e 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -10,30 +10,6 @@ struct DictAttack { void* context; }; -typedef enum { - MfClassicNestedPhaseNone, - MfClassicNestedPhaseAnalyzePRNG, - MfClassicNestedPhaseDictAttack, - MfClassicNestedPhaseDictAttackResume, - MfClassicNestedPhaseCalibrate, - MfClassicNestedPhaseCollectNtEnc, - MfClassicNestedPhaseFinished, -} MfClassicNestedPhase; - -typedef enum { - MfClassicPrngTypeUnknown, // Tag not yet tested - MfClassicPrngTypeNoTag, // No tag detected during test - MfClassicPrngTypeWeak, // Weak PRNG, standard Nested - MfClassicPrngTypeHard, // Hard PRNG, Hardnested -} MfClassicPrngType; - -typedef enum { - MfClassicBackdoorUnknown, // Tag not yet tested - MfClassicBackdoorNone, // No observed backdoor - MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor - MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) -} MfClassicBackdoor; - typedef struct { FuriString* header; bool card_detected; diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 9afbeaf84b..c2a7b1e68d 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -9,6 +9,31 @@ extern "C" { typedef struct DictAttack DictAttack; +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + +typedef enum { + MfClassicPrngTypeUnknown, // Tag not yet tested + MfClassicPrngTypeNoTag, // No tag detected during test + MfClassicPrngTypeWeak, // Weak PRNG, standard Nested + MfClassicPrngTypeHard, // Hard PRNG, Hardnested +} MfClassicPrngType; + +typedef enum { + MfClassicBackdoorUnknown, // Tag not yet tested + MfClassicBackdoorNone, // No observed backdoor + MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor + MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce) +} MfClassicBackdoor; + typedef enum { DictAttackEventSkipPressed, } DictAttackEvent; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index de258b4d5d..0e48b5d9fc 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -13,8 +13,14 @@ #define MF_CLASSIC_MAX_BUFF_SIZE (64) -const MfClassicKey auth1_backdoor_key = {.data = {0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}; -const MfClassicKey auth2_backdoor_key = {.data = {0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}; +// Ordered by frequency, labeled chronologically +const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[] = { + {{{0xa3, 0x96, 0xef, 0xa4, 0xe2, 0x4f}}, MfClassicBackdoorAuth3}, // Fudan (static encrypted) + {{{0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}, MfClassicBackdoorAuth1}, // Fudan, Infineon, NXP + {{{0x51, 0x8b, 0x33, 0x54, 0xe7, 0x60}}, MfClassicBackdoorAuth2}, // Fudan +}; +const size_t mf_classic_backdoor_keys_count = + sizeof(mf_classic_backdoor_keys) / sizeof(mf_classic_backdoor_keys[0]); const uint16_t valid_sums[] = {0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256}; @@ -529,36 +535,43 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - bool current_key_is_auth1 = - memcmp(dict_attack_ctx->current_key.data, auth1_backdoor_key.data, sizeof(MfClassicKey)) == - 0; - bool current_key_is_auth2 = - memcmp(dict_attack_ctx->current_key.data, auth2_backdoor_key.data, sizeof(MfClassicKey)) == - 0; - - if(!current_key_is_auth1) { - dict_attack_ctx->current_key = auth2_backdoor_key; - } else if(current_key_is_auth2) { - dict_attack_ctx->current_key = auth1_backdoor_key; + + size_t current_key_index = + mf_classic_backdoor_keys_count - 1; // Default to the last valid index + + // Find the current key in the backdoor_keys array + for(size_t i = 0; i < mf_classic_backdoor_keys_count; i++) { + if(memcmp( + dict_attack_ctx->current_key.data, + mf_classic_backdoor_keys[i].key.data, + sizeof(MfClassicKey)) == 0) { + current_key_index = i; + break; + } } + // Choose the next key to try + size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count; + uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1; + + FURI_LOG_E(TAG, "Trying backdoor v%d", backdoor_version); + dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key; + // Attempt backdoor authentication MfClassicError error = mf_classic_poller_auth( instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); - bool backdoor_found = (error == MfClassicErrorNone) ? true : false; + bool backdoor_found = (error == MfClassicErrorNone); if(backdoor_found) { - FURI_LOG_E(TAG, "Backdoor identified"); - if(!current_key_is_auth1) { - dict_attack_ctx->backdoor = MfClassicBackdoorAuth2; - } else { - dict_attack_ctx->backdoor = MfClassicBackdoorAuth1; - } + FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version); + dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type; instance->state = MfClassicPollerStateBackdoorReadSector; - } else if(current_key_is_auth2) { + } else if(next_key_index == (mf_classic_backdoor_keys_count - 1)) { + // We've tried all backdoor keys dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; } + return command; } @@ -1109,7 +1122,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); if((dict_attack_ctx->static_encrypted) && - (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2)) { + (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3)) { command = NfcCommandReset; uint8_t target_block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); @@ -1365,7 +1378,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst break; } } else if(dict_attack_ctx->static_encrypted) { - if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { found_nt = dict_attack_ctx->static_encrypted_nonce; } else { dist = UINT16_MAX; @@ -1749,7 +1762,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_known_key_sector = 0; dict_attack_ctx->nested_known_key_type = MfClassicKeyTypeA; dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; - if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) { + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { dict_attack_ctx->static_encrypted = true; } dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; @@ -1898,7 +1911,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance // TODO: Calibrates too frequently for static encrypted backdoored tags if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || - ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && (dict_attack_ctx->nested_nonce.count == 1)) || ((!(is_weak)) && (dict_attack_ctx->nested_nonce.count > 0))) { instance->state = MfClassicPollerStateNestedLog; @@ -1941,7 +1954,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; } - if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth2) && + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && (dict_attack_ctx->nested_target_key % 4 == 0) && (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { dict_attack_ctx->calibrated = false; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index e17e13d13d..111ddf2602 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -35,6 +35,7 @@ extern "C" { extern const MfClassicKey auth1_backdoor_key; extern const MfClassicKey auth2_backdoor_key; +extern const MfClassicKey auth3_backdoor_key; extern const uint16_t valid_sums[19]; typedef enum { @@ -68,9 +69,18 @@ typedef enum { MfClassicBackdoorUnknown, // Tag not yet tested MfClassicBackdoorNone, // No observed backdoor MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor - MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (static encrypted nonce) + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor + MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce) } MfClassicBackdoor; +typedef struct { + MfClassicKey key; + MfClassicBackdoor type; +} MfClassicBackdoorKeyPair; + +extern const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[]; +extern const size_t mf_classic_backdoor_keys_count; + typedef struct { uint32_t cuid; // Card UID uint8_t key_idx; // Key index From ab8bc3e21c4a2d641f588ecb6ede081fee7bd4cc Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 9 Sep 2024 20:51:51 -0400 Subject: [PATCH 039/236] Clear TODO line --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index e0050f4aef..b660520a3c 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -6,7 +6,6 @@ #define TAG "NfcMfClassicDictAttack" // TODO: Update progress bar with nested attacks -// TODO: Zero the new values when skipping or stopping the attack typedef enum { DictAttackStateUserDictInProgress, From d7484ee84078265192e019eeb07de79fb461e164 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 10 Sep 2024 09:03:11 -0400 Subject: [PATCH 040/236] Fix v1/v2 backdoor nonce collection --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 0e48b5d9fc..de0d645fdf 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1272,7 +1272,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst MfClassicAuthContext auth_ctx = {}; MfClassicError error; - bool use_backdoor_for_initial_auth = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); + bool use_backdoor = (dict_attack_ctx->backdoor != MfClassicBackdoorNone); bool is_weak = dict_attack_ctx->prng_type == MfClassicPrngTypeWeak; uint8_t nonce_pair_index = is_weak ? (dict_attack_ctx->nested_target_key % 2) : 0; uint8_t nt_enc_per_collection = @@ -1295,7 +1295,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - use_backdoor_for_initial_auth); + use_backdoor); if(error != MfClassicErrorNone) { FURI_LOG_E(TAG, "Failed to perform full authentication"); @@ -1317,7 +1317,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst &dict_attack_ctx->nested_known_key, dict_attack_ctx->nested_known_key_type, &auth_ctx, - false, + use_backdoor, false); if(error != MfClassicErrorNone) { From c43806b5dbd827c0c998338779a6b743864019a9 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 10 Sep 2024 14:10:09 -0400 Subject: [PATCH 041/236] Speed up backdoor detection, alert on new backdoor --- .../protocols/mf_classic/mf_classic_poller.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index de0d645fdf..2b122679e8 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -560,16 +560,19 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) // Attempt backdoor authentication MfClassicError error = mf_classic_poller_auth( instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); - bool backdoor_found = (error == MfClassicErrorNone); - - if(backdoor_found) { + if((next_key_index == 0) && (error == MfClassicErrorProtocol)) { + FURI_LOG_E(TAG, "No backdoor identified"); + dict_attack_ctx->backdoor = MfClassicBackdoorNone; + instance->state = MfClassicPollerStateRequestKey; + } else if(error == MfClassicErrorNone) { FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version); dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type; instance->state = MfClassicPollerStateBackdoorReadSector; - } else if(next_key_index == (mf_classic_backdoor_keys_count - 1)) { - // We've tried all backdoor keys - dict_attack_ctx->backdoor = MfClassicBackdoorNone; - instance->state = MfClassicPollerStateRequestKey; + } else if( + (error == MfClassicErrorAuth) && + (next_key_index == (mf_classic_backdoor_keys_count - 1))) { + // We've tried all backdoor keys, this is a unique key and an important research finding + furi_crash("New backdoor: please report!"); } return command; @@ -1083,6 +1086,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance } NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { + // TODO: Discard outliers (e.g. greater than 3 standard deviations) NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; From ab0debba0213ebd91a19e4a151e820cb6a9d7769 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 10 Sep 2024 19:20:53 -0400 Subject: [PATCH 042/236] Add additional condition to backdoor check --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 2b122679e8..7a068c4b87 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -560,7 +560,8 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) // Attempt backdoor authentication MfClassicError error = mf_classic_poller_auth( instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); - if((next_key_index == 0) && (error == MfClassicErrorProtocol)) { + if((next_key_index == 0) && + (error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) { FURI_LOG_E(TAG, "No backdoor identified"); dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; From 13411da449ec9632b12b1db48ee4dcceeb42c063 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 11 Sep 2024 16:22:52 -0400 Subject: [PATCH 043/236] I'll try freeing memory, that's a good trick! --- .../protocols/mf_classic/mf_classic_poller.c | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 7a068c4b87..88af4744b9 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -65,6 +65,26 @@ void mf_classic_poller_free(MfClassicPoller* instance) { bit_buffer_free(instance->tx_encrypted_buffer); bit_buffer_free(instance->rx_encrypted_buffer); + // Clean up resources in MfClassicPollerDictAttackContext + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + + // Free the dictionaries + if(dict_attack_ctx->mf_classic_system_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + dict_attack_ctx->mf_classic_system_dict = NULL; + } + if(dict_attack_ctx->mf_classic_user_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + dict_attack_ctx->mf_classic_user_dict = NULL; + } + + // Free the nested nonce array if it exists + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } + free(instance); } @@ -1861,9 +1881,11 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_target_key == dict_target_key_max) { if(dict_attack_ctx->mf_classic_system_dict) { keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + dict_attack_ctx->mf_classic_system_dict = NULL; } if(dict_attack_ctx->mf_classic_user_dict) { keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + dict_attack_ctx->mf_classic_user_dict = NULL; } dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { From 0428e82b14065e36cdae352b1b59a6ededa3c6f8 Mon Sep 17 00:00:00 2001 From: porta Date: Thu, 12 Sep 2024 19:32:07 +0300 Subject: [PATCH 044/236] Fix USB-UART bridge exit screen stopping the bridge prematurely (#3892) * fix: exit screen stopping bridge prematurely * refactor: merge exit confirmation scene into main usb uart scene --- .../main/gpio/scenes/gpio_scene_config.h | 1 - .../gpio/scenes/gpio_scene_exit_confirm.c | 44 ------------------- .../main/gpio/scenes/gpio_scene_usb_uart.c | 32 +++++++++----- 3 files changed, 21 insertions(+), 56 deletions(-) delete mode 100644 applications/main/gpio/scenes/gpio_scene_exit_confirm.c diff --git a/applications/main/gpio/scenes/gpio_scene_config.h b/applications/main/gpio/scenes/gpio_scene_config.h index d6fd24d19d..3406e42d3a 100644 --- a/applications/main/gpio/scenes/gpio_scene_config.h +++ b/applications/main/gpio/scenes/gpio_scene_config.h @@ -3,4 +3,3 @@ ADD_SCENE(gpio, test, Test) ADD_SCENE(gpio, usb_uart, UsbUart) ADD_SCENE(gpio, usb_uart_cfg, UsbUartCfg) ADD_SCENE(gpio, usb_uart_close_rpc, UsbUartCloseRpc) -ADD_SCENE(gpio, exit_confirm, ExitConfirm) diff --git a/applications/main/gpio/scenes/gpio_scene_exit_confirm.c b/applications/main/gpio/scenes/gpio_scene_exit_confirm.c deleted file mode 100644 index efb0734a31..0000000000 --- a/applications/main/gpio/scenes/gpio_scene_exit_confirm.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "gpio_app_i.h" - -void gpio_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { - GpioApp* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, result); -} - -void gpio_scene_exit_confirm_on_enter(void* context) { - GpioApp* app = context; - DialogEx* dialog = app->dialog; - - dialog_ex_set_context(dialog, app); - dialog_ex_set_left_button_text(dialog, "Exit"); - dialog_ex_set_right_button_text(dialog, "Stay"); - dialog_ex_set_header(dialog, "Exit USB-UART?", 22, 12, AlignLeft, AlignTop); - dialog_ex_set_result_callback(dialog, gpio_scene_exit_confirm_dialog_callback); - - view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewExitConfirm); -} - -bool gpio_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { - GpioApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == DialogExResultRight) { - consumed = scene_manager_previous_scene(app->scene_manager); - } else if(event.event == DialogExResultLeft) { - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, GpioSceneStart); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = true; - } - - return consumed; -} - -void gpio_scene_exit_confirm_on_exit(void* context) { - GpioApp* app = context; - - // Clean view - dialog_ex_reset(app->dialog); -} diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart.c b/applications/main/gpio/scenes/gpio_scene_usb_uart.c index 9a3514ca4f..e3e7e8c24a 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart.c @@ -6,7 +6,7 @@ typedef struct { UsbUartState state; } SceneUsbUartBridge; -static SceneUsbUartBridge* scene_usb_uart; +static SceneUsbUartBridge* scene_usb_uart = NULL; void gpio_scene_usb_uart_callback(GpioCustomEvent event, void* context) { furi_assert(context); @@ -14,10 +14,21 @@ void gpio_scene_usb_uart_callback(GpioCustomEvent event, void* context) { view_dispatcher_send_custom_event(app->view_dispatcher, event); } +void gpio_scene_usb_uart_dialog_callback(DialogExResult result, void* context) { + GpioApp* app = context; + if(result == DialogExResultLeft) { + usb_uart_disable(app->usb_uart_bridge); + free(scene_usb_uart); + scene_usb_uart = NULL; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, GpioSceneStart); + } else { + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); + } +} + void gpio_scene_usb_uart_on_enter(void* context) { GpioApp* app = context; - uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUart); - if(prev_state == 0) { + if(!scene_usb_uart) { scene_usb_uart = malloc(sizeof(SceneUsbUartBridge)); scene_usb_uart->cfg.vcp_ch = 0; scene_usb_uart->cfg.uart_ch = 0; @@ -31,7 +42,6 @@ void gpio_scene_usb_uart_on_enter(void* context) { usb_uart_get_state(app->usb_uart_bridge, &scene_usb_uart->state); gpio_usb_uart_set_callback(app->gpio_usb_uart, gpio_scene_usb_uart_callback, app); - scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 0); view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); notification_message(app->notifications, &sequence_display_backlight_enforce_on); } @@ -39,11 +49,16 @@ void gpio_scene_usb_uart_on_enter(void* context) { bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { GpioApp* app = context; if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 1); scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCfg); return true; } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(app->scene_manager, GpioSceneExitConfirm); + DialogEx* dialog = app->dialog; + dialog_ex_set_context(dialog, app); + dialog_ex_set_left_button_text(dialog, "Exit"); + dialog_ex_set_right_button_text(dialog, "Stay"); + dialog_ex_set_header(dialog, "Exit USB-UART?", 22, 12, AlignLeft, AlignTop); + dialog_ex_set_result_callback(dialog, gpio_scene_usb_uart_dialog_callback); + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewExitConfirm); return true; } else if(event.type == SceneManagerEventTypeTick) { uint32_t tx_cnt_last = scene_usb_uart->state.tx_cnt; @@ -61,10 +76,5 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { void gpio_scene_usb_uart_on_exit(void* context) { GpioApp* app = context; - uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioSceneUsbUart); - if(prev_state == 0) { - usb_uart_disable(app->usb_uart_bridge); - free(scene_usb_uart); - } notification_message(app->notifications, &sequence_display_backlight_enforce_auto); } From 8edafa3f39860df2642f1cda4fb50120a0bf9600 Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 12 Sep 2024 14:28:18 -0400 Subject: [PATCH 045/236] Do not enter nested attack if card is already finished --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 88af4744b9..260c41221e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -858,9 +858,10 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) instance->mfc_event.type = MfClassicPollerEventTypeKeyAttackStop; command = instance->callback(instance->general_event, instance->context); // Nested entrypoint - // TODO: Ensure nested attack isn't run if tag is fully read - if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseNone || - dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished) { + bool nested_active = dict_attack_ctx->nested_phase != MfClassicNestedPhaseNone; + if((nested_active && + (dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) || + (!(nested_active) && !(mf_classic_is_card_read(instance->data)))) { instance->state = MfClassicPollerStateNestedController; break; } From b670d5b6e2081bec5abcf3a693db5ce3af5d8ab2 Mon Sep 17 00:00:00 2001 From: porta Date: Fri, 13 Sep 2024 20:31:07 +0300 Subject: [PATCH 046/236] [FL-3885] Put errno into TCB (#3893) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: thread-safe errno * ci: fix pvs warning * ci: silence pvs warning * fix: 🤯 * test: convert test app into a unit test --- .../unit_tests/tests/furi/furi_errno_test.c | 51 +++++++++++++++++++ .../debug/unit_tests/tests/furi/furi_test.c | 6 +++ targets/f7/inc/FreeRTOSConfig.h | 13 +++-- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 applications/debug/unit_tests/tests/furi/furi_errno_test.c diff --git a/applications/debug/unit_tests/tests/furi/furi_errno_test.c b/applications/debug/unit_tests/tests/furi/furi_errno_test.c new file mode 100644 index 0000000000..b42e7c0828 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_errno_test.c @@ -0,0 +1,51 @@ +#include +#include +#include "../test.h" // IWYU pragma: keep + +#define TAG "ErrnoTest" +#define THREAD_CNT 16 +#define ITER_CNT 1000 + +static int32_t errno_fuzzer(void* context) { + int start_value = (int)context; + int32_t fails = 0; + + for(int i = start_value; i < start_value + ITER_CNT; i++) { + errno = i; + furi_thread_yield(); + if(errno != i) fails++; + } + + for(int i = 0; i < ITER_CNT; i++) { + errno = 0; + furi_thread_yield(); + UNUSED(strtol("123456", NULL, 10)); // -V530 + furi_thread_yield(); + if(errno != 0) fails++; + + errno = 0; + furi_thread_yield(); + UNUSED(strtol("123456123456123456123456123456123456123456123456", NULL, 10)); // -V530 + furi_thread_yield(); + if(errno != ERANGE) fails++; + } + + return fails; +} + +void test_errno_saving(void) { + FuriThread* threads[THREAD_CNT]; + + for(int i = 0; i < THREAD_CNT; i++) { + int start_value = i * ITER_CNT; + threads[i] = furi_thread_alloc_ex("ErrnoFuzzer", 1024, errno_fuzzer, (void*)start_value); + furi_thread_set_priority(threads[i], FuriThreadPriorityNormal); + furi_thread_start(threads[i]); + } + + for(int i = 0; i < THREAD_CNT; i++) { + furi_thread_join(threads[i]); + mu_assert_int_eq(0, furi_thread_get_return_code(threads[i])); + furi_thread_free(threads[i]); + } +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index be579d2b8c..2a76d5184c 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -8,6 +8,7 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); void test_furi_event_loop(void); +void test_errno_saving(void); static int foo = 0; @@ -42,6 +43,10 @@ MU_TEST(mu_test_furi_event_loop) { test_furi_event_loop(); } +MU_TEST(mu_test_errno_saving) { + test_errno_saving(); +} + MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_check); @@ -51,6 +56,7 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(mu_test_furi_pubsub); MU_RUN_TEST(mu_test_furi_memmgr); MU_RUN_TEST(mu_test_furi_event_loop); + MU_RUN_TEST(mu_test_errno_saving); } int run_minunit_test_furi(void) { diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 2948faef93..82cda2c6da 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -2,6 +2,7 @@ #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include +#include #pragma GCC diagnostic ignored "-Wredundant-decls" #endif @@ -26,6 +27,7 @@ #define configUSE_16_BIT_TICKS 0 #define configMAX_PRIORITIES (32) #define configMINIMAL_STACK_SIZE ((uint16_t)128) +#define configUSE_POSIX_ERRNO 1 /* Heap size determined automatically by linker */ // #define configTOTAL_HEAP_SIZE ((size_t)0) @@ -146,9 +148,14 @@ standard names. */ #define configOVERRIDE_DEFAULT_TICK_CONFIGURATION \ 1 /* required only for Keil but does not hurt otherwise */ -#define traceTASK_SWITCHED_IN() \ - extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ - furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack) +#define traceTASK_SWITCHED_IN() \ + extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ + furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack); \ + errno = pxCurrentTCB->iTaskErrno +// ^^^^^ acquire errno directly from TCB because FreeRTOS assigns its `FreeRTOS_errno' _after_ our hook is called + +// referencing `FreeRTOS_errno' here vvvvv because FreeRTOS calls our hook _before_ copying the value into the TCB, hence a manual write to the TCB would get overwritten +#define traceTASK_SWITCHED_OUT() FreeRTOS_errno = errno #define portCLEAN_UP_TCB(pxTCB) \ extern void furi_thread_cleanup_tcb_event(TaskHandle_t task); \ From 19a3736fe5dab5d01220660b318fed824a2d6e22 Mon Sep 17 00:00:00 2001 From: porta Date: Sun, 15 Sep 2024 18:01:42 +0300 Subject: [PATCH 047/236] [FL-3891] Folder rename fails (#3896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix, refactor: storage is_subdir API * docs: fix incorrect comment * test: new storage apis * test: use temporary path * style: fix formatting * UnitTest: storage path macros naming * UnitTest: storage path macros naming part 2 Co-authored-by: あく --- .../unit_tests/tests/storage/storage_test.c | 87 +++++++++++++++---- applications/services/storage/storage.h | 25 +++--- .../services/storage/storage_external_api.c | 18 ++-- .../services/storage/storage_message.h | 2 +- .../services/storage/storage_processing.c | 18 +++- targets/f18/api_symbols.csv | 5 +- targets/f7/api_symbols.csv | 5 +- 7 files changed, 120 insertions(+), 40 deletions(-) diff --git a/applications/debug/unit_tests/tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c index f317fbf680..75c52ef9a7 100644 --- a/applications/debug/unit_tests/tests/storage/storage_test.c +++ b/applications/debug/unit_tests/tests/storage/storage_test.c @@ -6,9 +6,10 @@ // This is a hack to access internal storage functions and definitions #include -#define UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/" path) +#define UNIT_TESTS_RESOURCES_PATH(path) EXT_PATH("unit_tests/" path) +#define UNIT_TESTS_PATH(path) EXT_PATH(".tmp/unit_tests/" path) -#define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test") +#define STORAGE_LOCKED_FILE UNIT_TESTS_PATH("locked_file.test") #define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX #define STORAGE_TEST_DIR UNIT_TESTS_PATH("test_dir") @@ -369,33 +370,78 @@ MU_TEST(storage_file_rename) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); - mu_check(write_file_13DA(storage, EXT_PATH("file.old"))); - mu_check(check_file_13DA(storage, EXT_PATH("file.old"))); + mu_check(write_file_13DA(storage, UNIT_TESTS_PATH("file.old"))); + mu_check(check_file_13DA(storage, UNIT_TESTS_PATH("file.old"))); mu_assert_int_eq( - FSE_OK, storage_common_rename(storage, EXT_PATH("file.old"), EXT_PATH("file.new"))); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("file.old"), NULL)); - mu_assert_int_eq(FSE_OK, storage_common_stat(storage, EXT_PATH("file.new"), NULL)); - mu_check(check_file_13DA(storage, EXT_PATH("file.new"))); - mu_assert_int_eq(FSE_OK, storage_common_remove(storage, EXT_PATH("file.new"))); + FSE_OK, + storage_common_rename(storage, UNIT_TESTS_PATH("file.old"), UNIT_TESTS_PATH("file.new"))); + mu_assert_int_eq( + FSE_NOT_EXIST, storage_common_stat(storage, UNIT_TESTS_PATH("file.old"), NULL)); + mu_assert_int_eq(FSE_OK, storage_common_stat(storage, UNIT_TESTS_PATH("file.new"), NULL)); + mu_check(check_file_13DA(storage, UNIT_TESTS_PATH("file.new"))); + mu_assert_int_eq(FSE_OK, storage_common_remove(storage, UNIT_TESTS_PATH("file.new"))); storage_file_free(file); furi_record_close(RECORD_STORAGE); } +static const char* dir_rename_tests[][2] = { + {UNIT_TESTS_PATH("dir.old"), UNIT_TESTS_PATH("dir.new")}, + {UNIT_TESTS_PATH("test_dir"), UNIT_TESTS_PATH("test_dir-new")}, + {UNIT_TESTS_PATH("test"), UNIT_TESTS_PATH("test-test")}, +}; + MU_TEST(storage_dir_rename) { Storage* storage = furi_record_open(RECORD_STORAGE); - storage_dir_create(storage, EXT_PATH("dir.old")); + for(size_t i = 0; i < COUNT_OF(dir_rename_tests); i++) { + const char* old_path = dir_rename_tests[i][0]; + const char* new_path = dir_rename_tests[i][1]; + + storage_dir_create(storage, old_path); + mu_check(storage_dir_rename_check(storage, old_path)); + + mu_assert_int_eq(FSE_OK, storage_common_rename(storage, old_path, new_path)); + mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, old_path, NULL)); + mu_check(storage_dir_rename_check(storage, new_path)); + + storage_dir_remove(storage, new_path); + mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, new_path, NULL)); + } + + furi_record_close(RECORD_STORAGE); +} - mu_check(storage_dir_rename_check(storage, EXT_PATH("dir.old"))); +MU_TEST(storage_equiv_and_subdir) { + Storage* storage = furi_record_open(RECORD_STORAGE); mu_assert_int_eq( - FSE_OK, storage_common_rename(storage, EXT_PATH("dir.old"), EXT_PATH("dir.new"))); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("dir.old"), NULL)); - mu_check(storage_dir_rename_check(storage, EXT_PATH("dir.new"))); + true, + storage_common_equivalent_path(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + true, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah/"), UNIT_TESTS_PATH("blah/"))); + mu_assert_int_eq( + false, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah-blah"))); + mu_assert_int_eq( + false, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah/"), UNIT_TESTS_PATH("blah-blah/"))); - storage_dir_remove(storage, EXT_PATH("dir.new")); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("dir.new"), NULL)); + mu_assert_int_eq( + true, storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + true, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah/blah"))); + mu_assert_int_eq( + false, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah/blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + false, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah-blah"))); furi_record_close(RECORD_STORAGE); } @@ -403,10 +449,13 @@ MU_TEST(storage_dir_rename) { MU_TEST_SUITE(storage_rename) { MU_RUN_TEST(storage_file_rename); MU_RUN_TEST(storage_dir_rename); + MU_RUN_TEST(storage_equiv_and_subdir); Storage* storage = furi_record_open(RECORD_STORAGE); - storage_dir_remove(storage, EXT_PATH("dir.old")); - storage_dir_remove(storage, EXT_PATH("dir.new")); + for(size_t i = 0; i < COUNT_OF(dir_rename_tests); i++) { + storage_dir_remove(storage, dir_rename_tests[i][0]); + storage_dir_remove(storage, dir_rename_tests[i][1]); + } furi_record_close(RECORD_STORAGE); } @@ -653,7 +702,7 @@ MU_TEST(test_md5_calc) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); - const char* path = UNIT_TESTS_PATH("storage/md5.txt"); + const char* path = UNIT_TESTS_RESOURCES_PATH("storage/md5.txt"); const char* md5_cstr = "2a456fa43e75088fdde41c93159d62a2"; const uint8_t md5[MD5_HASH_SIZE] = { 0x2a, diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 072db1305b..ea0ff24ade 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -401,21 +401,26 @@ bool storage_common_exists(Storage* storage, const char* path); * - /int/Test and /int/test -> false (Case-sensitive storage), * - /ext/Test and /ext/test -> true (Case-insensitive storage). * - * If the truncate parameter is set to true, the second path will be - * truncated to be no longer than the first one. It is useful to determine - * whether path2 is a subdirectory of path1. - * * @param storage pointer to a storage API instance. * @param path1 pointer to a zero-terminated string containing the first path. * @param path2 pointer to a zero-terminated string containing the second path. - * @param truncate whether to truncate path2 to be no longer than path1. * @return true if paths are equivalent, false otherwise. */ -bool storage_common_equivalent_path( - Storage* storage, - const char* path1, - const char* path2, - bool truncate); +bool storage_common_equivalent_path(Storage* storage, const char* path1, const char* path2); + +/** + * @brief Check whether a path is a subpath of another path. + * + * This function respects storage-defined equivalence rules + * (see `storage_common_equivalent_path`). + * + * @param storage pointer to a storage API instance. + * @param parent pointer to a zero-terminated string containing the parent path. + * @param child pointer to a zero-terminated string containing the child path. + * @return true if `child` is a subpath of `parent`, or if `child` is equivalent + * to `parent`; false otherwise. + */ +bool storage_common_is_subdir(Storage* storage, const char* parent, const char* child); /******************* Error Functions *******************/ diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 7803e8f6a2..ecc5330af5 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -493,13 +493,13 @@ FS_Error storage_common_rename(Storage* storage, const char* old_path, const cha } // Cannot rename a directory to itself or to a nested directory - if(storage_common_equivalent_path(storage, old_path, new_path, true)) { + if(storage_common_is_subdir(storage, old_path, new_path)) { error = FSE_INVALID_NAME; break; } // Renaming a regular file to itself does nothing and always succeeds - } else if(storage_common_equivalent_path(storage, old_path, new_path, false)) { + } else if(storage_common_equivalent_path(storage, old_path, new_path)) { error = FSE_OK; break; } @@ -816,11 +816,11 @@ bool storage_common_exists(Storage* storage, const char* path) { return storage_common_stat(storage, path, &file_info) == FSE_OK; } -bool storage_common_equivalent_path( +static bool storage_internal_equivalent_path( Storage* storage, const char* path1, const char* path2, - bool truncate) { + bool check_subdir) { furi_check(storage); S_API_PROLOGUE; @@ -829,7 +829,7 @@ bool storage_common_equivalent_path( .cequivpath = { .path1 = path1, .path2 = path2, - .truncate = truncate, + .check_subdir = check_subdir, .thread_id = furi_thread_get_current_id(), }}; @@ -839,6 +839,14 @@ bool storage_common_equivalent_path( return S_RETURN_BOOL; } +bool storage_common_equivalent_path(Storage* storage, const char* path1, const char* path2) { + return storage_internal_equivalent_path(storage, path1, path2, false); +} + +bool storage_common_is_subdir(Storage* storage, const char* parent, const char* child) { + return storage_internal_equivalent_path(storage, parent, child, true); +} + /****************** ERROR ******************/ const char* storage_error_get_desc(FS_Error error_id) { diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index cd45906d4a..ba34aa7c08 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -72,7 +72,7 @@ typedef struct { typedef struct { const char* path1; const char* path2; - bool truncate; + bool check_subdir; FuriThreadId thread_id; } SADataCEquivPath; diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 8d86dd3851..7a328051d2 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -694,7 +694,23 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { storage_path_trim_trailing_slashes(path2); storage_process_alias(app, path1, message->data->cequivpath.thread_id, false); storage_process_alias(app, path2, message->data->cequivpath.thread_id, false); - if(message->data->cequivpath.truncate) { + if(message->data->cequivpath.check_subdir) { + // by appending slashes at the end and then truncating the second path, we can + // effectively check for shared path components: + // example 1: + // path1: "/ext/blah" -> "/ext/blah/" -> "/ext/blah/" + // path2: "/ext/blah-blah" -> "/ect/blah-blah/" -> "/ext/blah-" + // results unequal, conclusion: path2 is not a subpath of path1 + // example 2: + // path1: "/ext/blah" -> "/ext/blah/" -> "/ext/blah/" + // path2: "/ext/blah/blah" -> "/ect/blah/blah/" -> "/ext/blah/" + // results equal, conclusion: path2 is a subpath of path1 + // example 3: + // path1: "/ext/blah/blah" -> "/ect/blah/blah/" -> "/ext/blah/blah/" + // path2: "/ext/blah" -> "/ext/blah/" -> "/ext/blah/" + // results unequal, conclusion: path2 is not a subpath of path1 + furi_string_push_back(path1, '/'); + furi_string_push_back(path2, '/'); furi_string_left(path2, furi_string_size(path1)); } message->return_data->bool_value = diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 553cf14720..1d9ac28698 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,73.0,, +Version,+,74.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -2491,9 +2491,10 @@ Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_ Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" -Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*, _Bool" +Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" +Function,+,storage_common_is_subdir,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_migrate,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5d5c2a707a..cb3037e48f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,73.0,, +Version,+,74.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -3168,9 +3168,10 @@ Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" Function,+,st25tb_verify,_Bool,"St25tbData*, const FuriString*" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" -Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*, _Bool" +Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" +Function,+,storage_common_is_subdir,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_migrate,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" From 913a86bbec7c9590692e00c1a7b0a74c36a4360c Mon Sep 17 00:00:00 2001 From: EntranceJew Date: Sun, 15 Sep 2024 10:07:04 -0500 Subject: [PATCH 048/236] kerel typo (#3901) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- furi/core/kernel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/furi/core/kernel.h b/furi/core/kernel.h index 2973a90e57..8362746e21 100644 --- a/furi/core/kernel.h +++ b/furi/core/kernel.h @@ -79,7 +79,7 @@ void furi_delay_tick(uint32_t ticks); * * @warning This should never be called in interrupt request context. * - * @param[in] tick The tick until which kerel should delay task execution + * @param[in] tick The tick until which kernel should delay task execution * * @return The furi status. */ From 18f8cfbef07ad8fd25f8e06e633176604ea82da1 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 16 Sep 2024 05:28:04 -0400 Subject: [PATCH 049/236] Do not reset the poller between collected nonces --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 260c41221e..0934a7765c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,10 +6,10 @@ #define TAG "MfClassicPoller" -// TODO: Reflect status in NFC app +// TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches -// TODO: Test not resetting the tag during Hardnested (single initial auth, repeated failed nested) // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary +// TODO: Validate Hardnested is collecting nonces from the correct block #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -553,7 +553,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* } NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { - NfcCommand command = NfcCommandReset; + NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; size_t current_key_index = @@ -1287,7 +1287,7 @@ static inline bool is_byte_found(uint8_t* found, uint8_t byte) { NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) // TODO: Look into using MfClassicNt more - NfcCommand command = NfcCommandReset; + NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { @@ -1512,7 +1512,7 @@ static MfClassicKey* search_dicts_for_nonce_key( NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) // TODO: Look into using MfClassicNt more - NfcCommand command = NfcCommandReset; + NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; do { From a5b3d71e5963876cc88bd4692b15cb2e56437cb1 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Mon, 16 Sep 2024 15:37:40 +0300 Subject: [PATCH 050/236] feat: cli icon on the desktop --- applications/services/cli/cli_vcp.c | 35 ++++++++++++++++++ assets/icons/StatusBar/Console_active_8x8.png | Bin 0 -> 4347 bytes 2 files changed, 35 insertions(+) create mode 100644 assets/icons/StatusBar/Console_active_8x8.png diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index cdabaaa054..9eabd4d7d8 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include #define TAG "CliVcp" @@ -43,6 +46,10 @@ typedef struct { FuriHalUsbInterface* usb_if_prev; uint8_t data_buffer[USB_CDC_PKT_LEN]; + + // CLI icon + Gui* gui; + ViewPort* view_port; } CliVcp; static int32_t vcp_worker(void* context); @@ -64,6 +71,13 @@ static CliVcp* vcp = NULL; static const uint8_t ascii_soh = 0x01; static const uint8_t ascii_eot = 0x04; +static void cli_vcp_icon_draw_callback(Canvas* canvas, void* context) { + furi_assert(canvas); + furi_assert(context); + const Icon* icon = context; + canvas_draw_icon(canvas, 0, 0, icon); +} + static void cli_vcp_init(void) { if(vcp == NULL) { vcp = malloc(sizeof(CliVcp)); @@ -115,6 +129,18 @@ static int32_t vcp_worker(void* context) { if(vcp->connected == false) { vcp->connected = true; furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); + + // GUI icon + furi_assert(!vcp->gui); + furi_assert(!vcp->view_port); + const Icon* icon = &I_Console_active_8x8; + vcp->gui = furi_record_open(RECORD_GUI); + vcp->view_port = view_port_alloc(); + view_port_set_width(vcp->view_port, icon_get_width(icon)); + // casting const away. we know that we cast it right back in the callback + view_port_draw_callback_set( + vcp->view_port, cli_vcp_icon_draw_callback, (void*)icon); + gui_add_view_port(vcp->gui, vcp->view_port, GuiLayerStatusBarLeft); } } @@ -126,6 +152,15 @@ static int32_t vcp_worker(void* context) { vcp->connected = false; furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); + + // remove GUI icon + furi_assert(vcp->gui); + furi_assert(vcp->view_port); + gui_remove_view_port(vcp->gui, vcp->view_port); + view_port_free(vcp->view_port); + furi_record_close(RECORD_GUI); + vcp->gui = NULL; + vcp->view_port = NULL; } } diff --git a/assets/icons/StatusBar/Console_active_8x8.png b/assets/icons/StatusBar/Console_active_8x8.png new file mode 100644 index 0000000000000000000000000000000000000000..0423c12563b7602ed5ca6b513b2ceb9eab3751f8 GIT binary patch literal 4347 zcmeHKeQZ-z6o2jb7-Qg^AYVgYIzU8T-$!4&_HBhhJ6Hpq1FEYZfUob}*FISLO8d5U z2pS0B_)x;YL39D4l1xQ|Mo>U;D09Tfha!-OAOS-Zi9{rTM49KkcHIahW{Lc_*WBLo z&OP^c?)lwwPkUEZR!kj`Gdu^wumPTO_e|)swU*rnz7O2DZ#9M)1|h#k@_5GSBmF_q z3p1qc3|(ZSAq&d_eFL+PPkM%{+H{O7^+F7VMYFE?1?;<-(&eMUtx# zh9#R8uXC1vSz6FK_nB?u1`jT-=fviU+8Ot^J-yuI%icNp-GO5^zA$P1iczhtGopn3 z#s~hI=zYhr2B$fiNF-#XAfdijI&Lj~8*pZ=`vbaBgE(sKM#z2O~c*xT-B z4~kXG&px*2+qHR9D4d?P<$=~wC2c32ixz#h`pWpr@fTm2Gdjq$Jd)S8DdImc;(+D# z#=~`6?BA?vn7pW>gg@GQ<-@zpd;k7w_t_m=f-7ED=WVu6A3x{^e?)ka^_`Q?)xR-s z!{sBNY>%Efx4rS*?RAge`l7PpnWrvrhh~i0Q9rsq4}Wu2{?|M5wmnhU>Rxs2{6n|+ zHnL&bn5uqj3i>yfF8}$){`RwpkEIvpedo<-U2=HHsar#{mhLTmQ#$oi+pg1ggYihq zt0%5hU2M7Z$gevaA9NUR?pm_?x5hD-4I7JFj;=QkYbfj;y7_0_*?-pTd$fP<8KZN4 zL^-s$X#yr+99NfBP+&aY`|CN^lZmS5mG;f@wd+}-DX}+BE^J;3d7PAee%0@t&IwA` z%!^6@nUmqDzKf^x)Zy1e&u6E2HRQy%-Evp5P z^n&g>BkrqX3zmDQq%TG$mrJ7*aLEu^3vj_N@HdAB(eg^XRaCRVN6hysbK zQ5Znuus-@WdsUV?$+~TA+K3)b*M)$)4E}A_9kJ`iVCD64ZbhikoO;|&leS(?R0LV% zbdeG)HY>%81aD&l1RD@V!r_qY1mj==HX*>U5+inj@j~GaCjl$um!A$ zpe;6 zizqu`W06IqDQh6WA{|QG2X0cO$7y2BRHvr0npY(y7IvDZ%aNL7=YUTRqgg7ikwzCW zHrDPavN#w^k&Q0uG$22f+Zbyq2ec ztm`2bPA{WM%%>>TPE)2fXNJ9SoJ3yb-B8uRTc%q3I;!GacOW~MUW1WLFPe-D03y#y#2fztNS`d7VNL z_!pFb%hJeCuX1qJ>JykY)t$OJ|K-~IB1rwBIguzx2z? zEhx5bpY|!JhN)$K)un{Bf92R6ckZes1BZYS>2Xi;4L$bWS~>@#a+iH{wSDfsirGeQ xBe!~MX~>_uD8J$A(wiHu5zXJfw(8gg!yjV@=5G4E?>d-@dCDr>?@xYi>A&8Jxyb+k literal 0 HcmV?d00001 From 15b271bd922ed0e315206492e67cc1469652aa4c Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Mon, 16 Sep 2024 16:27:24 +0300 Subject: [PATCH 051/236] fix: cli autolock inhibit --- applications/services/cli/cli_vcp.c | 43 +++++++++++-------- applications/services/desktop/desktop.c | 43 ++++++++++++++++++- applications/services/desktop/desktop.h | 14 ++++++ applications/services/desktop/desktop_i.h | 1 + .../services/desktop/views/desktop_events.h | 2 + scripts/serial_cli.py | 2 +- 6 files changed, 83 insertions(+), 22 deletions(-) diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index 9eabd4d7d8..a747102842 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -5,6 +5,7 @@ #include #include #include +#include #define TAG "CliVcp" @@ -50,6 +51,9 @@ typedef struct { // CLI icon Gui* gui; ViewPort* view_port; + + // Autolocking inhibition + Desktop* desktop; } CliVcp; static int32_t vcp_worker(void* context); @@ -117,6 +121,15 @@ static int32_t vcp_worker(void* context) { FURI_LOG_D(TAG, "Start"); vcp->running = true; + // GUI icon + vcp->desktop = furi_record_open(RECORD_DESKTOP); + const Icon* icon = &I_Console_active_8x8; + vcp->gui = furi_record_open(RECORD_GUI); + vcp->view_port = view_port_alloc(); + view_port_set_width(vcp->view_port, icon_get_width(icon)); + // casting const away. we know that we cast it right back in the callback + view_port_draw_callback_set(vcp->view_port, cli_vcp_icon_draw_callback, (void*)icon); + while(1) { uint32_t flags = furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); @@ -129,18 +142,8 @@ static int32_t vcp_worker(void* context) { if(vcp->connected == false) { vcp->connected = true; furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); - - // GUI icon - furi_assert(!vcp->gui); - furi_assert(!vcp->view_port); - const Icon* icon = &I_Console_active_8x8; - vcp->gui = furi_record_open(RECORD_GUI); - vcp->view_port = view_port_alloc(); - view_port_set_width(vcp->view_port, icon_get_width(icon)); - // casting const away. we know that we cast it right back in the callback - view_port_draw_callback_set( - vcp->view_port, cli_vcp_icon_draw_callback, (void*)icon); gui_add_view_port(vcp->gui, vcp->view_port, GuiLayerStatusBarLeft); + desktop_api_add_external_inhibitor(vcp->desktop); } } @@ -152,15 +155,8 @@ static int32_t vcp_worker(void* context) { vcp->connected = false; furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); - - // remove GUI icon - furi_assert(vcp->gui); - furi_assert(vcp->view_port); gui_remove_view_port(vcp->gui, vcp->view_port); - view_port_free(vcp->view_port); - furi_record_close(RECORD_GUI); - vcp->gui = NULL; - vcp->view_port = NULL; + desktop_api_remove_external_inhibitor(vcp->desktop); } } @@ -225,6 +221,10 @@ static int32_t vcp_worker(void* context) { } if(flags & VcpEvtStop) { + if(vcp->connected) { + gui_remove_view_port(vcp->gui, vcp->view_port); + desktop_api_remove_external_inhibitor(vcp->desktop); + } vcp->connected = false; vcp->running = false; furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); @@ -238,6 +238,11 @@ static int32_t vcp_worker(void* context) { break; } } + + view_port_free(vcp->view_port); + furi_record_close(RECORD_DESKTOP); + furi_record_close(RECORD_GUI); + FURI_LOG_D(TAG, "End"); return 0; } diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index e57e1eb00f..93d00c78dc 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -19,6 +19,8 @@ static void desktop_auto_lock_arm(Desktop*); static void desktop_auto_lock_inhibit(Desktop*); static void desktop_start_auto_lock_timer(Desktop*); static void desktop_apply_settings(Desktop*); +static void desktop_auto_lock_add_inhibitor(Desktop* desktop); +static void desktop_auto_lock_remove_inhibitor(Desktop* desktop); static void desktop_loader_callback(const void* message, void* context) { furi_assert(context); @@ -130,16 +132,22 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { animation_manager_unload_and_stall_animation(desktop->animation_manager); } - desktop_auto_lock_inhibit(desktop); + desktop_auto_lock_add_inhibitor(desktop); desktop->app_running = true; furi_semaphore_release(desktop->animation_semaphore); } else if(event == DesktopGlobalAfterAppFinished) { animation_manager_load_and_continue_animation(desktop->animation_manager); - desktop_auto_lock_arm(desktop); + desktop_auto_lock_remove_inhibitor(desktop); desktop->app_running = false; + } else if(event == DesktopGlobalAddExternalInhibitor) { + desktop_auto_lock_add_inhibitor(desktop); + + } else if(event == DesktopGlobalRemoveExternalInhibitor) { + desktop_auto_lock_remove_inhibitor(desktop); + } else if(event == DesktopGlobalAutoLock) { if(!desktop->app_running && !desktop->locked) { desktop_lock(desktop); @@ -205,6 +213,24 @@ static void desktop_auto_lock_arm(Desktop* desktop) { } } +static void desktop_auto_lock_add_inhibitor(Desktop* desktop) { + furi_check(furi_semaphore_release(desktop->auto_lock_inhibitors) == FuriStatusOk); + FURI_LOG_D( + TAG, + "%lu autolock inhibitors (+1)", + furi_semaphore_get_count(desktop->auto_lock_inhibitors)); + desktop_auto_lock_inhibit(desktop); +} + +static void desktop_auto_lock_remove_inhibitor(Desktop* desktop) { + furi_check(furi_semaphore_acquire(desktop->auto_lock_inhibitors, 0) == FuriStatusOk); + uint32_t inhibitors = furi_semaphore_get_count(desktop->auto_lock_inhibitors); + FURI_LOG_D(TAG, "%lu autolock inhibitors (-1)", inhibitors); + if(inhibitors == 0) { + desktop_auto_lock_arm(desktop); + } +} + static void desktop_auto_lock_inhibit(Desktop* desktop) { desktop_stop_auto_lock_timer(desktop); if(desktop->input_events_subscription) { @@ -371,6 +397,7 @@ static Desktop* desktop_alloc(void) { desktop->notification = furi_record_open(RECORD_NOTIFICATION); desktop->input_events_pubsub = furi_record_open(RECORD_INPUT_EVENTS); + desktop->auto_lock_inhibitors = furi_semaphore_alloc(UINT32_MAX, 0); desktop->auto_lock_timer = furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop); @@ -503,6 +530,18 @@ void desktop_api_set_settings(Desktop* instance, const DesktopSettings* settings view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopGlobalSaveSettings); } +void desktop_api_add_external_inhibitor(Desktop* instance) { + furi_assert(instance); + view_dispatcher_send_custom_event( + instance->view_dispatcher, DesktopGlobalAddExternalInhibitor); +} + +void desktop_api_remove_external_inhibitor(Desktop* instance) { + furi_assert(instance); + view_dispatcher_send_custom_event( + instance->view_dispatcher, DesktopGlobalRemoveExternalInhibitor); +} + /* * Application thread */ diff --git a/applications/services/desktop/desktop.h b/applications/services/desktop/desktop.h index e83bc3ee4d..4f1556f7c7 100644 --- a/applications/services/desktop/desktop.h +++ b/applications/services/desktop/desktop.h @@ -21,3 +21,17 @@ FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance); void desktop_api_get_settings(Desktop* instance, DesktopSettings* settings); void desktop_api_set_settings(Desktop* instance, const DesktopSettings* settings); + +/** + * @brief Adds 1 to the count of active external autolock inhibitors + * + * Autolocking will not get triggered while there's at least 1 inhibitor + */ +void desktop_api_add_external_inhibitor(Desktop* instance); + +/** + * @brief Removes 1 from the count of active external autolock inhibitors + * + * Autolocking will not get triggered while there's at least 1 inhibitor + */ +void desktop_api_remove_external_inhibitor(Desktop* instance); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index 1dc7c7d219..b62dac63cb 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -73,6 +73,7 @@ struct Desktop { FuriPubSub* input_events_pubsub; FuriPubSubSubscription* input_events_subscription; + FuriSemaphore* auto_lock_inhibitors; FuriTimer* auto_lock_timer; FuriTimer* update_clock_timer; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index 07631dfac2..94348184d1 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -57,4 +57,6 @@ typedef enum { DesktopGlobalApiUnlock, DesktopGlobalSaveSettings, DesktopGlobalReloadSettings, + DesktopGlobalAddExternalInhibitor, + DesktopGlobalRemoveExternalInhibitor, } DesktopEvent; diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index 8e35d57fac..095b5dd35b 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -13,7 +13,7 @@ def main(): parser.add_argument("-p", "--port", help="CDC Port", default="auto") args = parser.parse_args() if not (port := resolve_port(logger, args.port)): - logger.error("Is Flipper connected via USB and not in DFU mode?") + logger.error("Is Flipper connected via USB, currently unlocked and not in DFU mode?") return 1 subprocess.call( [ From 1a9aca2d8c6e1c8625ea5200423844973621a696 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Mon, 16 Sep 2024 17:11:31 +0300 Subject: [PATCH 052/236] fix: always autolock if pin set --- applications/services/desktop/desktop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 93d00c78dc..ceea867ba6 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -562,7 +562,7 @@ int32_t desktop_srv(void* p) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { + if(desktop_pin_code_is_set()) { desktop_lock(desktop); } From 2af87cdb7029edc09a2260bbe4e239b74dbf5b49 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:01:38 +0300 Subject: [PATCH 053/236] hollarm button codes and gangqi validator allow more serials [ci skip] --- lib/subghz/protocols/gangqi.c | 4 ++-- lib/subghz/protocols/hollarm.c | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 9cbb717635..55998dd2bc 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -496,8 +496,8 @@ void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output bool serial_is_valid = (((!(sum_3bytes_serial & 0x3)) && ((0xB < sum_3bytes_serial) && (sum_3bytes_serial < 0x141))) && - ((((instance->generic.serial >> 16) & 0xFF) == 0x2) || - (((instance->generic.serial >> 16) & 0xFF) == 0x3))); + ((((instance->generic.serial >> 16) & 0xFF) >= 0x0) || + (((instance->generic.serial >> 16) & 0xFF) <= 0x3))); furi_string_cat_printf( output, diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index 5cf5a6ef83..ed94cb7a9a 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -402,16 +402,16 @@ static const char* subghz_protocol_hollarm_get_button_name(uint8_t btn) { "Disarm", // B (2) "Arm", // A (1) "0x3", - "Alarm", // C (3) + "Ringtone/Alarm", // C (3) "0x5", "0x6", "0x7", "Ring", // D (4) - "0x9", - "0xA", - "0xB", - "0xC", - "0xD", + "Settings mode", + "Exit settings", + "Vibro sens. setting", + "Not used\n(in settings)", + "Volume setting", "0xE", "0xF"}; return btn <= 0xf ? name_btn[btn] : name_btn[0]; From 282638297045c1056923a346aa7ec9e7971aa9f9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:31:02 +0300 Subject: [PATCH 054/236] fix check [ci skip] --- lib/subghz/protocols/gangqi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 55998dd2bc..56d39c2cd2 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -496,8 +496,7 @@ void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output bool serial_is_valid = (((!(sum_3bytes_serial & 0x3)) && ((0xB < sum_3bytes_serial) && (sum_3bytes_serial < 0x141))) && - ((((instance->generic.serial >> 16) & 0xFF) >= 0x0) || - (((instance->generic.serial >> 16) & 0xFF) <= 0x3))); + (((instance->generic.serial >> 16) & 0xFF) <= 0x3)); furi_string_cat_printf( output, From 31df2c065511765c99ff94a6027d436839162797 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 16 Sep 2024 19:17:05 +0300 Subject: [PATCH 055/236] upd changelog --- CHANGELOG.md | 46 +++---------------- applications/main/gpio/gpio_custom_event.h | 1 - .../main/gpio/scenes/gpio_scene_usb_uart.c | 5 -- 3 files changed, 7 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca8f32a61..e6c6a456dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,46 +1,14 @@ ## Main changes - SubGHz: - - Add new protocols (by @xMasterX) (big thanks to @Skorpionm for help with GangQi and Hollarm protocols!): - - Marantec24 (static 24 bit) with add manually support - - GangQi (static 34 bit) with button parsing and add manually support (thanks to @mishamyte for captures and testing) - - Hollarm (static 42 bit) with button parsing and add manually support (thanks to @mishamyte for captures) - - Hay21 (dynamic 21 bit) with button parsing - - Princeton custom buttons support (0x1, 0x2, 0x4, 0x8, 0xF) -- 125kHz RFID: - - OFW: Fix detection of GProx II cards and false detection of other cards (by @Astrrra) - - OFW: Fix Guard GProxII False Positive and 36-bit Parsing (by @zinongli) - - OFW: GProxII Fix Writing and Rendering Conflict -- NFC: - - Saflok parser improvements (by @zinongli & @xtruan & @zacharyweiss & @evilmog & @Arkwin) - - OFW: Fix crash on Ultralight unlock (by @Astrrra) - - OFW: FeliCa anti-collision fix -* OFW: Rename 'Detect Reader' to 'Extract MF Keys' -* OFW: Happy mode -* OFW: Infrared: Universal AC - Add Airwell AW-HKD012-N91 -* OFW: Broken file interaction fixes -* OFW: Add the Procrastination animation -* OFW PR 3892: Fix USB-UART bridge exit screen stopping the bridge prematurely (by @portasynthinca3) + - Protocol improvements: + - GangQi fix serial check + - Hollarm add more button codes (thanks to @mishamyte for captures) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* Docs: Improved the description steps to create a new remote BFT Mitto with more detailed and accurate instructions (by @chrostino | PR #805) -* OFW: FuriTimer: Use an event instead of a volatile bool to wait for deletion -* OFW: Threading, Timers improvements -* OFW: Replace all calls to strncpy with strlcpy, use strdup more, expose strlcat -* OFW: feat: add linux/gnome badusb demo resource files -* OFW: Exposed `view_dispatcher_get_event_loop` -* OFW: Infrared button operation fails now shows more informative messages -* OFW: Loader: Warn about missing SD card for main apps -* OFW: Desktop: Sanity check PIN length for good measure -* OFW: DialogEx: Fix NULL ptr crash -* OFW: Debug: use proper hook for handle_exit in flipperapps -* OFW: Clean up of LFS traces -* OFW: Proper integer parsing -* OFW: SubGhz: Fix RPC status for ButtonRelease event -* OFW: CCID: App changes -* OFW: 5V on GPIO control for ext. modules -* OFW: Gui: Add up and down button drawing functions to GUI elements -* OFW: Gui: change dialog_ex text ownership model -* OFW: Publishing T5577 page 1 block count macro +* OFW: kerel typo +* OFW: Folder rename fails +* OFW: Put errno into TCB +* OFW: Fix USB-UART bridge exit screen stopping the bridge prematurely

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) diff --git a/applications/main/gpio/gpio_custom_event.h b/applications/main/gpio/gpio_custom_event.h index b18ad698a1..72b8feccd0 100644 --- a/applications/main/gpio/gpio_custom_event.h +++ b/applications/main/gpio/gpio_custom_event.h @@ -10,5 +10,4 @@ typedef enum { GpioUsbUartEventConfig, GpioUsbUartEventConfigSet, - GpioUsbUartEventStop, } GpioCustomEvent; diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart.c b/applications/main/gpio/scenes/gpio_scene_usb_uart.c index fb113ff458..e3e7e8c24a 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart.c @@ -8,11 +8,6 @@ typedef struct { static SceneUsbUartBridge* scene_usb_uart = NULL; -typedef enum { - UsbUartSceneStateInitialize, - UsbUartSceneStateKeep, -} UsbUartSceneState; - void gpio_scene_usb_uart_callback(GpioCustomEvent event, void* context) { furi_assert(context); GpioApp* app = context; From 4a8202514d7b6b6695667c083cd32b611d40f7c5 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Mon, 16 Sep 2024 19:29:55 +0300 Subject: [PATCH 056/236] style: fix linter errors --- assets/icons/StatusBar/Console_active_8x8.png | Bin 4347 -> 4559 bytes scripts/serial_cli.py | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/icons/StatusBar/Console_active_8x8.png b/assets/icons/StatusBar/Console_active_8x8.png index 0423c12563b7602ed5ca6b513b2ceb9eab3751f8..794f4e59726186ddc3c0998ba81fe8814c86ad72 100644 GIT binary patch delta 703 zcmeyZcwTve1Scai0|UdqgE}WBD&{dVPn_V)`$v<3AsQ$MrYAm-Ww*34va~X`oXo;# zSsz^zBE`VK*qZ6=9N_8ftWZ#tpO%@E%D_-Dr*@*P$6*JFw)@+>LbbTW1WQi@%zd*+ zsc$Qn>k5Ia)-YKgy}!+Byq;a%iyuT4d`RE;>cOKmtD9FVaQthqu(r_L<}s6DjY_wM22 z-?3BDE3f~xteQIKB*T0MndDuGg82gHbE2%C*FG}P_Tu_z`LV@OT&3>Gt~Z~gpEn(@ znl6k_iIMw`yX6FQ%Jq|Zt%e{Y>_5SvL z)3g8nGSuxZTD>ky{4N6nU-agyOkGSWCTSKaNtVV2x|U|C2D&C@h8DU>$p#j>rYUL3 ziOI=^=H_XMlbc!lxlIjB3@r@I%q$GeCd;z*!J~N-n>viW`2m|C<77Ve7?tFdG?UcC zRAXHO!xUp(6AKF?-9$@+L|vn#R3l3>OCv)QW24D^?0yiPCI*`yv*&Rr^L>-v2TVz9 z0X`wFK>Gjx|4VPq2fCGkG0EHAg`tC0)?;$IfFiqqtj1o`MDxl00vhTXJIEZ3k6x-JDr((fm9oOGEKu-x zRI5@5$5yA+d}`C{94EJ_96cIXplk7CV?=2|;kBy=Lj_pu9p=c+5s6)LYEP6RcVqdU1Cn8gttDZ*WPupU%<^k!7zBf@KF_;DRW#?$^`Q-ZM_OK5${O zp5!ED){I}DDaIzd*A3T;fv#G<_n;)_XGH$-gk-#A)yXT7)Fio%} zdAqv+X(0INyt{sKgrFijkBqhH8t*-m8w536^gUf1LnJP*?Kk9NFyLv~^5uX0KEGHd x-)UCe3mh^ccxr?ueQEkN<(uw`E3>*TKV$r;!N)V>9s5L(fu62@F6*2UngGI64HW Date: Tue, 17 Sep 2024 13:26:28 +0300 Subject: [PATCH 057/236] style: fix linter error(?) --- assets/icons/StatusBar/Console_active_8x8.png | Bin 4559 -> 75 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/icons/StatusBar/Console_active_8x8.png b/assets/icons/StatusBar/Console_active_8x8.png index 794f4e59726186ddc3c0998ba81fe8814c86ad72..f2230a374fc93be0309b026550135d2c21c155fb 100644 GIT binary patch delta 56 zcmX@F>^(u!hye)Rlr*aWDIrf6$B>FS$q5Zi>};%Rwrp$+%uEaub)uI30;%$J^>bP0 Hl+XkKg!m2J literal 4559 zcmeHLYitx%6rL^8LP@b84*_e&K}?Ix&SPh%Gh>%hb_-o(aV?9KhlbgiJKKTXon>~~ zZZQ%BK`Wq$ijp804WeKmAcBH|icyR)K7ObK6a=EE2)=?31Uxgl-L}ERG|Iou=Jwt> z=iKj}^WAgL_HJ8v%EXf5;l&t+l?3Yo_0adRo2wW6z4+#sgBa!-gc^ccFgV)L^aH_F zm@8-J>NGBZIrgm8(E7Uyuz}D&32h+AZF{>7clCDq%`hI38}EQ|dCtb3&%oRg==VTl zVLS+Wa0Aot&#rp}#`e?4`S?vFTS&!QbgL1c7z~B6zbY_n7UNCpy^=vsngF854~Jm`+Vu#rFWD~c3)U>Q%Uf_<Fkj9;a^UWx=gr^Bj%|Bl?cC65 zei)xAeRI!o`sPW+KZ!rxS2Og+g(vr%{&~bViw`VZKaBt6h1TQr_Hd)}>dF}(hub!_ zjeKzP?fa@v?0Bnn`Aajm?)cjGM?b%qKx@C>IpL8vh&A~5MJq;6DnBs(vF4S7uBnh7 zzWew4%l6l~*UlbAn`53X7`*yK(WNCvU-$kp_0eQ~d4Fr)=jYzNw)9Bv;oid+$L{*! zoxY`+!mT^k?5{7Otg6nWWMv+*@F&*^??aF2zc`mjh2;?D5|BU#ogj@8)l4RL{X$@mSR~FEXZ_g z(vmY|GCk6U$YKOgS~2yMr5j1y#+0K*ljS1_(Btj?B~qbK9zL1Qu>gEf897BUZkkFY zsLnIeR&6tY+YZ|PbGSzUayHBLC49f9TY_+6~qV|R?fDio3ehSFp&8VvXd z+h0jF6kU~^A+30LFDfLqikd*OA{*tED9dTA+6gL{Oj~kNK{gbCyLG_f zG(nAu9-0(+M3WrP2xL^D1=6c(imWJ1Wu+!}g1F7pAuHv0XRmB16`)v6)-*4}le9pK zB*$(B5d2Lk^U^4^mAy=p$G+-609bWlJ;6gpZi4Cz~=Iiin;-Q!Uvh zjj3cIVvZ4Lo);Ltvcu>OWTqkEY)q$;IvQ1xCIF%giK8dv7@|_in4_?_MS@^}SlP}~ zKz8I1i{#W%!;Bb4+(+b#b6y>SDUEV6hM;=n z(zRW#|3fMujb?b3lSzSB?QHUTNkQWT5^*f6@nWSwv)QQf=xIZmtxG|z6{G- zM(l&zWMO!6q!#*1mo9BQv;i)jm@9hg!~pg{@3K00>6@yXk;X9hNV~Z*8>^c^SY!o5 zwMD1;mX?;4zwVVQK{U*&ZLs`C!oG!Im;J~v{oejV!DwJGP!s9%nKEZlK~Z7v!s^08 Ytk+CzTk_ Date: Tue, 17 Sep 2024 14:38:14 -0400 Subject: [PATCH 058/236] Clean up various issues --- applications/main/nfc/views/dict_attack.c | 1 + applications/main/nfc/views/dict_attack.h | 1 + .../protocols/mf_classic/mf_classic_poller.c | 74 ++++++++++++------- .../mf_classic/mf_classic_poller_i.h | 1 + 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 2fae91694e..c0cc3802be 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -47,6 +47,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { furi_string_set(m->header, "Nested Dictionary"); break; case MfClassicNestedPhaseCalibrate: + case MfClassicNestedPhaseRecalibrate: furi_string_set(m->header, "Calibration"); break; case MfClassicNestedPhaseCollectNtEnc: diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index c2a7b1e68d..d28188fbc6 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -15,6 +15,7 @@ typedef enum { MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseRecalibrate, MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseFinished, } MfClassicNestedPhase; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 0934a7765c..75139faf43 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -10,6 +10,7 @@ // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary // TODO: Validate Hardnested is collecting nonces from the correct block +// TODO: Nested entrypoint for cached keys #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -553,7 +554,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* } NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { - NfcCommand command = NfcCommandContinue; + NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; size_t current_key_index = @@ -1152,11 +1153,13 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) command = NfcCommandReset; uint8_t target_block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->nested_target_key / 4); + MfClassicKeyType target_key_type = + ((dict_attack_ctx->nested_target_key % 4) < 2) ? MfClassicKeyTypeA : MfClassicKeyTypeB; error = mf_classic_poller_auth_nested( instance, target_block, &dict_attack_ctx->nested_known_key, - dict_attack_ctx->nested_known_key_type, + target_key_type, &auth_ctx, use_backdoor, false); @@ -1231,7 +1234,6 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc( cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); - // TODO: Make sure we're not off-by-one here for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); if(nth_successor != decrypted_nt_enc) { @@ -1305,11 +1307,12 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst (is_weak && !(dict_attack_ctx->static_encrypted)) ? ((dict_attack_ctx->attempt_count + 2) + nonce_pair_index) : 1; - MfClassicKeyType target_key_type = ((dict_attack_ctx->nested_target_key & 0x03) < 2) ? - MfClassicKeyTypeA : - MfClassicKeyTypeB; - // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? Match calibrated? - uint8_t target_block = (4 * (dict_attack_ctx->nested_target_key / 4)) + 3; + uint8_t target_sector = dict_attack_ctx->nested_target_key / (is_weak ? 4 : 2); + MfClassicKeyType target_key_type = + (dict_attack_ctx->nested_target_key % (is_weak ? 4 : 2) < (is_weak ? 2 : 1)) ? + MfClassicKeyTypeA : + MfClassicKeyTypeB; + uint8_t target_block = mf_classic_get_sector_trailer_num_by_sector(target_sector); uint32_t nt_enc_temp_arr[nt_enc_per_collection]; uint8_t nt_enc_collected = 0; uint8_t parity = 0; @@ -1510,7 +1513,7 @@ static MfClassicKey* search_dicts_for_nonce_key( } NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instance) { - // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_dict_target_key) + // TODO: Handle when nonce is not collected (retry counter? Do not increment nested_target_key) // TODO: Look into using MfClassicNt more NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; @@ -1532,9 +1535,8 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc ((!is_weak) && ((dict_attack_ctx->nested_target_key % 16) < 8))) ? MfClassicKeyTypeA : MfClassicKeyTypeB; - // TODO: mf_classic_get_sector_trailer_num_by_sector or mf_classic_get_sector_trailer_num_by_block? - uint8_t target_block = (is_weak) ? (4 * (dict_attack_ctx->nested_target_key / 2)) + 3 : - (4 * (dict_attack_ctx->nested_target_key / 16)) + 3; + uint8_t target_sector = dict_attack_ctx->nested_target_key / (is_weak ? 2 : 16); + uint8_t target_block = mf_classic_get_sector_trailer_num_by_sector(target_sector); uint8_t parity = 0; if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 0)) || @@ -1764,8 +1766,7 @@ bool is_valid_sum(uint16_t sum) { } NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance) { - // Iterate through keys - //NfcCommand command = NfcCommandContinue; + // This function guides the nested attack through its phases, and iterates over the target keys NfcCommand command = mf_classic_poller_handle_data_update(instance); MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; bool initial_dict_attack_iter = false; @@ -1795,6 +1796,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance initial_dict_attack_iter = true; } } + // Identify PRNG type if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseAnalyzePRNG) { if(dict_attack_ctx->nested_nonce.count < MF_CLASSIC_NESTED_ANALYZE_NT_COUNT) { instance->state = MfClassicPollerStateNestedCollectNt; @@ -1896,6 +1898,10 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance instance->state = MfClassicPollerStateSuccess; return command; } + if(dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) { + // Skip initial calibration for static encrypted backdoored tags + dict_attack_ctx->calibrated = true; + } dict_attack_ctx->nested_phase = MfClassicNestedPhaseCalibrate; instance->state = MfClassicPollerStateNestedController; return command; @@ -1919,24 +1925,23 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Calibration bool initial_collect_nt_enc_iter = false; + bool recalibrated = false; if(!(dict_attack_ctx->calibrated)) { if(dict_attack_ctx->prng_type == MfClassicPrngTypeWeak) { instance->state = MfClassicPollerStateNestedCalibrate; return command; } initial_collect_nt_enc_iter = true; - dict_attack_ctx->auth_passed = true; dict_attack_ctx->calibrated = true; - dict_attack_ctx->current_key_checked = false; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCalibrate) { initial_collect_nt_enc_iter = true; - dict_attack_ctx->auth_passed = true; - dict_attack_ctx->current_key_checked = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; + } else if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseRecalibrate) { + recalibrated = true; dict_attack_ctx->nested_phase = MfClassicNestedPhaseCollectNtEnc; } // Collect and log nonces - // TODO: Calibrates too frequently for static encrypted backdoored tags if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseCollectNtEnc) { if(((is_weak) && (dict_attack_ctx->nested_nonce.count == 2)) || ((is_weak) && (dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && @@ -1970,10 +1975,13 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); return command; } - if(!(dict_attack_ctx->auth_passed)) { + if(initial_collect_nt_enc_iter) { + dict_attack_ctx->current_key_checked = false; + } + if(!(dict_attack_ctx->auth_passed) && !(initial_collect_nt_enc_iter)) { dict_attack_ctx->attempt_count++; } else { - if(is_weak && !(initial_collect_nt_enc_iter)) { + if(is_weak && !(initial_collect_nt_enc_iter) && !(recalibrated)) { if(!(dict_attack_ctx->static_encrypted)) { dict_attack_ctx->nested_target_key++; } else { @@ -1982,28 +1990,36 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance if(dict_attack_ctx->nested_target_key % 2 == 0) { dict_attack_ctx->current_key_checked = false; } - if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && - (dict_attack_ctx->nested_target_key % 4 == 0) && - (dict_attack_ctx->nested_target_key < nonce_collect_key_max)) { - dict_attack_ctx->calibrated = false; - } } dict_attack_ctx->attempt_count = 0; } dict_attack_ctx->auth_passed = true; if(!(dict_attack_ctx->current_key_checked)) { + dict_attack_ctx->current_key_checked = true; + // Check if the nested target key is a known key if(mf_classic_nested_is_target_key_found(instance, false)) { // Continue to next key - if(!(is_weak)) { + if(!(dict_attack_ctx->static_encrypted)) { dict_attack_ctx->nested_target_key++; dict_attack_ctx->current_key_checked = false; } instance->state = MfClassicPollerStateNestedController; return command; } - dict_attack_ctx->current_key_checked = true; + + // If it is not a known key, we'll need to calibrate for static encrypted backdoored tags + if((dict_attack_ctx->backdoor == MfClassicBackdoorAuth3) && + (dict_attack_ctx->nested_target_key < nonce_collect_key_max) && + !(recalibrated)) { + dict_attack_ctx->calibrated = false; + dict_attack_ctx->nested_phase = MfClassicNestedPhaseRecalibrate; + instance->state = MfClassicPollerStateNestedController; + return command; + } } + + // If we have tried to collect this nonce too many times, skip if((is_weak && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_RETRY_MAXIMUM)) || (!(is_weak) && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) { @@ -2025,6 +2041,8 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } dict_attack_ctx->attempt_count = 0; } + + // Collect a nonce dict_attack_ctx->auth_passed = false; instance->state = MfClassicPollerStateNestedCollectNtEnc; return command; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 111ddf2602..71c972c1a0 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -54,6 +54,7 @@ typedef enum { MfClassicNestedPhaseDictAttack, MfClassicNestedPhaseDictAttackResume, MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseRecalibrate, MfClassicNestedPhaseCollectNtEnc, MfClassicNestedPhaseFinished, } MfClassicNestedPhase; From 8eae5c06082aa7cdd4e257652341165877633267 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 17 Sep 2024 17:07:31 -0400 Subject: [PATCH 059/236] Fix Hardnested sector/key type logging --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 75139faf43..7aab12b538 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -9,8 +9,6 @@ // TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary -// TODO: Validate Hardnested is collecting nonces from the correct block -// TODO: Nested entrypoint for cached keys #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -1672,11 +1670,16 @@ NfcCommand mf_classic_poller_handler_nested_log(MfClassicPoller* instance) { bool params_write_success = true; for(size_t i = 0; i < nonce_pair_count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; + // TODO: Avoid repeating logic here + uint8_t nonce_sector = nonce->key_idx / (weak_prng ? 4 : 2); + MfClassicKeyType nonce_key_type = + (nonce->key_idx % (weak_prng ? 4 : 2) < (weak_prng ? 2 : 1)) ? MfClassicKeyTypeA : + MfClassicKeyTypeB; furi_string_printf( temp_str, "Sec %d key %c cuid %08lx", - (nonce->key_idx / 4), - ((nonce->key_idx & 0x03) < 2) ? 'A' : 'B', + nonce_sector, + (nonce_key_type == MfClassicKeyTypeA) ? 'A' : 'B', nonce->cuid); for(uint8_t nt_idx = 0; nt_idx < ((weak_prng && (!(static_encrypted))) ? 2 : 1); nt_idx++) { From d8864a490b75d121ee1fe1be652d542623043fba Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 17 Sep 2024 17:40:17 -0400 Subject: [PATCH 060/236] Add nested_target_key 64 to TODO --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 7aab12b538..aac9f228b3 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -9,6 +9,7 @@ // TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary +// TODO: Fix rare nested_target_key 64 bug #define MF_CLASSIC_MAX_BUFF_SIZE (64) From c1cdd491a6877bbf50a06eb955c1329e99d85848 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 18 Sep 2024 12:51:48 -0400 Subject: [PATCH 061/236] Implement progress bar for upgraded attacks in NFC app --- applications/main/nfc/nfc_app_i.h | 2 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 6 +- applications/main/nfc/views/dict_attack.c | 82 +++++++++++++++---- applications/main/nfc/views/dict_attack.h | 4 + .../protocols/mf_classic/mf_classic_poller.c | 17 ++-- .../protocols/mf_classic/mf_classic_poller.h | 3 + .../mf_classic/mf_classic_poller_i.h | 1 + 7 files changed, 88 insertions(+), 27 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 5310b89791..4aacdd19b7 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -100,6 +100,8 @@ typedef struct { uint8_t nested_phase; uint8_t prng_type; uint8_t backdoor; + uint16_t nested_target_key; + uint16_t msb_count; } NfcMfClassicDictAttackContext; struct NfcApp { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index b660520a3c..3d902fb582 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -5,7 +5,7 @@ #define TAG "NfcMfClassicDictAttack" -// TODO: Update progress bar with nested attacks +// TODO: Fix lag when leaving the dictionary attack view during Hardnested typedef enum { DictAttackStateUserDictInProgress, @@ -63,6 +63,8 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) instance->nfc_dict_context.nested_phase = data_update->nested_phase; instance->nfc_dict_context.prng_type = data_update->prng_type; instance->nfc_dict_context.backdoor = data_update->backdoor; + instance->nfc_dict_context.nested_target_key = data_update->nested_target_key; + instance->nfc_dict_context.msb_count = data_update->msb_count; view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { @@ -125,6 +127,8 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { dict_attack_set_nested_phase(instance->dict_attack, mfc_dict->nested_phase); dict_attack_set_prng_type(instance->dict_attack, mfc_dict->prng_type); dict_attack_set_backdoor(instance->dict_attack, mfc_dict->backdoor); + dict_attack_set_nested_target_key(instance->dict_attack, mfc_dict->nested_target_key); + dict_attack_set_msb_count(instance->dict_attack, mfc_dict->msb_count); } } diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index c0cc3802be..5138dd912a 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -24,6 +24,8 @@ typedef struct { MfClassicNestedPhase nested_phase; MfClassicPrngType prng_type; MfClassicBackdoor backdoor; + uint16_t nested_target_key; + uint16_t msb_count; } DictAttackViewModel; static void dict_attack_draw_callback(Canvas* canvas, void* model) { @@ -71,7 +73,12 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { canvas_draw_str_aligned( canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); - if(m->is_key_attack) { + if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + uint8_t nonce_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); + snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + } else if(m->is_key_attack) { snprintf( draw_str, sizeof(draw_str), @@ -81,21 +88,47 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); } canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - float dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); - float progress = m->sectors_total == 0 ? 0 : - ((float)(m->current_sector) + dict_progress) / - (float)(m->sectors_total); - if(progress > 1.0f) { - progress = 1.0f; - } - if(m->dict_keys_current == 0) { - // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + float dict_progress = 0; + if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || + m->nested_phase == MfClassicNestedPhaseDictAttack || + m->nested_phase == MfClassicNestedPhaseDictAttackResume) { + // Phase: Nested dictionary attack + uint8_t target_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else if( + m->nested_phase == MfClassicNestedPhaseCalibrate || + m->nested_phase == MfClassicNestedPhaseRecalibrate || + m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + // Phase: Nonce collection + if(m->prng_type == MfClassicPrngTypeWeak) { + uint8_t target_sector = m->nested_target_key / 4; + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else { + uint16_t max_msb = UINT8_MAX + 1; + dict_progress = (float)(m->msb_count) / (float)(max_msb); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); + } } else { - snprintf( - draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, + sizeof(draw_str), + "%zu/%zu", + m->dict_keys_current, + m->dict_keys_total); + } + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; } elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); canvas_set_font(canvas, FontSecondary); @@ -170,6 +203,8 @@ void dict_attack_reset(DictAttack* instance) { model->nested_phase = MfClassicNestedPhaseNone; model->prng_type = MfClassicPrngTypeUnknown; model->backdoor = MfClassicBackdoorUnknown; + model->nested_target_key = 0; + model->msb_count = 0; furi_string_reset(model->header); }, false); @@ -301,3 +336,20 @@ void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor) { with_view_model( instance->view, DictAttackViewModel * model, { model->backdoor = backdoor; }, true); } + +void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t nested_target_key) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->nested_target_key = nested_target_key; }, + true); +} + +void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index d28188fbc6..8dc9b9708a 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -77,6 +77,10 @@ void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type); void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor); +void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key); + +void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index aac9f228b3..cf61db09ff 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -6,7 +6,6 @@ #define TAG "MfClassicPoller" -// TODO: Reflect status in NFC app (second text line, progress bar) // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary // TODO: Fix rare nested_target_key 64 bug @@ -97,6 +96,8 @@ static NfcCommand mf_classic_poller_handle_data_update(MfClassicPoller* instance data_update->nested_phase = instance->mode_ctx.dict_attack_ctx.nested_phase; data_update->prng_type = instance->mode_ctx.dict_attack_ctx.prng_type; data_update->backdoor = instance->mode_ctx.dict_attack_ctx.backdoor; + data_update->nested_target_key = instance->mode_ctx.dict_attack_ctx.nested_target_key; + data_update->msb_count = instance->mode_ctx.dict_attack_ctx.msb_count; instance->mfc_event.type = MfClassicPollerEventTypeDataUpdate; return instance->callback(instance->general_event, instance->context); } @@ -1415,6 +1416,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Hardnested if(!is_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF)) { set_byte_found(dict_attack_ctx->nt_enc_msb, (nt_enc >> 24) & 0xFF); + dict_attack_ctx->msb_count++; // Add unique parity to sum dict_attack_ctx->msb_par_sum += nfc_util_even_parity32(parity & 0x08); } @@ -1751,15 +1753,6 @@ bool mf_classic_nested_is_target_key_found(MfClassicPoller* instance, bool is_di return mf_classic_is_key_found(instance->data, target_sector, target_key_type); } -bool found_all_nt_enc_msb(const MfClassicPollerDictAttackContext* dict_attack_ctx) { - for(int i = 0; i < 32; i++) { - if(dict_attack_ctx->nt_enc_msb[i] != 0xFF) { - return false; - } - } - return true; -} - bool is_valid_sum(uint16_t sum) { for(size_t i = 0; i < 19; i++) { if(sum == valid_sums[i]) { @@ -1964,7 +1957,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } // Target all remaining sectors, key A and B if(dict_attack_ctx->nested_target_key < nonce_collect_key_max) { - if((!(is_weak)) && found_all_nt_enc_msb(dict_attack_ctx)) { + if((!(is_weak)) && (dict_attack_ctx->msb_count == (UINT8_MAX + 1))) { if(is_valid_sum(dict_attack_ctx->msb_par_sum)) { // All Hardnested nonces collected dict_attack_ctx->nested_target_key++; @@ -1975,6 +1968,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->attempt_count++; instance->state = MfClassicPollerStateNestedCollectNtEnc; } + dict_attack_ctx->msb_count = 0; dict_attack_ctx->msb_par_sum = 0; memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); return command; @@ -2038,6 +2032,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_target_key += 2; dict_attack_ctx->current_key_checked = false; } else { + dict_attack_ctx->msb_count = 0; dict_attack_ctx->msb_par_sum = 0; memset(dict_attack_ctx->nt_enc_msb, 0, sizeof(dict_attack_ctx->nt_enc_msb)); dict_attack_ctx->nested_target_key++; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 0501de21e3..818d19d0a3 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -80,6 +80,9 @@ typedef struct { uint8_t nested_phase; /**< Nested attack phase. */ uint8_t prng_type; /**< PRNG (weak or hard). */ uint8_t backdoor; /**< Backdoor type. */ + uint16_t nested_target_key; /**< Target key for nested attack. */ + uint16_t + msb_count; /**< Number of unique most significant bytes seen during Hardnested attack. */ } MfClassicPollerEventDataUpdate; /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 71c972c1a0..7b9c1e73f2 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -179,6 +179,7 @@ typedef struct { uint8_t nt_enc_msb [32]; // Bit-packed array to track which unique most significant bytes have been seen (256 bits = 32 bytes) uint16_t msb_par_sum; // Sum of parity bits for each unique most significant byte + uint16_t msb_count; // Number of unique most significant bytes seen } MfClassicPollerDictAttackContext; typedef struct { From 96606dc36f5d567faede99d68e59fb6792a8d6c5 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 18 Sep 2024 12:52:22 -0400 Subject: [PATCH 062/236] Typo --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 3d902fb582..4ec313f959 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -5,7 +5,7 @@ #define TAG "NfcMfClassicDictAttack" -// TODO: Fix lag when leaving the dictionary attack view during Hardnested +// TODO: Fix lag when leaving the dictionary attack view after Hardnested typedef enum { DictAttackStateUserDictInProgress, From 0df33899eb67b4b063c026fca38df803167c2abd Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 20 Sep 2024 06:35:46 +0300 Subject: [PATCH 063/236] Frequency analyzer fixes and improvements disable ext module due to lack of required hardware on them and incorrect usage of freq analyzer, like trying to receive "signals" with it (from the space??), while it should be used only to get frequency of the remote placed around 1-10cm around flipper's left corner Also fix possible GSM mobile towers signal interference by limiting upper freq to 920mhz max Fix dupliacted freq lists and use user config for nearest freq selector too (finally) --- .../subghz_frequency_analyzer_worker.c | 48 ++++++-- .../subghz_frequency_analyzer_worker.h | 5 + .../subghz/views/subghz_frequency_analyzer.c | 104 ++---------------- documentation/SubGHzSettings.md | 2 + lib/subghz/subghz_setting.c | 2 + 5 files changed, 55 insertions(+), 106 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 00ce0fb045..3894f022fb 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -132,11 +132,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { uint32_t current_frequency = subghz_setting_get_frequency(instance->setting, i); // if(furi_hal_subghz_is_frequency_valid(current_frequency) && if(subghz_devices_is_frequency_valid(radio_device, current_frequency) && - (current_frequency != 467750000) && (current_frequency != 464000000) && - !((instance->ext_radio) && - ((current_frequency == 390000000) || (current_frequency == 312000000) || - (current_frequency == 312100000) || (current_frequency == 312200000) || - (current_frequency == 440175000)))) { + (((current_frequency != 467750000) && (current_frequency != 464000000)) && + (current_frequency <= 920000000))) { furi_hal_spi_acquire(spi_bus); cc1101_switch_to_idle(spi_bus); frequency = cc1101_set_frequency(spi_bus, current_frequency); @@ -323,18 +320,21 @@ void subghz_frequency_analyzer_worker_start( furi_assert(instance); furi_assert(!instance->worker_running); + /* SubGhzRadioDeviceType radio_type = subghz_txrx_radio_device_get(txrx); if(radio_type == SubGhzRadioDeviceTypeExternalCC1101) { instance->spi_bus = &furi_hal_spi_bus_handle_external; instance->ext_radio = true; } else if(radio_type == SubGhzRadioDeviceTypeInternal) { - instance->spi_bus = &furi_hal_spi_bus_handle_subghz; - instance->ext_radio = false; + */ + instance->spi_bus = &furi_hal_spi_bus_handle_subghz; + /* + instance->ext_radio = false; } else { - furi_crash("Unsuported external module"); + furi_crash("Wrong subghz radio type"); } - + */ instance->radio_device = subghz_devices_get_by_name(subghz_txrx_radio_device_get_name(txrx)); instance->worker_running = true; @@ -365,3 +365,33 @@ void subghz_frequency_analyzer_worker_set_trigger_level( float subghz_frequency_analyzer_worker_get_trigger_level(SubGhzFrequencyAnalyzerWorker* instance) { return instance->trigger_level; } + +uint32_t subghz_frequency_analyzer_get_nearest_frequency( + SubGhzFrequencyAnalyzerWorker* instance, + uint32_t input) { + uint32_t prev_freq = 0; + uint32_t result = 0; + uint32_t current; + + for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) { + current = subghz_setting_get_frequency(instance->setting, i); + if(current == 0) { + continue; + } + if(current == input) { + result = current; + break; + } + if(current > input && prev_freq < input) { + if(current - input < input - prev_freq) { + result = current; + } else { + result = prev_freq; + } + break; + } + prev_freq = current; + } + + return result; +} diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h index 1b21c39e9b..6533571d57 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -78,3 +78,8 @@ void subghz_frequency_analyzer_worker_set_trigger_level( * @return RSSI trigger level */ float subghz_frequency_analyzer_worker_get_trigger_level(SubGhzFrequencyAnalyzerWorker* instance); + +// Round up the frequency +uint32_t subghz_frequency_analyzer_get_nearest_frequency( + SubGhzFrequencyAnalyzerWorker* instance, + uint32_t input); diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index fde3a1f613..2ce2dfbbff 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -20,71 +20,6 @@ #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #endif -static const uint32_t subghz_frequency_list[] = { - /* 300 - 348 */ - 300000000, - 302757000, - 303875000, - 303900000, - 304250000, - 307000000, - 307500000, - 307800000, - 309000000, - 310000000, - 312000000, - 312100000, - 312200000, - 313000000, - 313850000, - 314000000, - 314350000, - 314980000, - 315000000, - 318000000, - 330000000, - 345000000, - 348000000, - 350000000, - - /* 387 - 464 */ - 387000000, - 390000000, - 418000000, - 430000000, - 430500000, - 431000000, - 431500000, - 433075000, /* LPD433 first */ - 433220000, - 433420000, - 433657070, - 433889000, - 433920000, /* LPD433 mid */ - 434075000, - 434176948, - 434190000, - 434390000, - 434420000, - 434620000, - 434775000, /* LPD433 last channels */ - 438900000, - 440175000, - 464000000, - 467750000, - - /* 779 - 928 */ - 779000000, - 868350000, - 868400000, - 868800000, - 868950000, - 906400000, - 915000000, - 925000000, - 928000000, -}; - typedef enum { SubGhzFrequencyAnalyzerStatusIDLE, } SubGhzFrequencyAnalyzerStatus; @@ -225,7 +160,7 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel canvas_set_color(canvas, ColorBlack); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 0, 7, model->is_ext_radio ? "Ext" : "Int"); + //canvas_draw_str(canvas, 0, 7, model->is_ext_radio ? "Ext" : "Int"); canvas_draw_str(canvas, 20, 7, "Frequency Analyzer"); // RSSI @@ -278,34 +213,6 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel elements_button_right(canvas, "+T"); } -uint32_t subghz_frequency_find_correct(uint32_t input) { - uint32_t prev_freq = 0; - uint32_t result = 0; - uint32_t current; - - for(size_t i = 0; i < ARRAY_SIZE(subghz_frequency_list) - 1; i++) { - current = subghz_frequency_list[i]; - if(current == 0) { - continue; - } - if(current == input) { - result = current; - break; - } - if(current > input && prev_freq < input) { - if(current - input < input - prev_freq) { - result = current; - } else { - result = prev_freq; - } - break; - } - prev_freq = current; - } - - return result; -} - bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { furi_assert(context); SubGhzFrequencyAnalyzer* instance = (SubGhzFrequencyAnalyzer*)context; @@ -364,7 +271,8 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { } else if( (model->show_frame && model->signal) || (!model->show_frame && model->signal)) { - frequency_candidate = subghz_frequency_find_correct(model->frequency); + frequency_candidate = subghz_frequency_analyzer_get_nearest_frequency( + instance->worker, model->frequency); } frequency_candidate = frequency_candidate == 0 || @@ -372,7 +280,8 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { instance->txrx, frequency_candidate) || prev_freq_to_save == frequency_candidate ? 0 : - subghz_frequency_find_correct(frequency_candidate); + subghz_frequency_analyzer_get_nearest_frequency( + instance->worker, frequency_candidate); if(frequency_candidate > 0 && frequency_candidate != model->frequency_to_save) { model->frequency_to_save = frequency_candidate; updated = true; @@ -445,7 +354,8 @@ void subghz_frequency_analyzer_pair_callback( SubGhzFrequencyAnalyzerModel * model, { bool in_array = false; - uint32_t normal_frequency = subghz_frequency_find_correct(model->frequency); + uint32_t normal_frequency = subghz_frequency_analyzer_get_nearest_frequency( + instance->worker, model->frequency); for(size_t i = 0; i < MAX_HISTORY; i++) { if(model->history_frequency[i] == normal_frequency) { in_array = true; diff --git a/documentation/SubGHzSettings.md b/documentation/SubGHzSettings.md index 3c952a1a39..a936187783 100644 --- a/documentation/SubGHzSettings.md +++ b/documentation/SubGHzSettings.md @@ -30,6 +30,8 @@ if you need your custom one, make sure it doesn't listed here 314980000, 315000000, 318000000, + 320000000, + 320150000, 330000000, 345000000, 348000000, diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index a7c3ee1dfc..2ee02c7f92 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -36,6 +36,8 @@ static const uint32_t subghz_frequency_list[] = { 314980000, 315000000, 318000000, + 320000000, + 320150000, 330000000, 345000000, 348000000, From 6eccdc8f93cb9493ba5f8b8aa83b0f2dda2de93c Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 20 Sep 2024 11:53:04 -0400 Subject: [PATCH 064/236] Zero nested_target_key and msb_count on exit --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 4ec313f959..a430613ef3 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -304,6 +304,8 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.nested_phase = MfClassicNestedPhaseNone; instance->nfc_dict_context.prng_type = MfClassicPrngTypeUnknown; instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown; + instance->nfc_dict_context.nested_target_key = 0; + instance->nfc_dict_context.msb_count = 0; nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); From c21b35951a08ffa3d270ca028521a6a0262fdbce Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 20 Sep 2024 13:09:04 -0400 Subject: [PATCH 065/236] Note TODO (malloc) --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index cf61db09ff..538ff1c53b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -9,6 +9,7 @@ // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches // TODO: Load dictionaries specific to a CUID to not clutter the user dictionary // TODO: Fix rare nested_target_key 64 bug +// TODO: Dead code for malloc returning NULL? #define MF_CLASSIC_MAX_BUFF_SIZE (64) From 16e4b9219aa9136ce55afeabf2cf4e5e992fea3d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 21 Sep 2024 03:49:55 +0300 Subject: [PATCH 066/236] merge ofw pr 3885 - Add API to enforce ISO15693 mode [ci skip] by aaronjamt https://github.com/flipperdevices/flipperzero-firmware/pull/3885/files --- lib/nfc/nfc.c | 27 ++++++++++++++++++ lib/nfc/nfc.h | 24 ++++++++++++++++ .../parsers/iso15693/iso15693_parser.c | 28 +++++++++++++++++-- .../parsers/iso15693/iso15693_parser.h | 4 +++ targets/f7/api_symbols.csv | 6 ++++ targets/f7/furi_hal/furi_hal_nfc_iso15693.c | 18 ++++++++++++ targets/furi_hal_include/furi_hal_nfc.h | 18 ++++++++++++ 7 files changed, 122 insertions(+), 3 deletions(-) diff --git a/lib/nfc/nfc.c b/lib/nfc/nfc.c index 90e65b282f..4f43587111 100644 --- a/lib/nfc/nfc.c +++ b/lib/nfc/nfc.c @@ -646,6 +646,33 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) { return ret; } +NfcError nfc_iso15693_detect_mode(Nfc* instance) { + furi_check(instance); + + FuriHalNfcError error = furi_hal_nfc_iso15693_detect_mode(); + NfcError ret = nfc_process_hal_error(error); + + return ret; +} + +NfcError nfc_iso15693_force_1outof4(Nfc* instance) { + furi_check(instance); + + FuriHalNfcError error = furi_hal_nfc_iso15693_force_1outof4(); + NfcError ret = nfc_process_hal_error(error); + + return ret; +} + +NfcError nfc_iso15693_force_1outof256(Nfc* instance) { + furi_check(instance); + + FuriHalNfcError error = furi_hal_nfc_iso15693_force_1outof256(); + NfcError ret = nfc_process_hal_error(error); + + return ret; +} + NfcError nfc_felica_listener_set_sensf_res_data( Nfc* instance, const uint8_t* idm, diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index 8fbf90d1fa..ebd29dc4b1 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -380,6 +380,30 @@ NfcError nfc_felica_listener_set_sensf_res_data( */ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance); +/** + * @brief Set ISO15693 parser mode to autodetect + * +* @param[in,out] instance pointer to the instance to be configured. + * @returns NfcErrorNone on success, any other error code on failure. +*/ +NfcError nfc_iso15693_detect_mode(Nfc* instance); + +/** + * @brief Set ISO15693 parser mode to 1OutOf4, disables autodetection + * + * @param[in,out] instance pointer to the instance to be configured. + * @return NfcErrorNone on success, any other error code on failure. +*/ +NfcError nfc_iso15693_force_1outof4(Nfc* instance); + +/** + * @brief Set ISO15693 parser mode to 1OutOf256, disables autodetection + * + * @param[in,out] instance pointer to the instance to be configured. + * @return NfcErrorNone on success, any other error code on failure. +*/ +NfcError nfc_iso15693_force_1outof256(Nfc* instance); + #ifdef __cplusplus } #endif diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.c b/lib/signal_reader/parsers/iso15693/iso15693_parser.c index a2c6912e63..d2f538c5ea 100644 --- a/lib/signal_reader/parsers/iso15693/iso15693_parser.c +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c @@ -26,6 +26,7 @@ typedef enum { struct Iso15693Parser { Iso15693ParserState state; Iso15693ParserMode mode; + bool detect_mode; SignalReader* signal_reader; @@ -62,6 +63,7 @@ typedef Iso15693ParserCommand (*Iso15693ParserStateHandler)(Iso15693Parser* inst Iso15693Parser* iso15693_parser_alloc(const GpioPin* pin, size_t max_frame_size) { Iso15693Parser* instance = malloc(sizeof(Iso15693Parser)); + instance->detect_mode = true; instance->parsed_frame = bit_buffer_alloc(max_frame_size); instance->signal_reader = signal_reader_alloc(pin, ISO15693_PARSER_SIGNAL_READER_BUFF_SIZE); @@ -86,7 +88,7 @@ void iso15693_parser_reset(Iso15693Parser* instance) { furi_assert(instance); instance->state = Iso15693ParserStateParseSoF; - instance->mode = Iso15693ParserMode1OutOf4; + if(instance->detect_mode) instance->mode = Iso15693ParserMode1OutOf4; memset(instance->bitstream_buff, 0x00, sizeof(instance->bitstream_buff)); instance->bitstream_idx = 0; @@ -122,10 +124,10 @@ static void signal_reader_callback(SignalReaderEvent event, void* context) { if(instance->state == Iso15693ParserStateParseSoF) { if(event.data->data[0] == sof_1_out_of_4) { - instance->mode = Iso15693ParserMode1OutOf4; + if(instance->detect_mode) instance->mode = Iso15693ParserMode1OutOf4; instance->state = Iso15693ParserStateParseFrame; } else if(event.data->data[0] == sof_1_out_of_256) { - instance->mode = Iso15693ParserMode1OutOf256; + if(instance->detect_mode) instance->mode = Iso15693ParserMode1OutOf256; instance->state = Iso15693ParserStateParseFrame; } else if(event.data->data[0] == eof_single) { instance->eof_received = true; @@ -298,3 +300,23 @@ void iso15693_parser_get_data( bit_buffer_write_bytes(instance->parsed_frame, buff, buff_size); *data_bits = bit_buffer_get_size(instance->parsed_frame); } + +void iso15693_parser_detect_mode(Iso15693Parser* instance) { + furi_assert(instance); + + instance->detect_mode = true; +} + +void iso15693_parser_force_1outof4(Iso15693Parser* instance) { + furi_assert(instance); + + instance->detect_mode = false; + instance->mode = Iso15693ParserMode1OutOf4; +} + +void iso15693_parser_force_1outof256(Iso15693Parser* instance) { + furi_assert(instance); + + instance->detect_mode = false; + instance->mode = Iso15693ParserMode1OutOf256; +} \ No newline at end of file diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.h b/lib/signal_reader/parsers/iso15693/iso15693_parser.h index 3017a96d79..66486e0e7a 100644 --- a/lib/signal_reader/parsers/iso15693/iso15693_parser.h +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.h @@ -37,6 +37,10 @@ void iso15693_parser_get_data( size_t buff_size, size_t* data_bits); +void iso15693_parser_detect_mode(Iso15693Parser* instance); +void iso15693_parser_force_1outof4(Iso15693Parser* instance); +void iso15693_parser_force_1outof256(Iso15693Parser* instance); + #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 410e3e17ef..a78a44c2e1 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1489,6 +1489,9 @@ Function,+,furi_hal_nfc_iso14443a_poller_trx_short_frame,FuriHalNfcError,FuriHal Function,+,furi_hal_nfc_iso14443a_poller_tx_custom_parity,FuriHalNfcError,"const uint8_t*, size_t" Function,+,furi_hal_nfc_iso14443a_rx_sdd_frame,FuriHalNfcError,"uint8_t*, size_t, size_t*" Function,+,furi_hal_nfc_iso14443a_tx_sdd_frame,FuriHalNfcError,"const uint8_t*, size_t" +Function,+,furi_hal_nfc_iso15693_detect_mode,FuriHalNfcError, +Function,+,furi_hal_nfc_iso15693_force_1outof256,FuriHalNfcError, +Function,+,furi_hal_nfc_iso15693_force_1outof4,FuriHalNfcError, Function,+,furi_hal_nfc_iso15693_listener_tx_sof,FuriHalNfcError, Function,+,furi_hal_nfc_listener_enable_rx,FuriHalNfcError, Function,+,furi_hal_nfc_listener_idle,FuriHalNfcError, @@ -2831,6 +2834,9 @@ Function,+,nfc_iso14443a_listener_tx_custom_parity,NfcError,"Nfc*, const BitBuff Function,+,nfc_iso14443a_poller_trx_custom_parity,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,nfc_iso14443a_poller_trx_sdd_frame,NfcError,"Nfc*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,nfc_iso14443a_poller_trx_short_frame,NfcError,"Nfc*, NfcIso14443aShortFrame, BitBuffer*, uint32_t" +Function,+,nfc_iso15693_detect_mode,NfcError,Nfc* +Function,+,nfc_iso15693_force_1outof256,NfcError,Nfc* +Function,+,nfc_iso15693_force_1outof4,NfcError,Nfc* Function,+,nfc_iso15693_listener_tx_sof,NfcError,Nfc* Function,+,nfc_listener_alloc,NfcListener*,"Nfc*, NfcProtocol, const NfcDeviceData*" Function,+,nfc_listener_free,void,NfcListener* diff --git a/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c index 3245b67cc8..0fd5dbca5d 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_iso15693.c +++ b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c @@ -406,6 +406,24 @@ FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof(void) { return FuriHalNfcErrorNone; } +FuriHalNfcError furi_hal_nfc_iso15693_detect_mode(void) { + iso15693_parser_detect_mode(furi_hal_nfc_iso15693_listener->parser); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_iso15693_force_1outof4(void) { + iso15693_parser_force_1outof4(furi_hal_nfc_iso15693_listener->parser); + + return FuriHalNfcErrorNone; +} + +FuriHalNfcError furi_hal_nfc_iso15693_force_1outof256(void) { + iso15693_parser_force_1outof256(furi_hal_nfc_iso15693_listener->parser); + + return FuriHalNfcErrorNone; +} + static FuriHalNfcError furi_hal_nfc_iso15693_listener_rx( FuriHalSpiBusHandle* handle, uint8_t* rx_data, diff --git a/targets/furi_hal_include/furi_hal_nfc.h b/targets/furi_hal_include/furi_hal_nfc.h index 3a81de6f52..a651c97772 100644 --- a/targets/furi_hal_include/furi_hal_nfc.h +++ b/targets/furi_hal_include/furi_hal_nfc.h @@ -452,6 +452,24 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( */ FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof(void); +/** Set ISO15693 parser mode to autodetect + * + * @return FuriHalNfcError +*/ +FuriHalNfcError furi_hal_nfc_iso15693_detect_mode(void); + +/** Set ISO15693 parser mode to 1OutOf4, disables autodetection + * + * @return FuriHalNfcError +*/ +FuriHalNfcError furi_hal_nfc_iso15693_force_1outof4(void); + +/** Set ISO15693 parser mode to 1OutOf256, disables autodetection + * + * @return FuriHalNfcError +*/ +FuriHalNfcError furi_hal_nfc_iso15693_force_1outof256(void); + /** * @brief Set FeliCa collision resolution parameters in listener mode. * From 42ab90b19b281d45e19b95b812093802617ca7a4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 21 Sep 2024 04:10:17 +0300 Subject: [PATCH 067/236] upd changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6c6a456dc..2864856cc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ ## Main changes - SubGHz: + - Frequency analyzer fixes and improvements: + - Enforce int module (like in OFW) usage due to lack of required hardware on external boards (PathIsolate (+rf switch for multiple paths)) and incorrect usage and/or understanding the purpose of frequency analyzer app by users, it should be used only to get frequency of the remote placed around 1-10cm around flipper's left corner + - Fix possible GSM mobile towers signal interference by limiting upper frequency to 920mhz max + - Fix duplicated frequency lists and use user config for nearest frequency selector too - Protocol improvements: - GangQi fix serial check - Hollarm add more button codes (thanks to @mishamyte for captures) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* OFW PR 3332: Autolock fixes (by @portasynthinca3) +* OFW PR 3885: Add API to enforce ISO15693 mode (by @aaronjamt) * OFW: kerel typo * OFW: Folder rename fails * OFW: Put errno into TCB From b0c4600115f40f1e4435c5c1a04bd1aeb9d789b3 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Tue, 27 Aug 2024 17:46:49 +0300 Subject: [PATCH 068/236] Now 4a listener invokes upper level callback on Halt and FieldOff --- lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c | 9 +++++++++ lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h | 1 + 2 files changed, 10 insertions(+) diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 95612bf54d..32cc8f1987 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -84,6 +84,15 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted || iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) { instance->state = Iso14443_4aListenerStateIdle; + + instance->iso14443_4a_event.type = iso14443_3a_event->type == + Iso14443_3aListenerEventTypeHalted ? + Iso14443_4aListenerEventTypeHalted : + Iso14443_4aListenerEventTypeFieldOff; + + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } command = NfcCommandContinue; } diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h index ba649847b2..04f0b197af 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h @@ -12,6 +12,7 @@ typedef struct Iso14443_4aListener Iso14443_4aListener; typedef enum { Iso14443_4aListenerEventTypeHalted, + Iso14443_4aListenerEventTypeFieldOff, Iso14443_4aListenerEventTypeReceivedData, } Iso14443_4aListenerEventType; From 8027c300c35120986519668f83d060b5c2ce7f50 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Mon, 23 Sep 2024 11:06:22 +0300 Subject: [PATCH 069/236] Added new method for drawing mirrored XBM bitmaps --- applications/services/gui/canvas.c | 13 +++++++++++++ applications/services/gui/canvas.h | 17 +++++++++++++++++ targets/f7/api_symbols.csv | 3 ++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index de09305aab..b310c93188 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -517,6 +517,19 @@ void canvas_draw_xbm( canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap, IconRotation0); } +void canvas_draw_xbm_mirrored( + Canvas* canvas, + int32_t x, + int32_t y, + size_t width, + size_t height, + const uint8_t* bitmap_data) { + furi_check(canvas); + x += canvas->offset_x; + y += canvas->offset_y; + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, IconRotation180); +} + void canvas_draw_glyph(Canvas* canvas, int32_t x, int32_t y, uint16_t ch) { furi_check(canvas); x += canvas->offset_x; diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 9554a200e3..be8334904d 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -287,6 +287,23 @@ void canvas_draw_xbm( size_t height, const uint8_t* bitmap); +/** Draw mirrored XBM bitmap + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param[in] width bitmap width + * @param[in] height bitmap height + * @param bitmap pointer to XBM bitmap data + */ +void canvas_draw_xbm_mirrored( + Canvas* canvas, + int32_t x, + int32_t y, + size_t width, + size_t height, + const uint8_t* bitmap_data); + /** Draw dot at x,y * * @param canvas Canvas instance diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index cb3037e48f..160c065e98 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,74.0,, +Version,+,75.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -823,6 +823,7 @@ Function,+,canvas_draw_str,void,"Canvas*, int32_t, int32_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" +Function,+,canvas_draw_xbm_mirrored,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* From 6a77ab77b07436cc79bc13d7612223f54f1122ff Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 14:26:19 -0400 Subject: [PATCH 070/236] Dismiss duplicate nonces --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 538ff1c53b..61a85cf8bc 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1383,6 +1383,13 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst uint32_t nt_prev = 0, decrypted_nt_prev = 0, found_nt = 0; uint16_t dist = 0; if(is_weak && !(dict_attack_ctx->static_encrypted)) { + // Ensure this isn't the same nonce as the previous collection + if((dict_attack_ctx->nested_nonce.count == 1) && + (dict_attack_ctx->nested_nonce.nonces[0].nt_enc == nt_enc)) { + FURI_LOG_E(TAG, "Duplicate nonce, dismissing collection attempt"); + break; + } + // Decrypt the previous nonce nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); From 901bdf96921c4b2e9bc02e12c424d475a05bc9f2 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 15:54:50 -0400 Subject: [PATCH 071/236] Fix calibration (ensure values are within 3 standard deviations) --- .../protocols/mf_classic/mf_classic_poller.c | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 61a85cf8bc..f254c8e06a 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1110,10 +1110,10 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance } NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) { - // TODO: Discard outliers (e.g. greater than 3 standard deviations) NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; uint32_t nt_enc_temp_arr[MF_CLASSIC_NESTED_CALIBRATION_COUNT]; + uint16_t distances[MF_CLASSIC_NESTED_CALIBRATION_COUNT - 1] = {0}; dict_attack_ctx->d_min = UINT16_MAX; dict_attack_ctx->d_max = 0; @@ -1230,25 +1230,22 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) // Find the distance between each nonce FURI_LOG_E(TAG, "Calculating distance between nonces"); uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); - for(uint32_t collection_cycle = 0; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; + uint8_t valid_distances = 0; + for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { bool found = false; uint32_t decrypted_nt_enc = decrypt_nt_enc( cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); for(int i = 0; i < 65535; i++) { uint32_t nth_successor = prng_successor(nt_prev, i); - if(nth_successor != decrypted_nt_enc) { - continue; - } - if(collection_cycle > 0) { + if(nth_successor == decrypted_nt_enc) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); - if(i < dict_attack_ctx->d_min) dict_attack_ctx->d_min = i; - if(i > dict_attack_ctx->d_max) dict_attack_ctx->d_max = i; + distances[valid_distances++] = i; + nt_prev = nth_successor; + found = true; + break; } - nt_prev = nth_successor; - found = true; - break; } if(!found) { FURI_LOG_E( @@ -1260,9 +1257,49 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } } - // Some breathing room, doesn't account for overflows or static nested (FIXME) - dict_attack_ctx->d_min -= 3; - dict_attack_ctx->d_max += 3; + // Calculate median and standard deviation + if(valid_distances > 0) { + // Sort the distances array (bubble sort) + for(uint8_t i = 0; i < valid_distances - 1; i++) { + for(uint8_t j = 0; j < valid_distances - i - 1; j++) { + if(distances[j] > distances[j + 1]) { + uint16_t temp = distances[j]; + distances[j] = distances[j + 1]; + distances[j + 1] = temp; + } + } + } + + // Calculate median + uint16_t median = + (valid_distances % 2 == 0) ? + (distances[valid_distances / 2 - 1] + distances[valid_distances / 2]) / 2 : + distances[valid_distances / 2]; + + // Calculate standard deviation + float sum = 0, sum_sq = 0; + for(uint8_t i = 0; i < valid_distances; i++) { + sum += distances[i]; + sum_sq += (float)distances[i] * distances[i]; + } + float mean = sum / valid_distances; + float variance = (sum_sq / valid_distances) - (mean * mean); + float std_dev = sqrtf(variance); + + // Filter out values over 3 standard deviations away from the median + dict_attack_ctx->d_min = UINT16_MAX; + dict_attack_ctx->d_max = 0; + for(uint8_t i = 0; i < valid_distances; i++) { + if(fabsf((float)distances[i] - median) <= 3 * std_dev) { + if(distances[i] < dict_attack_ctx->d_min) dict_attack_ctx->d_min = distances[i]; + if(distances[i] > dict_attack_ctx->d_max) dict_attack_ctx->d_max = distances[i]; + } + } + + // Some breathing room + dict_attack_ctx->d_min = (dict_attack_ctx->d_min > 3) ? dict_attack_ctx->d_min - 3 : 0; + dict_attack_ctx->d_max += 3; + } furi_assert(dict_attack_ctx->d_min <= dict_attack_ctx->d_max); dict_attack_ctx->calibrated = true; From 4eb0f2a21b17b8beba51082fa42ebdc32d17c311 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 18:03:15 -0400 Subject: [PATCH 072/236] Log static --- lib/nfc/protocols/mf_classic/mf_classic_poller.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index f254c8e06a..d7d8ee904c 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1306,12 +1306,13 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) instance->state = MfClassicPollerStateNestedController; mf_classic_poller_halt(instance); + uint16_t d_dist = dict_attack_ctx->d_max - dict_attack_ctx->d_min; FURI_LOG_E( TAG, "Calibration completed: min=%u max=%u static=%s", dict_attack_ctx->d_min, dict_attack_ctx->d_max, - (dict_attack_ctx->d_min == dict_attack_ctx->d_max) ? "true" : "false"); + ((d_dist >= 3) && (d_dist <= 6)) ? "true" : "false"); return command; } From 6ae950673e36244deb25e39f5949125c0eeb1f16 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 19:06:08 -0400 Subject: [PATCH 073/236] No nested dictionary attack re-entry --- .../main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index a430613ef3..434680d54e 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -219,7 +219,9 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventDictAttackComplete) { - if(state == DictAttackStateUserDictInProgress) { + bool ran_nested_dict = instance->nfc_dict_context.nested_phase != + MfClassicNestedPhaseNone; + if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->nfc_dict_context.dict); @@ -248,7 +250,9 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent } else if(event.event == NfcCustomEventDictAttackSkip) { const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); - if(state == DictAttackStateUserDictInProgress) { + bool ran_nested_dict = instance->nfc_dict_context.nested_phase != + MfClassicNestedPhaseNone; + if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { if(instance->nfc_dict_context.is_card_present) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); @@ -266,7 +270,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent dolphin_deed(DolphinDeedNfcReadSuccess); } consumed = true; - } else if(state == DictAttackStateSystemDictInProgress) { + } else { nfc_scene_mf_classic_dict_attack_notify_read(instance); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); From cd76926c746f746162cf9f0093f6df029bb79f96 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 19:13:20 -0400 Subject: [PATCH 074/236] Note minor inefficiency --- applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 434680d54e..6340eebf5a 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -6,6 +6,7 @@ #define TAG "NfcMfClassicDictAttack" // TODO: Fix lag when leaving the dictionary attack view after Hardnested +// TODO: Re-enters backdoor detection between user and system dictionary if no backdoor is found typedef enum { DictAttackStateUserDictInProgress, From 0ba8ac4ed01e459a79f608e8b49439b1c283c254 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 19:44:08 -0400 Subject: [PATCH 075/236] Uniformly use crypto1_ prefix for symbols in Crypto1 API --- lib/nfc/helpers/crypto1.c | 17 ++++++++------- lib/nfc/helpers/crypto1.h | 10 ++++----- .../mf_classic/mf_classic_listener.c | 9 +++++--- .../protocols/mf_classic/mf_classic_poller.c | 21 ++++++++++--------- targets/f7/api_symbols.csv | 10 ++++----- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/nfc/helpers/crypto1.c b/lib/nfc/helpers/crypto1.c index e59657a409..0f2b48e4e1 100644 --- a/lib/nfc/helpers/crypto1.c +++ b/lib/nfc/helpers/crypto1.c @@ -82,7 +82,7 @@ uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { return out; } -uint32_t prng_successor(uint32_t x, uint32_t n) { +uint32_t crypto1_prng_successor(uint32_t x, uint32_t n) { SWAPENDIAN(x); while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; @@ -169,9 +169,9 @@ void crypto1_encrypt_reader_nonce( nr[i] = byte; } - nt_num = prng_successor(nt_num, 32); + nt_num = crypto1_prng_successor(nt_num, 32); for(size_t i = 4; i < 8; i++) { - nt_num = prng_successor(nt_num, 8); + nt_num = crypto1_prng_successor(nt_num, 8); uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num); bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01); bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); @@ -198,7 +198,7 @@ static uint8_t lfsr_rollback_bit(Crypto1* crypto1, uint32_t in, int fb) { return ret; } -uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { +uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { uint32_t ret = 0; for(int i = 31; i >= 0; i--) { ret |= lfsr_rollback_bit(crypto1, BEBIT(in, i), fb) << (24 ^ i); @@ -206,7 +206,7 @@ uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb) { return ret; } -bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { +bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc) { return (nfc_util_even_parity8((nt >> 24) & 0xFF) == (((nt_par_enc >> 3) & 1) ^ FURI_BIT(ks, 16))) && (nfc_util_even_parity8((nt >> 16) & 0xFF) == @@ -215,7 +215,7 @@ bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_pa (((nt_par_enc >> 1) & 1) ^ FURI_BIT(ks, 0))); } -bool is_weak_prng_nonce(uint32_t nonce) { +bool crypto1_is_weak_prng_nonce(uint32_t nonce) { if(nonce == 0) return false; uint16_t x = nonce >> 16; x = (x & 0xff) << 8 | x >> 8; @@ -226,11 +226,12 @@ bool is_weak_prng_nonce(uint32_t nonce) { return x == (nonce & 0xFFFF); } -uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { +uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key) { uint64_t known_key_int = bit_lib_bytes_to_num_be(known_key.data, 6); Crypto1 crypto_temp; crypto1_init(&crypto_temp, known_key_int); crypto1_word(&crypto_temp, nt_enc ^ cuid, 1); - uint32_t decrypted_nt_enc = (nt_enc ^ lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); + uint32_t decrypted_nt_enc = + (nt_enc ^ crypto1_lfsr_rollback_word(&crypto_temp, nt_enc ^ cuid, 1)); return decrypted_nt_enc; } diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index 26862ab498..a0ab77f665 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -39,15 +39,15 @@ void crypto1_encrypt_reader_nonce( BitBuffer* out, bool is_nested); -uint32_t lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); +uint32_t crypto1_lfsr_rollback_word(Crypto1* crypto1, uint32_t in, int fb); -bool nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); +bool crypto1_nonce_matches_encrypted_parity_bits(uint32_t nt, uint32_t ks, uint8_t nt_par_enc); -bool is_weak_prng_nonce(uint32_t nonce); +bool crypto1_is_weak_prng_nonce(uint32_t nonce); -uint32_t decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); +uint32_t crypto1_decrypt_nt_enc(uint32_t cuid, uint32_t nt_enc, MfClassicKey known_key); -uint32_t prng_successor(uint32_t x, uint32_t n); +uint32_t crypto1_prng_successor(uint32_t x, uint32_t n); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index 7e4f4725b2..ef571117a1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -157,14 +157,17 @@ static MfClassicListenerCommand uint32_t nt_num = bit_lib_bytes_to_num_be(instance->auth_context.nt.data, sizeof(MfClassicNt)); uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); - if(secret_poller != prng_successor(nt_num, 64)) { + if(secret_poller != crypto1_prng_successor(nt_num, 64)) { FURI_LOG_T( - TAG, "Wrong reader key: %08lX != %08lX", secret_poller, prng_successor(nt_num, 64)); + TAG, + "Wrong reader key: %08lX != %08lX", + secret_poller, + crypto1_prng_successor(nt_num, 64)); command = MfClassicListenerCommandSleep; break; } - uint32_t at_num = prng_successor(nt_num, 96); + uint32_t at_num = crypto1_prng_successor(nt_num, 96); bit_lib_num_to_bytes_be(at_num, sizeof(uint32_t), instance->auth_context.at.data); bit_buffer_copy_bytes( instance->tx_plain_buffer, instance->auth_context.at.data, sizeof(MfClassicAr)); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index d7d8ee904c..be08b06986 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -1061,7 +1061,7 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan for(uint8_t i = 0; i < dict_attack_ctx->nested_nonce.count; i++) { MfClassicNestedNonce* nonce = &dict_attack_ctx->nested_nonce.nonces[i]; - if(!is_weak_prng_nonce(nonce->nt)) hard_nt_count++; + if(!crypto1_is_weak_prng_nonce(nonce->nt)) hard_nt_count++; } if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) { @@ -1174,7 +1174,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) uint32_t nt_enc = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); // Store the decrypted static encrypted nonce dict_attack_ctx->static_encrypted_nonce = - decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); + crypto1_decrypt_nt_enc(cuid, nt_enc, dict_attack_ctx->nested_known_key); dict_attack_ctx->calibrated = true; @@ -1234,10 +1234,10 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; collection_cycle++) { bool found = false; - uint32_t decrypted_nt_enc = decrypt_nt_enc( + uint32_t decrypted_nt_enc = crypto1_decrypt_nt_enc( cuid, nt_enc_temp_arr[collection_cycle], dict_attack_ctx->nested_known_key); for(int i = 0; i < 65535; i++) { - uint32_t nth_successor = prng_successor(nt_prev, i); + uint32_t nth_successor = crypto1_prng_successor(nt_prev, i); if(nth_successor == decrypted_nt_enc) { FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); FURI_LOG_E(TAG, "dist from nt prev: %i", i); @@ -1430,15 +1430,16 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Decrypt the previous nonce nt_prev = nt_enc_temp_arr[nt_enc_collected - 1]; - decrypted_nt_prev = decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); + decrypted_nt_prev = + crypto1_decrypt_nt_enc(cuid, nt_prev, dict_attack_ctx->nested_known_key); // Find matching nt_enc plain at expected distance found_nt = 0; uint8_t found_nt_cnt = 0; uint16_t current_dist = dict_attack_ctx->d_min; while(current_dist <= dict_attack_ctx->d_max) { - uint32_t nth_successor = prng_successor(decrypted_nt_prev, current_dist); - if(nonce_matches_encrypted_parity_bits( + uint32_t nth_successor = crypto1_prng_successor(decrypted_nt_prev, current_dist); + if(crypto1_nonce_matches_encrypted_parity_bits( nth_successor, nth_successor ^ nt_enc, parity)) { found_nt_cnt++; if(found_nt_cnt > 1) { @@ -1535,13 +1536,13 @@ static MfClassicKey* search_dicts_for_nonce_key( bool full_match = true; for(uint8_t j = 0; j < nonce_array->count; j++) { // Verify nonce matches encrypted parity bits for all nonces - uint32_t nt_enc_plain = decrypt_nt_enc( + uint32_t nt_enc_plain = crypto1_decrypt_nt_enc( nonce_array->nonces[j].cuid, nonce_array->nonces[j].nt_enc, stack_key); if(is_weak) { - full_match &= is_weak_prng_nonce(nt_enc_plain); + full_match &= crypto1_is_weak_prng_nonce(nt_enc_plain); if(!full_match) break; } - full_match &= nonce_matches_encrypted_parity_bits( + full_match &= crypto1_nonce_matches_encrypted_parity_bits( nt_enc_plain, nt_enc_plain ^ nonce_array->nonces[j].nt_enc, nonce_array->nonces[j].par); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index cafd1f1504..942fbec7ce 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -889,10 +889,15 @@ Function,+,crypto1_alloc,Crypto1*, Function,+,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" Function,+,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" Function,+,crypto1_decrypt,void,"Crypto1*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,crypto1_encrypt,void,"Crypto1*, uint8_t*, const BitBuffer*, BitBuffer*" Function,+,crypto1_encrypt_reader_nonce,void,"Crypto1*, uint64_t, uint32_t, uint8_t*, uint8_t*, BitBuffer*, _Bool" Function,+,crypto1_free,void,Crypto1* Function,+,crypto1_init,void,"Crypto1*, uint64_t" +Function,+,crypto1_is_weak_prng_nonce,_Bool,uint32_t +Function,+,crypto1_lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" +Function,+,crypto1_nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" +Function,+,crypto1_prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,crypto1_reset,void,Crypto1* Function,+,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* @@ -903,7 +908,6 @@ Function,+,datetime_get_days_per_year,uint16_t,uint16_t Function,+,datetime_is_leap_year,_Bool,uint16_t Function,+,datetime_timestamp_to_datetime,void,"uint32_t, DateTime*" Function,+,datetime_validate_datetime,_Bool,DateTime* -Function,+,decrypt_nt_enc,uint32_t,"uint32_t, uint32_t, MfClassicKey" Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -2041,7 +2045,6 @@ Function,-,initstate,char*,"unsigned, char*, size_t" Function,+,input_get_key_name,const char*,InputKey Function,+,input_get_type_name,const char*,InputType Function,-,iprintf,int,"const char*, ..." -Function,+,is_weak_prng_nonce,_Bool,uint32_t Function,-,isalnum,int,int Function,-,isalnum_l,int,"int, locale_t" Function,-,isalpha,int,int @@ -2225,7 +2228,6 @@ Function,+,lfrfid_worker_start_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_stop,void,LFRFIDWorker* Function,+,lfrfid_worker_stop_thread,void,LFRFIDWorker* Function,+,lfrfid_worker_write_start,void,"LFRFIDWorker*, LFRFIDProtocol, LFRFIDWorkerWriteCallback, void*" -Function,+,lfsr_rollback_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,lgamma,double,double Function,-,lgamma_r,double,"double, int*" Function,-,lgammaf,float,float @@ -2822,7 +2824,6 @@ Function,+,nfc_util_even_parity32,uint8_t,uint32_t Function,+,nfc_util_even_parity8,uint8_t,uint8_t Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t -Function,+,nonce_matches_encrypted_parity_bits,_Bool,"uint32_t, uint32_t, uint8_t" Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" @@ -2935,7 +2936,6 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." -Function,+,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" From 61e24fcb13eb7680490c3ac344f701625b1ba715 Mon Sep 17 00:00:00 2001 From: noproto Date: Mon, 23 Sep 2024 21:55:39 -0400 Subject: [PATCH 076/236] Fix include paths --- lib/nfc/helpers/crypto1.h | 2 +- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nfc/helpers/crypto1.h b/lib/nfc/helpers/crypto1.h index a0ab77f665..0e358581a1 100644 --- a/lib/nfc/helpers/crypto1.h +++ b/lib/nfc/helpers/crypto1.h @@ -1,6 +1,6 @@ #pragma once -#include "protocols/mf_classic/mf_classic.h" +#include #include #ifdef __cplusplus diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 7b9c1e73f2..f0175b25bb 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,12 +3,12 @@ #include "mf_classic_poller.h" #include #include -#include "nfc/helpers/iso14443_crc.h" +#include #include #include #include #include "keys_dict.h" -#include "helpers/nfc_util.h" +#include #ifdef __cplusplus extern "C" { From 099bb4071a8196858a6304d2a963d91ea3b60009 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 24 Sep 2024 22:21:15 -0400 Subject: [PATCH 077/236] Fix include paths cont --- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index f0175b25bb..4056ba30cf 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -7,7 +7,7 @@ #include #include #include -#include "keys_dict.h" +#include #include #ifdef __cplusplus From 1932bd35a533b6d4545af295ca3365b45e074a65 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 25 Sep 2024 05:31:21 +0300 Subject: [PATCH 078/236] upd readme --- ReadMe.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 4c3807688a..fb88dc8d25 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -36,9 +36,14 @@ ## FAQ (frequently asked questions) [Follow this link to find answers to most asked questions](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/FAQ.md) +## Our official domains +- https://flipperunleashed.com/ -> our main web page +- https://unleashedflip.com/ -> update server, direct .tgz update links for web updater or direct download + ## Dev builds (unstable) (built automatically from dev branch) - https://dev.unleashedflip.com/ - https://t.me/kotnehleb + ## Releases in Telegram - https://t.me/unleashed_fw From ba672e775f14b5dbcc25b5a7c97c24b245162d68 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 25 Sep 2024 10:27:32 -0400 Subject: [PATCH 079/236] Support CUID dictionary --- applications/main/nfc/nfc_app_i.h | 1 + .../scenes/nfc_scene_mf_classic_dict_attack.c | 74 +++++++++++++++++-- .../protocols/mf_classic/mf_classic_poller.c | 15 ++-- .../protocols/mf_classic/mf_classic_poller.h | 3 +- .../mf_classic/mf_classic_poller_i.h | 1 + 5 files changed, 83 insertions(+), 11 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 4aacdd19b7..9656eae116 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -102,6 +102,7 @@ typedef struct { uint8_t backdoor; uint16_t nested_target_key; uint16_t msb_count; + bool enhanced_dict; } NfcMfClassicDictAttackContext; struct NfcApp { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 6340eebf5a..4c0459e744 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -1,5 +1,6 @@ #include "../nfc_app_i.h" +#include #include #include @@ -9,6 +10,7 @@ // TODO: Re-enters backdoor detection between user and system dictionary if no backdoor is found typedef enum { + DictAttackStateCUIDDictInProgress, DictAttackStateUserDictInProgress, DictAttackStateSystemDictInProgress, } DictAttackState; @@ -32,7 +34,9 @@ NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { const MfClassicData* mfc_data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic); - mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack; + mfc_event->data->poller_mode.mode = (instance->nfc_dict_context.enhanced_dict) ? + MfClassicPollerModeDictAttackEnhanced : + MfClassicPollerModeDictAttackStandard; mfc_event->data->poller_mode.data = mfc_data; instance->nfc_dict_context.sectors_total = mf_classic_get_total_sectors_num(mfc_data->type); @@ -136,8 +140,37 @@ static void nfc_scene_mf_classic_dict_attack_update_view(NfcApp* instance) { static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); + if(state == DictAttackStateCUIDDictInProgress) { + do { + size_t cuid_len = 0; + const uint8_t* cuid = nfc_device_get_uid(instance->nfc_device, &cuid_len); + FuriString* cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", + EXT_PATH("nfc/assets"), + (uint32_t)bit_lib_bytes_to_num_be(cuid + (cuid_len - 4), 4)); + + if(!keys_dict_check_presence(furi_string_get_cstr(cuid_dict_path))) { + state = DictAttackStateUserDictInProgress; + break; + } + + instance->nfc_dict_context.dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenExisting, + sizeof(MfClassicKey)); + if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { + keys_dict_free(instance->nfc_dict_context.dict); + state = DictAttackStateUserDictInProgress; + break; + } + + dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary"); + } while(false); + } if(state == DictAttackStateUserDictInProgress) { do { + instance->nfc_dict_context.enhanced_dict = true; + // TODO: Check for errors storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); storage_common_copy( @@ -191,7 +224,7 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { NfcApp* instance = context; scene_manager_set_scene_state( - instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateCUIDDictInProgress); nfc_scene_mf_classic_dict_attack_prepare_view(instance); dict_attack_set_card_state(instance->dict_attack, true); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); @@ -222,7 +255,19 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent if(event.event == NfcCustomEventDictAttackComplete) { bool ran_nested_dict = instance->nfc_dict_context.nested_phase != MfClassicNestedPhaseNone; - if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { + if(state == DictAttackStateCUIDDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + consumed = true; + } else if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); keys_dict_free(instance->nfc_dict_context.dict); @@ -253,7 +298,25 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent nfc_device_set_data(instance->nfc_device, NfcProtocolMfClassic, mfc_data); bool ran_nested_dict = instance->nfc_dict_context.nested_phase != MfClassicNestedPhaseNone; - if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { + if(state == DictAttackStateCUIDDictInProgress) { + if(instance->nfc_dict_context.is_card_present) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + } else { + nfc_scene_mf_classic_dict_attack_notify_read(instance); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } else if(state == DictAttackStateUserDictInProgress && !(ran_nested_dict)) { if(instance->nfc_dict_context.is_card_present) { nfc_poller_stop(instance->poller); nfc_poller_free(instance->poller); @@ -293,7 +356,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { dict_attack_reset(instance->dict_attack); scene_manager_set_scene_state( - instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateUserDictInProgress); + instance->scene_manager, NfcSceneMfClassicDictAttack, DictAttackStateCUIDDictInProgress); keys_dict_free(instance->nfc_dict_context.dict); @@ -311,6 +374,7 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.backdoor = MfClassicBackdoorUnknown; instance->nfc_dict_context.nested_target_key = 0; instance->nfc_dict_context.msb_count = 0; + instance->nfc_dict_context.enhanced_dict = false; nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index be08b06986..322526dfe3 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -7,7 +7,7 @@ #define TAG "MfClassicPoller" // TODO: Buffer writes for Hardnested, set state to Log when finished and sum property matches -// TODO: Load dictionaries specific to a CUID to not clutter the user dictionary +// TODO: Store target key in CUID dictionary // TODO: Fix rare nested_target_key 64 bug // TODO: Dead code for malloc returning NULL? @@ -163,7 +163,10 @@ NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { instance->mfc_event.type = MfClassicPollerEventTypeRequestMode; command = instance->callback(instance->general_event, instance->context); - if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttack) { + if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackStandard) { + mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); + instance->state = MfClassicPollerStateRequestKey; + } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackEnhanced) { mf_classic_copy(instance->data, instance->mfc_event_data.poller_mode.data); instance->state = MfClassicPollerStateAnalyzeBackdoor; } else if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeRead) { @@ -557,6 +560,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) { NfcCommand command = NfcCommandReset; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + instance->mode_ctx.dict_attack_ctx.enhanced_dict = true; size_t current_key_index = mf_classic_backdoor_keys_count - 1; // Default to the last valid index @@ -861,9 +865,10 @@ NfcCommand mf_classic_poller_handler_key_reuse_start(MfClassicPoller* instance) command = instance->callback(instance->general_event, instance->context); // Nested entrypoint bool nested_active = dict_attack_ctx->nested_phase != MfClassicNestedPhaseNone; - if((nested_active && - (dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) || - (!(nested_active) && !(mf_classic_is_card_read(instance->data)))) { + if((dict_attack_ctx->enhanced_dict) && + ((nested_active && + (dict_attack_ctx->nested_phase != MfClassicNestedPhaseFinished)) || + (!(nested_active) && !(mf_classic_is_card_read(instance->data))))) { instance->state = MfClassicPollerStateNestedController; break; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 818d19d0a3..5c2550b7e2 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -44,7 +44,8 @@ typedef enum { typedef enum { MfClassicPollerModeRead, /**< Poller reading mode. */ MfClassicPollerModeWrite, /**< Poller writing mode. */ - MfClassicPollerModeDictAttack, /**< Poller dictionary attack mode. */ + MfClassicPollerModeDictAttackStandard, /**< Poller dictionary attack mode. */ + MfClassicPollerModeDictAttackEnhanced, /**< Poller enhanced dictionary attack mode. */ } MfClassicPollerMode; /** diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 4056ba30cf..7b05ce2401 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -159,6 +159,7 @@ typedef struct { uint8_t reuse_key_sector; MfClassicBackdoor backdoor; // Enhanced dictionary attack and nested nonce collection + bool enhanced_dict; MfClassicNestedPhase nested_phase; MfClassicKey nested_known_key; MfClassicKeyType nested_known_key_type; From d2a8e9fb5e296005d7c99fcd1cda06eaf4f791d6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 26 Sep 2024 03:50:16 +0300 Subject: [PATCH 080/236] princeton add support for different button coding type [ci skip] --- lib/subghz/protocols/princeton.c | 116 +++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 23 deletions(-) diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index f271de250f..25b34a6c7d 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -134,9 +134,21 @@ static uint8_t subghz_protocol_princeton_get_btn_code(void) { case 0xF: btn = 0x2; break; + // Second encoding type + case 0x30: + btn = 0xC0; + break; + case 0xC0: + btn = 0x30; + break; + case 0x03: + btn = 0xC0; + break; + case 0x0C: + btn = 0xC0; + break; default: - btn = 0x2; break; } } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { @@ -156,9 +168,21 @@ static uint8_t subghz_protocol_princeton_get_btn_code(void) { case 0xF: btn = 0x1; break; + // Second encoding type + case 0x30: + btn = 0x03; + break; + case 0xC0: + btn = 0x03; + break; + case 0x03: + btn = 0x30; + break; + case 0x0C: + btn = 0x03; + break; default: - btn = 0x1; break; } } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { @@ -178,9 +202,21 @@ static uint8_t subghz_protocol_princeton_get_btn_code(void) { case 0xF: btn = 0x4; break; + // Second encoding type + case 0x30: + btn = 0x0C; + break; + case 0xC0: + btn = 0x0C; + break; + case 0x03: + btn = 0x0C; + break; + case 0x0C: + btn = 0x30; + break; default: - btn = 0x4; break; } } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) { @@ -202,7 +238,6 @@ static uint8_t subghz_protocol_princeton_get_btn_code(void) { break; default: - btn = 0x8; break; } } @@ -223,8 +258,15 @@ static bool instance->generic.btn = subghz_protocol_princeton_get_btn_code(); // Reconstruction of the data - instance->generic.data = - ((uint64_t)instance->generic.serial << 4 | (uint64_t)instance->generic.btn); + // If we have 8bit button code move serial to left by 8 bits (and 4 if 4 bits) + if(instance->generic.btn == 0x30 || instance->generic.btn == 0xC0 || + instance->generic.btn == 0x03 || instance->generic.btn == 0x0C) { + instance->generic.data = + ((uint64_t)instance->generic.serial << 8 | (uint64_t)instance->generic.btn); + } else { + instance->generic.data = + ((uint64_t)instance->generic.serial << 4 | (uint64_t)instance->generic.btn); + } size_t index = 0; size_t size_upload = (instance->generic.data_count_bit * 2) + 2; @@ -264,8 +306,17 @@ static bool * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_princeton_check_remote_controller(SubGhzBlockGeneric* instance) { - instance->serial = instance->data >> 4; - instance->btn = instance->data & 0xF; + // Parse button modes for second encoding type (and serial is smaller) + // Button code is 8bit and has fixed values of one of these + // Exclude button code for each type from serial number before parsing + if((instance->data & 0xFF) == 0x30 || (instance->data & 0xFF) == 0xC0 || + (instance->data & 0xFF) == 0x03 || (instance->data & 0xFF) == 0x0C) { + instance->serial = instance->data >> 8; + instance->btn = instance->data & 0xFF; + } else { + instance->serial = instance->data >> 4; + instance->btn = instance->data & 0xF; + } // Save original button for later use if(subghz_custom_btn_get_original() == 0) { @@ -533,19 +584,38 @@ void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* out uint32_t data_rev = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); - furi_string_cat_printf( - output, - "%s %dbit\r\n" - "Key:0x%08lX\r\n" - "Yek:0x%08lX\r\n" - "Sn:0x%05lX Btn:%01X\r\n" - "Te:%luus GT:Te*%lu\r\n", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (uint32_t)(instance->generic.data & 0xFFFFFF), - data_rev, - instance->generic.serial, - instance->generic.btn, - instance->te, - instance->guard_time); + if(instance->generic.btn == 0x30 || instance->generic.btn == 0xC0 || + instance->generic.btn == 0x03 || instance->generic.btn == 0x0C) { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n" + "Sn:0x%05lX Btn:%02X (8b)\r\n" + "Te:%luus GT:Te*%lu\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + data_rev, + instance->generic.serial, + instance->generic.btn, + instance->te, + instance->guard_time); + } else { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%08lX\r\n" + "Yek:0x%08lX\r\n" + "Sn:0x%05lX Btn:%01X (4b)\r\n" + "Te:%luus GT:Te*%lu\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + data_rev, + instance->generic.serial, + instance->generic.btn, + instance->te, + instance->guard_time); + } } From b03d9f313b8824385514892c0630c0255df21470 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:44:29 +0300 Subject: [PATCH 081/236] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2864856cc4..01d87c46df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix possible GSM mobile towers signal interference by limiting upper frequency to 920mhz max - Fix duplicated frequency lists and use user config for nearest frequency selector too - Protocol improvements: + - Princeton support for second button encoding type (8bit) - GangQi fix serial check - Hollarm add more button codes (thanks to @mishamyte for captures) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) From e407c623e0dd0b2508168be052b73ef48b003b0f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:58:50 +0300 Subject: [PATCH 082/236] Revert "Merge remote-tracking branch 'OFW/portasynthinca3/3332-autolock-fixes' into dev" This reverts commit ba4c4e528a95f2d77f01ec896141ac0f4d535021, reversing changes made to 16e4b9219aa9136ce55afeabf2cf4e5e992fea3d. --- applications/services/cli/cli_vcp.c | 40 ---------------- applications/services/desktop/desktop.c | 45 ++---------------- applications/services/desktop/desktop.h | 14 ------ applications/services/desktop/desktop_i.h | 1 - .../services/desktop/views/desktop_events.h | 2 - assets/icons/StatusBar/Console_active_8x8.png | Bin 75 -> 0 bytes scripts/serial_cli.py | 4 +- 7 files changed, 4 insertions(+), 102 deletions(-) delete mode 100644 assets/icons/StatusBar/Console_active_8x8.png diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index a747102842..cdabaaa054 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -2,10 +2,6 @@ #include #include #include -#include -#include -#include -#include #define TAG "CliVcp" @@ -47,13 +43,6 @@ typedef struct { FuriHalUsbInterface* usb_if_prev; uint8_t data_buffer[USB_CDC_PKT_LEN]; - - // CLI icon - Gui* gui; - ViewPort* view_port; - - // Autolocking inhibition - Desktop* desktop; } CliVcp; static int32_t vcp_worker(void* context); @@ -75,13 +64,6 @@ static CliVcp* vcp = NULL; static const uint8_t ascii_soh = 0x01; static const uint8_t ascii_eot = 0x04; -static void cli_vcp_icon_draw_callback(Canvas* canvas, void* context) { - furi_assert(canvas); - furi_assert(context); - const Icon* icon = context; - canvas_draw_icon(canvas, 0, 0, icon); -} - static void cli_vcp_init(void) { if(vcp == NULL) { vcp = malloc(sizeof(CliVcp)); @@ -121,15 +103,6 @@ static int32_t vcp_worker(void* context) { FURI_LOG_D(TAG, "Start"); vcp->running = true; - // GUI icon - vcp->desktop = furi_record_open(RECORD_DESKTOP); - const Icon* icon = &I_Console_active_8x8; - vcp->gui = furi_record_open(RECORD_GUI); - vcp->view_port = view_port_alloc(); - view_port_set_width(vcp->view_port, icon_get_width(icon)); - // casting const away. we know that we cast it right back in the callback - view_port_draw_callback_set(vcp->view_port, cli_vcp_icon_draw_callback, (void*)icon); - while(1) { uint32_t flags = furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); @@ -142,8 +115,6 @@ static int32_t vcp_worker(void* context) { if(vcp->connected == false) { vcp->connected = true; furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); - gui_add_view_port(vcp->gui, vcp->view_port, GuiLayerStatusBarLeft); - desktop_api_add_external_inhibitor(vcp->desktop); } } @@ -155,8 +126,6 @@ static int32_t vcp_worker(void* context) { vcp->connected = false; furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); - gui_remove_view_port(vcp->gui, vcp->view_port); - desktop_api_remove_external_inhibitor(vcp->desktop); } } @@ -221,10 +190,6 @@ static int32_t vcp_worker(void* context) { } if(flags & VcpEvtStop) { - if(vcp->connected) { - gui_remove_view_port(vcp->gui, vcp->view_port); - desktop_api_remove_external_inhibitor(vcp->desktop); - } vcp->connected = false; vcp->running = false; furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); @@ -238,11 +203,6 @@ static int32_t vcp_worker(void* context) { break; } } - - view_port_free(vcp->view_port); - furi_record_close(RECORD_DESKTOP); - furi_record_close(RECORD_GUI); - FURI_LOG_D(TAG, "End"); return 0; } diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index ceea867ba6..e57e1eb00f 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -19,8 +19,6 @@ static void desktop_auto_lock_arm(Desktop*); static void desktop_auto_lock_inhibit(Desktop*); static void desktop_start_auto_lock_timer(Desktop*); static void desktop_apply_settings(Desktop*); -static void desktop_auto_lock_add_inhibitor(Desktop* desktop); -static void desktop_auto_lock_remove_inhibitor(Desktop* desktop); static void desktop_loader_callback(const void* message, void* context) { furi_assert(context); @@ -132,22 +130,16 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { animation_manager_unload_and_stall_animation(desktop->animation_manager); } - desktop_auto_lock_add_inhibitor(desktop); + desktop_auto_lock_inhibit(desktop); desktop->app_running = true; furi_semaphore_release(desktop->animation_semaphore); } else if(event == DesktopGlobalAfterAppFinished) { animation_manager_load_and_continue_animation(desktop->animation_manager); - desktop_auto_lock_remove_inhibitor(desktop); + desktop_auto_lock_arm(desktop); desktop->app_running = false; - } else if(event == DesktopGlobalAddExternalInhibitor) { - desktop_auto_lock_add_inhibitor(desktop); - - } else if(event == DesktopGlobalRemoveExternalInhibitor) { - desktop_auto_lock_remove_inhibitor(desktop); - } else if(event == DesktopGlobalAutoLock) { if(!desktop->app_running && !desktop->locked) { desktop_lock(desktop); @@ -213,24 +205,6 @@ static void desktop_auto_lock_arm(Desktop* desktop) { } } -static void desktop_auto_lock_add_inhibitor(Desktop* desktop) { - furi_check(furi_semaphore_release(desktop->auto_lock_inhibitors) == FuriStatusOk); - FURI_LOG_D( - TAG, - "%lu autolock inhibitors (+1)", - furi_semaphore_get_count(desktop->auto_lock_inhibitors)); - desktop_auto_lock_inhibit(desktop); -} - -static void desktop_auto_lock_remove_inhibitor(Desktop* desktop) { - furi_check(furi_semaphore_acquire(desktop->auto_lock_inhibitors, 0) == FuriStatusOk); - uint32_t inhibitors = furi_semaphore_get_count(desktop->auto_lock_inhibitors); - FURI_LOG_D(TAG, "%lu autolock inhibitors (-1)", inhibitors); - if(inhibitors == 0) { - desktop_auto_lock_arm(desktop); - } -} - static void desktop_auto_lock_inhibit(Desktop* desktop) { desktop_stop_auto_lock_timer(desktop); if(desktop->input_events_subscription) { @@ -397,7 +371,6 @@ static Desktop* desktop_alloc(void) { desktop->notification = furi_record_open(RECORD_NOTIFICATION); desktop->input_events_pubsub = furi_record_open(RECORD_INPUT_EVENTS); - desktop->auto_lock_inhibitors = furi_semaphore_alloc(UINT32_MAX, 0); desktop->auto_lock_timer = furi_timer_alloc(desktop_auto_lock_timer_callback, FuriTimerTypeOnce, desktop); @@ -530,18 +503,6 @@ void desktop_api_set_settings(Desktop* instance, const DesktopSettings* settings view_dispatcher_send_custom_event(instance->view_dispatcher, DesktopGlobalSaveSettings); } -void desktop_api_add_external_inhibitor(Desktop* instance) { - furi_assert(instance); - view_dispatcher_send_custom_event( - instance->view_dispatcher, DesktopGlobalAddExternalInhibitor); -} - -void desktop_api_remove_external_inhibitor(Desktop* instance) { - furi_assert(instance); - view_dispatcher_send_custom_event( - instance->view_dispatcher, DesktopGlobalRemoveExternalInhibitor); -} - /* * Application thread */ @@ -562,7 +523,7 @@ int32_t desktop_srv(void* p) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); - if(desktop_pin_code_is_set()) { + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { desktop_lock(desktop); } diff --git a/applications/services/desktop/desktop.h b/applications/services/desktop/desktop.h index 4f1556f7c7..e83bc3ee4d 100644 --- a/applications/services/desktop/desktop.h +++ b/applications/services/desktop/desktop.h @@ -21,17 +21,3 @@ FuriPubSub* desktop_api_get_status_pubsub(Desktop* instance); void desktop_api_get_settings(Desktop* instance, DesktopSettings* settings); void desktop_api_set_settings(Desktop* instance, const DesktopSettings* settings); - -/** - * @brief Adds 1 to the count of active external autolock inhibitors - * - * Autolocking will not get triggered while there's at least 1 inhibitor - */ -void desktop_api_add_external_inhibitor(Desktop* instance); - -/** - * @brief Removes 1 from the count of active external autolock inhibitors - * - * Autolocking will not get triggered while there's at least 1 inhibitor - */ -void desktop_api_remove_external_inhibitor(Desktop* instance); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index b62dac63cb..1dc7c7d219 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -73,7 +73,6 @@ struct Desktop { FuriPubSub* input_events_pubsub; FuriPubSubSubscription* input_events_subscription; - FuriSemaphore* auto_lock_inhibitors; FuriTimer* auto_lock_timer; FuriTimer* update_clock_timer; diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index e0e4aa8752..ba91a30ccd 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -62,6 +62,4 @@ typedef enum { DesktopGlobalApiUnlock, DesktopGlobalSaveSettings, DesktopGlobalReloadSettings, - DesktopGlobalAddExternalInhibitor, - DesktopGlobalRemoveExternalInhibitor, } DesktopEvent; diff --git a/assets/icons/StatusBar/Console_active_8x8.png b/assets/icons/StatusBar/Console_active_8x8.png deleted file mode 100644 index f2230a374fc93be0309b026550135d2c21c155fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75 zcmeAS@N?(olHy`uVBq!ia0vp^93adHBpBY5G^+tAAx{^_kcv6U2@Oo_Y^-XwY-|k7 WObiosqL%&wsq%F7b6Mw<&;$V5vJSHV diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index a48647bd53..8e35d57fac 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -13,9 +13,7 @@ def main(): parser.add_argument("-p", "--port", help="CDC Port", default="auto") args = parser.parse_args() if not (port := resolve_port(logger, args.port)): - logger.error( - "Is Flipper connected via USB, currently unlocked and not in DFU mode?" - ) + logger.error("Is Flipper connected via USB and not in DFU mode?") return 1 subprocess.call( [ From 50ae431a4826983faa5f3e1368d5241f32a6700e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:59:07 +0300 Subject: [PATCH 083/236] upd changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01d87c46df..04ba1209e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ - Hollarm add more button codes (thanks to @mishamyte for captures) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* OFW PR 3332: Autolock fixes (by @portasynthinca3) * OFW PR 3885: Add API to enforce ISO15693 mode (by @aaronjamt) * OFW: kerel typo * OFW: Folder rename fails From 9e046c116f8bea5f7644699afd9c802b36c0dac9 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:13:43 +0100 Subject: [PATCH 084/236] Update audio.ir Updated audio.ir with new additions --- .../resources/infrared/assets/audio.ir | 874 +++++++++++++++++- 1 file changed, 872 insertions(+), 2 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index b8bddc4583..e0f60e973a 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 18th Feb, 2024 -# Last Checked 18th Feb, 2024 +# Last Updated 27th Sept, 2024 +# Last Checked 27th Sept, 2024 # name: Power type: parsed @@ -4232,3 +4232,873 @@ type: parsed protocol: NECext address: 87 7C 00 00 command: 25 DA 00 00 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 05 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 70 01 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 71 01 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 72 01 00 00 +# +name: Power +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 45 FF 00 00 +# +name: Play +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 44 FF 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 40 FF 00 00 +# +name: Next +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 43 FF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 09 FF 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 15 FF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 47 FF 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 55 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C0 3F 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: D2 11 00 00 +command: 05 FA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D2 19 00 00 +command: 82 7D 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D2 19 00 00 +command: 83 7C 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 90 6F 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 8F 70 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 91 6E 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 8D 72 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: A2 02 20 00 +command: 62 00 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: A2 02 20 00 +command: A2 00 00 00 +# +name: Power +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 10 00 00 00 +# +name: Mute +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 2C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 11 00 00 00 +# +name: Prev +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 21 00 00 00 +# +name: Next +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 20 00 00 00 +# +name: Power +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 0A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 06 F9 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5C A3 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1E E1 00 00 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8505 4189 584 1513 578 485 639 1458 579 487 636 410 581 1532 629 414 585 1532 637 409 583 1533 636 411 580 1531 638 1454 583 486 630 1463 581 486 637 409 583 1532 637 409 636 1480 635 407 639 432 630 414 639 434 628 1463 635 431 639 1456 635 432 637 1455 637 1476 631 1461 638 1480 634 27295 8557 4136 638 1457 526 543 634 1458 525 540 637 437 501 1585 638 412 525 1583 640 414 524 1585 638 415 522 1586 637 1459 524 540 637 1460 523 539 638 413 524 1583 640 414 524 1584 639 414 524 539 638 413 525 540 637 1458 525 539 638 1459 524 541 636 1459 524 1583 640 1458 525 1585 638 +# +name: Play +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8503 4190 584 1510 581 487 582 1511 581 488 635 410 582 1532 629 417 582 1531 638 410 582 1532 637 409 583 1532 637 1457 634 433 629 1462 636 431 638 410 636 1477 638 411 635 431 638 1456 636 431 631 417 636 432 630 1460 639 431 631 1462 637 1475 639 409 637 1478 629 1464 635 1478 636 27293 8559 4136 638 1457 526 539 638 1459 524 540 637 414 524 1585 638 412 525 1584 639 414 524 1585 638 413 525 1584 639 1458 525 540 637 1459 524 540 637 413 525 1587 636 413 525 538 639 1460 523 540 637 413 525 539 638 1459 524 539 638 1458 525 1586 637 412 525 1612 611 1457 526 1585 638 27294 8450 4244 637 1464 642 416 638 1482 623 414 641 416 621 1487 637 437 600 1485 639 415 646 1460 640 436 623 1462 639 1460 621 439 640 1482 600 440 639 436 625 1461 639 436 623 416 640 1482 600 439 640 437 500 538 640 1461 619 440 640 1462 643 1462 639 414 622 1486 639 1460 643 1465 638 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8506 4190 584 469 523 1585 583 491 501 1584 584 467 525 1585 583 467 525 1585 583 1513 525 538 585 1515 523 538 585 1513 525 538 585 1515 523 540 582 1512 526 1584 584 466 526 1585 583 1515 523 540 637 1460 524 539 638 412 526 540 637 1483 501 539 638 414 524 1584 639 412 526 1613 610 27292 8453 4241 639 1465 639 1465 636 1483 623 1463 637 437 622 1468 634 416 642 1463 639 414 524 539 638 437 501 538 638 1464 640 418 638 1483 523 516 638 1462 642 417 640 1464 642 1462 638 418 640 1464 638 416 642 417 639 437 624 1463 636 436 624 445 610 1483 621 418 638 1483 622 1465 636 +# +name: Power +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 84 7B 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 85 7A 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 90 6F 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 83 7C 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 87 78 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 80 7F 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 8A 75 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 81 7E 00 00 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: D0 03 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: 90 02 20 00 +command: A0 00 00 00 +# +name: Next +type: parsed +protocol: Kaseikyo +address: AA 02 20 01 +command: A0 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 05 00 00 00 +command: 29 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 05 00 00 00 +command: 35 00 00 00 +# +name: Power +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 06 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 13 00 00 00 +# +name: Play +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: A0 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 20 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 60 00 00 00 +# +name: Prev +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 04 00 00 00 +# +name: Next +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 0C 00 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1F E0 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1B E4 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 00 FF 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1E E1 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1D E2 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6466 25614 337 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4512 564 1692 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 564 564 564 564 1692 564 1692 564 564 564 564 564 564 564 564 564 1692 564 1692 564 564 564 1692 564 564 564 1692 564 564 564 1692 564 564 564 564 564 1692 564 564 564 1692 564 564 564 1692 564 564 564 1692 564 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4512 564 1692 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 564 564 564 564 1692 564 1692 564 564 564 564 564 564 564 564 564 1692 564 564 564 1692 564 564 564 564 564 564 564 564 564 564 564 564 564 1692 564 564 564 1692 564 1692 564 1692 564 1692 564 1692 564 1692 564 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4512 564 1692 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 564 564 564 564 1692 564 1692 564 564 564 564 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 1692 564 1692 564 1692 564 1692 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 1692 564 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 949 1512 477 544 451 1542 451 544 451 516 479 1514 479 1543 450 515 480 515 480 1513 481 544 451 1515 479 1514 480 516 479 514 481 1514 479 1514 479 50684 975 1513 476 544 451 1543 451 544 451 513 482 1515 479 1514 480 544 451 517 478 1515 479 516 479 1514 480 1514 480 514 481 544 451 1516 478 1543 450 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 980 1510 480 515 480 543 452 516 479 515 480 514 481 514 481 1512 482 1514 480 1542 451 1513 481 1514 479 1513 481 1514 480 1515 478 513 482 543 452 50677 979 1507 482 514 481 515 480 516 479 517 478 516 479 514 481 1514 479 1512 481 1542 451 1513 480 1512 481 1514 479 1512 481 1513 480 515 480 517 477 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 979 1510 479 510 485 510 485 511 484 541 454 512 483 510 485 1510 483 510 485 1510 483 1512 481 1510 483 1511 482 1510 482 1510 483 512 483 1540 452 50656 980 1507 481 511 484 510 485 512 483 513 482 514 481 515 479 1510 482 511 483 1508 484 1509 483 1511 482 1510 483 1511 482 1511 482 511 484 1508 485 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 976 1508 481 514 481 514 481 515 480 515 480 517 478 515 480 514 481 1514 480 1515 479 1514 480 1516 478 1514 479 1515 478 1543 450 1515 479 515 480 50695 977 1515 475 514 481 518 477 515 480 515 480 515 480 516 479 513 482 1514 480 1516 478 1513 481 1513 481 1513 481 1515 479 1515 479 1516 479 515 480 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 85 00 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 19 E6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 16 E9 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 09 F6 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 5E A1 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 0C F3 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 18 E7 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 45 BA 00 00 +# +name: Mute +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 0D 00 00 00 +# +name: Power +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: C7 00 00 00 +# +name: Next +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 20 00 00 00 +# +name: Prev +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 21 00 00 00 +# +name: Play +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 2C 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 12 ED 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1D E2 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1A E5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 03 FC 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4512 4509 481 521 484 523 481 519 485 524 481 1524 484 1525 483 521 483 521 483 1527 481 1524 484 1527 481 1524 484 522 482 522 483 521 483 521 483 4526 481 521 483 521 483 521 483 520 484 520 484 521 484 521 483 522 483 1526 482 1527 482 1525 483 521 483 1525 483 1526 482 1525 483 1553 455 523 481 522 482 523 481 1525 484 55518 4512 4509 482 521 483 520 484 522 483 521 483 1524 485 1526 483 519 486 520 485 1526 483 1527 482 1525 484 1525 484 521 483 522 483 521 483 520 484 4525 482 521 483 521 483 520 484 522 482 521 483 522 482 521 483 520 484 1526 482 1525 483 1526 482 522 482 1525 483 1525 483 1526 482 1527 481 520 484 521 483 520 484 1525 484 55505 4510 4508 481 523 481 520 484 520 485 521 483 1524 484 1526 482 522 482 519 485 1525 483 1527 481 1526 482 1524 484 520 484 520 484 521 483 520 484 4525 481 522 482 522 482 520 484 520 484 521 483 520 484 523 481 522 482 1524 484 1525 483 1527 481 521 483 1524 484 1525 483 1525 483 1526 482 522 482 521 483 522 482 1525 483 55507 4510 4508 481 521 483 523 481 521 483 520 484 1524 484 1525 483 521 483 521 483 1525 483 1526 482 1525 483 1526 482 520 484 520 484 520 484 522 482 4524 482 522 482 520 484 520 484 522 482 522 482 521 483 521 483 521 483 1524 484 1525 483 1526 482 523 481 1526 482 1526 482 1525 483 1525 483 520 484 520 484 521 483 1525 483 55510 4510 4507 483 522 482 521 484 522 482 520 484 1526 483 1524 484 521 483 521 483 1528 481 1525 483 1526 482 1526 483 522 482 520 485 521 484 522 482 4525 483 520 484 520 485 521 483 523 481 521 483 520 484 520 484 521 484 1525 483 1526 482 1525 483 521 483 1526 482 1525 484 1526 482 1525 483 522 482 520 484 521 483 1526 482 55505 4509 4505 508 497 484 521 483 522 482 521 483 1525 483 1527 481 522 482 521 483 1527 481 1526 505 1502 483 1524 508 497 483 522 482 522 482 521 483 4525 506 497 483 522 482 521 484 522 482 521 483 522 482 522 482 521 483 1526 506 1503 505 1503 481 521 483 1524 508 1501 507 1501 507 1503 505 497 483 521 483 520 484 1527 481 55507 4508 4508 505 498 482 521 483 520 508 498 506 1503 506 1501 507 497 483 520 484 1526 506 1502 506 1501 506 1502 507 496 485 521 483 522 482 521 483 4524 506 497 483 520 484 522 482 520 484 521 483 519 485 522 482 519 485 1526 506 1502 506 1501 507 497 483 1526 506 1500 508 1503 505 1502 506 497 483 521 483 520 484 1525 506 +# +name: Next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4508 4508 481 522 482 522 482 523 482 521 483 1527 482 1527 481 522 482 521 483 1526 483 1528 480 1526 482 1527 481 522 482 521 483 523 481 522 482 4526 481 522 482 523 481 524 480 523 481 522 482 522 482 1527 482 1526 482 523 481 522 482 522 482 1525 483 1528 480 1527 481 522 482 522 482 1527 481 1526 482 1526 482 522 482 55509 4507 4508 481 522 482 523 481 522 482 523 481 1527 481 1526 482 522 483 521 483 1525 483 1524 484 1525 483 1527 481 522 482 522 482 522 482 522 482 4524 482 523 481 523 481 522 482 522 482 523 481 522 482 1525 483 1525 483 522 482 521 483 522 482 1525 483 1527 481 1525 483 522 482 522 482 1526 482 1526 482 1525 483 521 483 55506 4506 4506 483 523 481 521 483 522 482 523 481 1527 481 1526 482 522 482 522 482 1526 482 1527 481 1526 482 1526 482 522 482 522 482 522 482 522 482 4524 482 522 482 521 483 524 480 522 482 524 480 522 482 1526 482 1526 482 523 481 523 481 522 482 1527 481 1526 482 1526 482 522 482 521 483 1525 483 1525 483 1525 483 526 478 55503 4506 4507 482 523 481 522 482 522 482 523 481 1525 483 1525 483 522 482 521 483 1525 483 1526 482 1526 482 1527 481 522 482 521 483 522 482 523 482 4523 483 523 481 523 481 522 482 523 481 522 482 522 482 1527 481 1526 482 521 483 522 482 521 483 1525 483 1526 482 1526 482 523 481 522 482 1525 483 1527 481 1526 482 522 482 55505 4509 4506 482 524 480 522 482 523 481 522 482 1526 482 1526 482 523 481 523 481 1525 483 1527 481 1526 482 1527 481 522 482 522 482 521 483 522 482 4525 481 521 483 522 482 521 483 522 482 521 483 522 482 1526 482 1526 482 521 483 521 483 522 482 1526 482 1526 508 1499 509 494 511 493 511 1498 510 1498 510 1498 510 493 511 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4508 4509 481 522 482 522 482 522 482 521 483 1528 480 1526 482 524 480 524 480 1525 483 1527 481 1525 483 1527 481 523 481 522 482 524 480 522 482 4525 482 521 483 523 481 523 481 524 480 1525 483 1527 481 1525 483 521 483 1525 483 1526 482 1525 483 521 483 523 481 521 483 521 483 1526 482 522 482 522 482 523 481 1526 482 55510 4507 4509 480 522 482 522 482 522 482 521 483 1526 482 1525 483 523 481 522 482 1526 482 1525 483 1525 483 1526 482 522 482 522 482 521 483 522 482 4524 482 522 482 521 483 522 482 523 481 1525 483 1526 482 1526 482 522 482 1526 482 1527 481 1527 481 522 482 521 483 522 482 524 480 1527 481 522 482 523 481 521 483 1528 480 55495 4505 4506 482 522 482 522 482 523 481 522 482 1527 480 1525 482 522 482 522 482 1526 481 1524 484 1525 482 1526 482 521 483 522 482 522 482 521 483 4524 481 523 481 522 482 521 483 520 484 1525 483 1526 481 1525 482 522 482 1525 482 1526 481 1525 483 521 483 521 483 522 482 522 482 1525 483 522 482 521 483 522 482 1526 482 55495 4507 4506 482 522 482 521 483 522 482 524 480 1526 481 1525 482 520 484 523 481 1525 482 1525 482 1526 481 1526 482 523 481 521 483 523 481 522 482 4524 481 522 482 522 482 523 481 522 482 1526 482 1526 481 1525 483 522 482 1525 483 1525 483 1527 481 521 483 522 482 520 484 521 483 1527 480 522 482 522 482 521 483 1526 481 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4508 4507 482 523 481 522 482 524 480 522 482 1526 482 1526 482 523 481 522 482 1526 482 1527 481 1527 481 1527 481 522 482 522 482 523 481 522 482 4524 482 523 481 522 482 521 483 521 483 523 481 521 483 523 481 1526 482 1525 483 1525 483 1526 482 522 482 1526 482 1526 482 1526 482 522 482 522 482 523 481 522 482 1525 483 55504 4507 4508 481 522 482 523 481 522 482 523 481 1527 481 1526 481 522 482 522 482 1527 481 1527 481 1526 482 1525 483 522 482 521 483 522 482 523 481 4526 480 521 483 522 482 522 482 522 482 521 483 521 483 522 482 1524 484 1527 481 1526 482 1526 482 522 482 1526 482 1526 482 1524 484 522 482 522 482 522 482 520 484 1526 482 55505 4505 4507 482 522 482 523 481 523 481 521 483 1526 482 1526 482 523 481 522 482 1525 483 1525 483 1527 481 1525 483 522 482 522 482 520 484 522 482 4525 481 523 481 521 483 521 483 522 482 522 482 523 481 521 483 1527 481 1527 481 1527 481 1526 482 522 482 1526 482 1525 483 1526 482 522 482 522 482 522 482 523 481 1526 482 55499 4507 4506 482 521 483 523 481 523 481 524 480 1527 481 1525 482 523 481 522 482 1527 481 1525 482 1526 481 1526 481 522 482 521 483 522 482 520 484 4525 480 523 481 522 482 523 481 520 484 523 481 521 483 523 481 1527 480 1527 481 1526 482 1526 482 522 482 1526 481 1525 483 1526 481 523 481 522 482 521 483 522 482 1525 482 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4506 4507 482 521 483 523 481 521 483 521 483 1525 483 1527 481 521 483 523 481 1526 482 1526 482 1527 481 1525 483 523 481 521 483 523 481 521 483 4524 481 523 481 521 483 524 480 520 484 1525 482 522 482 522 482 521 483 1527 480 1526 482 1525 483 524 480 523 481 1527 481 1524 484 1525 483 522 482 521 483 522 482 1524 484 55500 4506 4508 481 523 481 521 483 522 482 522 482 1525 483 1526 482 520 484 522 482 1525 483 1525 483 1525 483 1524 484 521 483 522 482 522 482 520 484 4526 480 522 482 522 482 522 482 520 484 1528 480 521 483 522 482 522 482 1526 482 1526 482 1526 482 522 482 524 480 1526 482 1526 482 1525 483 521 483 523 481 523 481 1526 482 55504 4507 4507 482 522 482 522 482 523 481 521 483 1525 483 1527 481 521 483 522 482 1525 483 1525 483 1525 483 1526 482 522 482 524 480 521 483 521 483 4523 482 521 483 521 483 523 481 523 481 1525 483 521 483 521 483 521 483 1527 481 1527 481 1525 483 520 484 522 482 1525 483 1525 483 1527 481 521 483 523 481 522 482 1525 483 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1012 1480 517 478 510 511 487 1480 517 1477 510 512 486 508 490 1478 509 512 486 1481 516 1478 509 513 485 509 489 1479 508 1486 511 510 488 1480 517 50917 1009 1483 514 507 491 504 484 1484 513 1480 517 504 484 512 486 1481 516 505 483 1485 512 1481 516 506 482 513 485 1482 515 1479 508 513 485 1483 514 50920 1005 1486 511 510 488 481 517 1476 511 1483 514 507 491 478 510 1484 513 508 490 1477 510 1484 513 509 489 505 483 1485 512 1482 515 506 482 1486 511 50923 1013 1479 508 514 484 484 514 1480 517 1476 511 511 487 508 490 1477 510 511 487 1480 517 1477 510 512 486 482 516 1478 509 1485 512 509 489 1479 508 50926 1010 1482 515 506 482 487 511 1482 515 1479 508 513 485 510 488 1479 518 503 485 1483 514 1480 517 504 484 511 487 1480 517 1477 510 511 487 1481 516 50918 1006 1486 511 510 488 480 518 1476 511 1483 514 507 491 504 484 1483 514 508 490 1477 510 1484 513 508 490 479 509 1485 512 1481 516 505 483 1486 511 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1013 1480 517 1476 511 1483 514 482 516 479 509 485 513 482 516 479 509 486 512 482 516 479 509 1485 512 1482 515 1479 518 1476 511 1483 514 1480 517 50917 1007 1485 512 1481 516 1478 509 487 511 484 514 480 518 477 511 483 515 481 517 477 511 484 514 1480 517 1477 510 1484 513 1480 517 1477 510 1485 512 50921 1013 1479 518 1476 511 1483 514 481 517 478 510 485 513 481 517 478 510 485 513 482 516 479 509 1485 512 1481 516 1478 519 1475 512 1482 515 1479 518 50915 1009 1483 514 1480 517 1477 510 485 513 482 516 478 510 485 513 482 516 479 509 486 512 483 515 1478 509 1485 512 1482 515 1479 508 1486 511 1483 514 50919 1016 1477 510 1484 513 1481 516 478 510 485 513 482 516 479 509 486 512 483 515 480 518 476 512 1482 515 1479 518 1476 511 1483 514 1480 517 1477 510 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1014 1479 508 487 511 1482 515 480 508 488 510 484 514 481 517 478 510 485 513 1480 517 478 510 1484 513 1480 517 1478 509 1485 512 1481 516 1479 508 50925 1009 1484 513 482 516 1477 510 486 512 482 516 479 509 486 512 483 515 480 508 1486 511 484 514 1479 508 1486 511 1483 514 1480 517 1477 510 1484 513 50920 1014 1479 508 487 511 1482 515 480 508 488 510 484 514 481 517 478 510 485 513 1480 517 478 510 1484 513 1481 516 1478 509 1485 512 1482 515 1479 508 50925 1009 1483 514 481 517 1477 510 485 513 482 516 478 510 485 513 482 516 479 509 1485 512 483 515 1478 509 1486 511 1482 515 1479 508 1486 511 1483 514 50919 1015 1478 509 486 512 1482 515 480 508 487 511 483 515 481 507 487 511 484 514 1480 517 478 510 1484 513 1480 517 1477 510 1484 513 1481 516 1479 508 50925 1008 1484 513 482 516 1477 510 485 513 482 516 479 509 486 512 483 515 480 508 1485 512 484 514 1479 508 1486 511 1483 514 1480 517 1476 511 1484 513 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1012 1479 518 1476 511 510 488 507 491 504 484 511 487 508 490 504 484 511 487 508 490 1478 509 1485 512 1481 516 1478 509 1485 512 1482 515 1479 508 50925 1010 1482 515 1479 508 513 485 510 488 507 481 514 484 510 488 507 491 504 484 511 487 1481 516 1477 510 1484 513 1481 516 1478 509 1485 512 1482 515 50918 1008 1484 513 1481 516 479 509 485 513 482 516 505 483 486 512 483 515 480 508 487 511 1483 514 1479 508 1486 511 1483 514 1480 507 1487 510 1484 513 50919 1017 1476 511 1482 515 480 518 477 511 484 514 481 517 478 510 485 513 481 517 478 510 1484 513 1481 516 1477 510 1484 513 1481 516 1478 509 1485 512 50921 1014 1478 519 1475 512 483 515 480 518 477 511 484 514 481 517 477 511 484 514 481 517 1476 511 1483 514 1480 517 1477 510 1484 513 1480 517 1478 509 50923 1013 1480 517 1477 510 485 513 482 516 479 509 486 512 482 516 479 509 486 512 483 515 1478 509 1485 512 1482 515 1479 518 1476 511 1483 514 1480 517 50916 1008 1484 513 1481 516 478 510 485 513 482 516 479 509 486 512 483 515 480 518 477 511 1482 515 1479 518 1476 511 1483 514 1479 518 1476 511 1484 513 50919 1015 1477 510 1484 513 482 516 479 509 486 512 483 515 479 509 486 512 483 515 480 518 1475 512 1482 515 1479 508 1486 511 1483 514 1480 507 1488 509 50923 1012 1481 516 1477 510 485 513 482 516 478 510 485 513 483 515 480 508 487 511 483 515 1479 508 1486 511 1483 514 1480 507 1487 510 1484 513 1481 516 +# +name: Next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1014 1479 508 1486 511 484 514 481 517 1477 510 1484 513 482 516 479 571 423 513 483 515 1478 509 1486 511 484 514 480 508 1486 511 1483 514 1481 516 50918 1015 1478 509 1485 512 483 515 479 509 1485 512 1482 515 480 508 487 511 484 514 481 517 1476 511 1483 514 481 517 478 510 1484 513 1481 516 1479 508 50926 1008 1485 512 1481 516 479 509 486 512 1482 515 1479 508 487 511 484 514 481 517 478 510 1483 514 1480 517 478 510 485 513 1480 517 1477 510 1485 512 50922 1012 1481 516 1477 510 486 512 484 514 1478 509 1485 512 484 514 481 517 478 510 485 513 1480 517 1476 511 485 513 482 516 1477 510 1484 513 1482 515 +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 2B D4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 3C C3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 1A E5 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 5D A2 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 0F F0 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 03 FC 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 40 BF 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 02 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 08 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 01 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 05 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 04 00 00 00 +# +name: Power +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 15 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 14 00 00 00 +# +name: Play +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 28 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 13 00 00 00 +# +name: Next +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 29 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 2A 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 13 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 17 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: E0 03 00 00 +# +name: Pause +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 2C 00 00 00 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4512 564 1692 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 564 564 564 564 1692 564 1692 564 564 564 564 564 564 564 564 564 1692 564 1692 564 1692 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 1692 564 1692 564 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 6A 00 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 0D F2 00 00 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4509 4509 480 521 483 521 483 523 481 521 483 1526 482 1527 481 523 481 522 482 1526 482 1526 482 1526 482 1526 482 522 482 522 482 523 481 523 481 4525 481 523 481 522 482 522 482 522 482 521 483 1525 483 523 481 1527 481 523 481 523 481 521 483 1529 479 1525 483 521 483 1525 483 521 483 1526 482 1525 483 1526 482 522 482 55510 4507 4508 481 522 482 522 482 521 483 521 483 1526 482 1528 480 521 483 521 483 1527 481 1526 482 1526 482 1527 481 522 482 521 483 524 480 522 482 4525 481 520 484 522 482 522 482 522 482 521 483 1527 481 521 483 1526 482 522 482 522 482 521 483 1525 483 1527 481 521 483 1526 482 521 483 1525 483 1528 480 1527 481 522 482 55506 4508 4506 483 522 482 521 483 522 482 522 482 1524 484 1526 482 523 481 522 482 1526 482 1526 482 1526 482 1527 481 522 482 521 483 521 483 522 482 4524 482 521 483 522 482 520 484 521 483 522 482 1526 482 521 483 1526 482 521 483 521 483 522 482 1530 478 1526 482 523 481 1526 482 522 482 1526 482 1525 483 1527 481 520 484 55500 4507 4506 483 521 483 521 483 521 483 524 480 1526 482 1526 482 522 482 522 482 1527 481 1526 482 1525 483 1525 483 523 481 523 481 524 480 521 483 4524 482 521 483 522 482 521 483 520 484 520 484 1526 482 523 481 1526 482 522 482 523 481 521 483 1525 483 1525 483 521 483 1526 482 522 482 1524 484 1526 482 1529 479 522 482 55505 4507 4508 481 520 484 521 483 521 483 521 483 1527 481 1527 481 523 481 523 481 1526 482 1524 484 1524 484 1524 484 522 482 521 483 522 482 520 484 4523 483 522 482 520 484 523 481 522 482 521 483 1526 482 521 483 1527 481 524 480 522 482 522 482 1526 482 1527 481 522 482 1526 482 522 482 1525 483 1526 482 1527 481 522 482 +# +name: Pause +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 82 52035 78 49327 112 +# +name: Pause +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 02 FD 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: E0 03 00 00 +# +name: Play +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 2C 00 00 00 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4512 564 1692 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 564 564 564 564 1692 564 1692 564 564 564 564 564 564 564 564 564 1692 564 1692 564 1692 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 564 1692 564 1692 564 1692 564 1692 564 1692 564 1692 564 +# +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 6A 00 00 00 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4509 4509 480 521 483 521 483 523 481 521 483 1526 482 1527 481 523 481 522 482 1526 482 1526 482 1526 482 1526 482 522 482 522 482 523 481 523 481 4525 481 523 481 522 482 522 482 522 482 521 483 1525 483 523 481 1527 481 523 481 523 481 521 483 1529 479 1525 483 521 483 1525 483 521 483 1526 482 1525 483 1526 482 522 482 55510 4507 4508 481 522 482 522 482 521 483 521 483 1526 482 1528 480 521 483 521 483 1527 481 1526 482 1526 482 1527 481 522 482 521 483 524 480 522 482 4525 481 520 484 522 482 522 482 522 482 521 483 1527 481 521 483 1526 482 522 482 522 482 521 483 1525 483 1527 481 521 483 1526 482 521 483 1525 483 1528 480 1527 481 522 482 55506 4508 4506 483 522 482 521 483 522 482 522 482 1524 484 1526 482 523 481 522 482 1526 482 1526 482 1526 482 1527 481 522 482 521 483 521 483 522 482 4524 482 521 483 522 482 520 484 521 483 522 482 1526 482 521 483 1526 482 521 483 521 483 522 482 1530 478 1526 482 523 481 1526 482 522 482 1526 482 1525 483 1527 481 520 484 55500 4507 4506 483 521 483 521 483 521 483 524 480 1526 482 1526 482 522 482 522 482 1527 481 1526 482 1525 483 1525 483 523 481 523 481 524 480 521 483 4524 482 521 483 522 482 521 483 520 484 520 484 1526 482 523 481 1526 482 522 482 523 481 521 483 1525 483 1525 483 521 483 1526 482 522 482 1524 484 1526 482 1529 479 522 482 55505 4507 4508 481 520 484 521 483 521 483 521 483 1527 481 1527 481 523 481 523 481 1526 482 1524 484 1524 484 1524 484 522 482 521 483 522 482 520 484 4523 483 522 482 520 484 523 481 522 482 521 483 1526 482 521 483 1527 481 524 480 522 482 522 482 1526 482 1527 481 522 482 1526 482 522 482 1525 483 1526 482 1527 481 522 482 +# +name: Play +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 82 52035 78 49327 112 +# +name: Play +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 02 FD 00 00 From 21f69d74f42e54fc365d605ea0403c714450d92e Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:16:55 +0100 Subject: [PATCH 085/236] Update fans.ir Updated fans with new additions --- .../resources/infrared/assets/fans.ir | 324 +++++++++++++++++- 1 file changed, 321 insertions(+), 3 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/fans.ir b/applications/main/infrared/resources/infrared/assets/fans.ir index e3d7107167..1dfe43496a 100644 --- a/applications/main/infrared/resources/infrared/assets/fans.ir +++ b/applications/main/infrared/resources/infrared/assets/fans.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -#Last Updated 18th Feb, 2024 -#Last Checked 18th Feb, 2024 +#Last Updated 27th Sept, 2024 +#Last Checked 27th Sept, 2024 # name: Power type: raw @@ -2070,7 +2070,7 @@ data: 9241 4434 633 565 602 565 628 539 628 539 627 540 626 542 624 545 622 547 # name: Power type: raw -frequency: 40000 +frequency: 38000 duty_cycle: 0.4 data: 20592 6864 2288 9152 11440 2288 2288 6864 2288 2288 2288 13728 # @@ -2205,3 +2205,321 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 1793 719 954 1558 955 720 955 1558 954 720 955 720 1769 743 930 2420 6794 2446 926 5775 926 1586 5955 746 926 1587 925 4938 900 2450 5117 747 1764 1586 925 4100 926 3263 4252 773 2602 1586 2602 1586 1764 3262 7629 1585 4252 774 923 751 924 1589 923 751 899 3290 1762 1588 923 8291 923 1589 2601 750 1762 1588 923 752 2575 1613 898 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4482 4502 457 1536 457 1538 456 1538 456 1538 456 1536 458 1537 458 2486 457 1536 458 1537 457 2485 458 1537 457 2485 458 1536 458 1538 456 2486 457 1537 458 1536 4483 4502 457 1536 457 1538 457 1537 458 1538 457 1538 456 1537 457 2486 457 1537 457 1538 456 2486 457 1538 456 2486 457 1538 456 1537 457 2486 457 1537 457 13998 9008 2315 456 49985 9008 2315 506 49937 9031 2292 507 49938 9009 2316 506 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4504 4479 509 1483 510 1485 509 1484 510 1485 510 1485 509 1484 511 2433 510 1485 509 2435 509 1484 511 2433 510 1485 510 2434 509 1486 509 1484 511 1486 508 1483 4507 4480 508 1484 509 1484 510 1485 509 1485 509 1485 510 1485 509 2433 511 1486 508 2435 508 1485 509 2433 510 1485 509 2433 510 1486 508 1485 509 1485 509 13943 9034 2291 509 49934 9031 2292 509 49936 9030 2291 509 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4505 4479 509 1483 511 1484 511 1485 510 1485 509 1485 510 1485 509 2433 510 1484 510 1486 508 1485 509 1485 509 2434 509 2433 510 2433 510 1485 510 1486 508 1483 4507 4479 509 1483 510 1486 509 1485 509 1484 511 1484 511 1484 510 2435 509 1484 510 1485 510 1486 509 1484 511 2435 508 2434 510 2434 510 1485 510 1484 511 13947 9033 2291 510 49944 9033 2292 510 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1268 414 1273 415 430 1244 1271 416 1270 415 429 1248 425 1245 427 1242 430 1244 428 1244 428 1246 1268 7164 1270 414 1272 414 430 1244 1272 414 1272 415 429 1245 428 1247 425 1248 424 1243 429 1245 427 1246 1269 8269 1241 414 1271 414 429 1248 1267 415 1271 415 428 1244 429 1246 426 1245 427 1245 427 1245 427 1244 1270 7166 1267 414 1272 414 429 1245 1271 414 1272 416 427 1246 427 1244 428 1245 427 1246 426 1247 425 1246 1269 8242 1268 414 1271 416 427 1246 1270 415 1271 414 430 1246 427 1244 428 1247 425 1244 428 1245 427 1245 1269 7164 1269 414 1270 416 428 1246 1269 415 1271 416 427 1245 428 1246 426 1244 428 1246 426 1243 429 1246 1244 8266 1268 414 1272 414 430 1245 1271 416 1270 415 429 1246 427 1244 428 1244 428 1245 427 1244 428 1245 1245 7189 1268 415 1271 415 428 1247 1268 415 1247 441 426 1248 425 1245 427 1244 428 1243 429 1245 427 1244 1270 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1244 437 1274 415 430 1246 1247 439 1272 415 430 1245 429 1243 430 1243 429 1243 1247 439 429 1246 428 7992 1246 438 1248 438 430 1246 1269 415 1272 416 429 1246 428 1243 430 1245 427 1243 1270 416 429 1244 430 9049 1246 438 1271 416 429 1244 1271 414 1273 415 430 1245 429 1244 429 1243 429 1245 1268 416 429 1244 430 7993 1247 439 1271 415 430 1245 1247 438 1272 415 430 1246 428 1246 427 1243 429 1246 1244 438 430 1245 429 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1245 438 1248 438 430 1245 1247 439 1271 417 427 1246 428 1245 427 1245 1245 438 429 1248 425 1246 426 7994 1242 437 1249 438 429 1249 1242 439 1266 419 429 1246 427 1244 428 1245 1245 438 429 1245 428 1245 427 9305 1245 438 1247 439 429 1245 1246 438 1271 416 428 1246 427 1244 428 1243 1246 439 428 1245 428 1243 429 7989 1246 439 1246 439 429 1247 1243 439 1246 440 428 1246 427 1247 425 1248 1241 439 428 1246 427 1244 428 9305 1243 439 1246 439 429 1244 1247 440 1246 439 429 1245 428 1244 428 1243 1247 440 427 1246 427 1246 426 7990 1246 438 1270 416 429 1243 1248 438 1271 416 429 1244 429 1244 428 1243 1246 438 429 1245 428 1244 428 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1311 373 1313 373 468 1179 1340 372 1314 372 469 1180 495 1179 495 1179 495 1178 496 1177 497 1178 1341 7109 1340 372 1314 366 476 1206 1313 372 1314 373 468 1178 497 1181 493 1205 469 1178 496 1179 495 1181 1338 8215 1314 373 1313 372 469 1206 1313 373 1313 372 469 1206 469 1206 468 1204 470 1179 496 1180 494 1205 1313 7136 1314 373 1313 372 469 1181 1338 372 1314 372 469 1205 469 1206 468 1206 468 1206 468 1206 468 1205 1314 8215 1424 364 1322 363 479 1094 1424 364 1322 364 477 1095 580 1069 605 1095 524 1151 523 1150 524 1126 1393 7082 1419 364 1270 365 503 1124 1395 365 1320 365 397 1205 470 1205 469 1204 470 1204 470 1204 470 1204 1315 8215 1313 371 1315 371 495 1180 1315 371 1315 371 470 1204 495 1180 470 1205 494 1180 494 1180 494 1180 1338 7112 1338 366 1320 366 476 1181 1338 366 1320 366 476 1181 493 1181 494 1181 493 1181 494 1181 493 1181 1338 8194 1337 366 1320 366 476 1182 1337 366 1320 366 476 1182 493 1181 493 1181 494 1182 492 1182 493 1181 1337 7114 1337 366 1320 366 476 1182 1337 366 1320 366 476 1182 493 1182 492 1182 493 1182 492 1182 492 1182 1337 8194 1336 366 1320 366 476 1182 1337 366 1320 366 476 1182 493 1182 492 1182 492 1182 493 1182 492 1182 1336 7115 1336 366 1320 366 476 1183 1336 366 1320 366 476 1182 493 1182 493 1182 492 1182 492 1182 493 1182 1336 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1397 363 1322 364 476 1149 1369 364 1321 364 422 1204 470 1177 497 1177 497 1176 1342 371 469 1177 497 7967 1314 371 1314 371 469 1178 1341 373 1312 372 468 1205 468 1205 469 1206 468 1205 1314 372 468 1205 524 8943 1397 364 1321 363 478 1121 1397 364 1321 364 477 1122 552 1149 525 1123 551 1121 1397 364 476 1150 524 7913 1368 364 1266 373 523 1150 1313 372 1313 373 468 1177 497 1205 469 1205 469 1179 1340 372 468 1205 469 9029 1314 372 1314 372 468 1179 1340 372 1313 371 470 1205 469 1205 469 1205 469 1204 1315 371 469 1205 469 7969 1313 371 1314 370 470 1178 1396 364 1322 364 476 1125 550 1123 551 1149 525 1123 1395 363 478 1149 525 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1396 364 1322 364 477 1123 1396 364 1322 364 477 1125 549 1124 1395 364 477 1126 548 1125 549 1127 547 7892 1391 364 1322 364 477 1129 1390 364 1272 371 470 1179 546 1128 1342 370 471 1205 470 1179 496 1204 470 9004 1391 365 1321 364 477 1127 1392 364 1272 371 520 1128 547 1128 1341 370 471 1204 521 1130 544 1154 520 7894 1391 364 1272 371 470 1205 1315 370 1316 370 471 1179 496 1204 1316 370 471 1179 496 1204 470 1204 470 9029 1315 370 1316 370 471 1181 1339 370 1315 370 471 1204 470 1204 1315 370 471 1204 470 1204 470 1204 470 7968 1315 370 1316 370 470 1204 1315 370 1316 370 470 1203 471 1203 1316 369 472 1203 471 1203 471 1203 471 9026 1316 369 1316 368 472 1202 1317 369 1316 369 471 1203 471 1202 1316 369 471 1202 471 1203 471 1203 496 7942 1341 364 1321 364 476 1178 1341 364 1322 364 476 1177 497 1178 1341 364 476 1178 496 1178 496 1177 497 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1338 372 1314 372 469 1179 1339 371 1314 372 469 1178 496 1178 496 1179 1339 371 470 1204 470 1178 496 7942 1339 372 1367 364 479 1124 1451 362 1323 362 480 1092 582 1093 581 1093 1424 363 478 1094 524 1149 525 9208 1391 364 1321 364 478 1150 1368 364 1322 364 532 1095 576 1098 524 1150 1367 364 505 1123 550 1123 550 7888 1314 371 1315 371 470 1204 1315 371 1314 371 470 1204 470 1204 470 1204 1315 371 470 1204 470 1204 470 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2228 672 788 676 787 675 788 676 816 676 787 1409 759 1437 759 704 785 678 785 1412 784 680 783 681 782 683 781 683 780 684 779 684 780 684 779 1417 780 684 779 1417 780 1417 779 1417 779 49573 2249 689 780 1417 779 49573 2250 690 780 1417 780 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2278 652 808 655 784 679 785 681 782 704 759 1437 759 1437 759 705 784 679 785 1412 783 680 782 682 781 1415 781 1416 780 683 780 683 781 1416 780 683 780 1416 780 683 781 683 781 683 781 49575 2250 689 781 1416 780 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2259 672 788 674 789 674 789 704 787 677 787 1408 758 1437 759 705 758 705 758 1437 784 679 784 680 783 1414 782 1415 781 1416 780 684 779 684 779 1417 779 684 779 684 779 684 779 1417 779 49567 2253 687 782 1415 781 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1335 509 1337 510 448 1390 1336 509 1337 511 447 1391 448 1390 449 1391 1335 511 447 1394 445 1391 448 9180 1334 510 1335 511 447 1393 1333 510 1335 511 448 1392 447 1391 448 1390 1335 511 447 1392 447 1391 448 9180 1334 510 1335 511 447 1391 1335 510 1336 511 448 1391 449 1391 448 1391 1335 511 447 1392 447 1391 448 9180 1335 510 1335 511 447 1391 1335 509 1337 510 448 1392 448 1391 448 1391 1335 511 448 1391 448 1391 448 9181 1335 510 1335 511 447 1391 1336 509 1337 511 448 1392 448 1392 447 1391 1335 511 447 1392 448 1393 446 9182 1334 510 1335 511 447 1391 1335 510 1336 511 448 1392 448 1392 447 1391 1335 511 447 1392 448 1392 447 9182 1334 510 1335 511 447 1391 1335 510 1335 511 448 1393 447 1392 447 1390 1336 511 447 1393 446 1393 446 9182 1334 511 1356 490 473 1366 1357 487 1335 511 474 1366 474 1365 474 1364 1334 511 475 1366 473 1366 473 9155 1334 510 1345 501 474 1365 1358 487 1335 512 474 1366 473 1365 474 1364 1334 511 475 1367 472 1366 473 +# +name: Mode +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1286 403 1287 401 447 1245 1289 405 1289 402 446 1244 447 1245 446 1246 445 1247 444 1248 443 1245 1288 7139 1286 403 1287 402 446 1246 1287 406 1288 402 446 1246 445 1244 447 1247 444 1244 446 1244 446 1246 1287 7138 1286 402 1288 402 446 1246 1288 405 1289 402 445 1245 446 1247 444 1245 446 1246 445 1273 418 1247 1287 7137 1288 402 1288 402 445 1247 1286 406 1288 402 445 1247 444 1245 446 1245 446 1244 447 1246 445 1245 1288 7137 1288 402 1288 402 446 1246 1287 405 1288 402 445 1247 444 1246 444 1247 444 1246 445 1246 445 1246 1287 7138 1287 403 1287 402 445 1246 1288 406 1288 402 446 1246 445 1246 445 1245 446 1245 446 1246 445 1246 1288 7139 1286 402 1288 401 446 1246 1287 406 1287 402 446 1247 444 1245 446 1247 444 1245 446 1247 444 1246 1287 7138 1288 401 1289 401 447 1248 1285 405 1289 402 446 1245 446 1246 445 1246 445 1245 446 1245 445 1247 1286 7138 1288 402 1288 401 447 1247 1286 406 1288 402 446 1246 445 1245 446 1246 445 1245 446 1245 446 1246 1287 7138 1287 402 1288 401 447 1245 1288 406 1288 401 447 1245 446 1245 446 1246 445 1244 447 1245 446 1247 1286 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1287 402 1289 402 446 1249 1285 406 1288 402 446 1245 446 1246 445 1246 445 1247 444 1247 1286 402 446 7981 1286 404 1287 402 445 1247 1287 406 1288 402 446 1245 446 1246 445 1246 445 1246 445 1247 1287 402 446 7978 1288 403 1288 402 446 1246 1288 406 1288 402 446 1248 443 1248 443 1274 417 1247 445 1248 1286 402 446 7980 1289 402 1289 402 446 1246 1288 406 1288 402 446 1247 444 1246 445 1247 445 1246 445 1249 1285 403 445 7986 1234 454 1288 403 445 1275 1208 458 1237 454 445 1247 444 1247 445 1247 444 1247 445 1247 1236 454 445 7988 1233 454 1237 454 445 1247 1287 408 1236 454 445 1247 445 1248 444 1247 445 1248 443 1248 1286 403 445 7986 1286 403 1288 403 445 1249 1285 406 1288 403 445 1249 442 1246 445 1249 442 1248 443 1248 1286 403 445 7985 1286 403 1288 403 445 1247 1287 406 1288 403 445 1249 442 1247 444 1247 444 1246 445 1247 1287 403 445 7984 1286 404 1286 402 446 1248 1285 407 1287 402 446 1247 444 1246 445 1246 445 1247 444 1247 1287 402 446 7985 1284 403 1287 403 445 1248 1285 407 1287 403 445 1247 444 1247 444 1247 444 1246 445 1247 1286 403 445 7982 1287 403 1287 402 446 1247 1287 407 1287 402 446 1246 445 1246 445 1246 445 1248 443 1247 1287 402 446 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1287 402 1289 401 447 1247 1287 405 1289 401 447 1247 444 1245 1289 401 447 1247 444 1245 447 1247 445 7983 1287 403 1288 402 446 1245 1290 406 1289 401 447 1245 447 1248 1286 402 446 1246 446 1246 446 1246 446 7981 1289 402 1289 402 446 1247 1287 406 1288 403 445 1246 445 1248 1286 401 447 1246 445 1245 446 1248 443 7984 1288 403 1288 402 446 1247 1287 406 1288 401 447 1245 446 1246 1288 401 447 1246 445 1245 446 1248 443 7984 1286 403 1288 401 447 1249 1285 405 1289 402 446 1246 445 1246 1288 402 446 1246 445 1247 444 1246 445 7982 1288 402 1289 401 447 1247 1287 405 1289 401 447 1245 446 1249 1285 401 447 1250 441 1245 446 1245 447 7984 1286 402 1288 401 447 1244 1290 405 1289 401 447 1245 446 1244 1290 401 447 1246 445 1246 445 1247 444 7981 1288 402 1288 402 446 1246 1287 405 1289 401 447 1244 447 1246 1288 401 447 1245 446 1246 445 1246 445 7983 1287 403 1288 402 446 1245 1289 406 1288 402 446 1246 445 1247 1287 402 446 1247 444 1246 445 1246 445 7985 1286 403 1288 402 446 1246 1288 406 1288 402 446 1245 446 1247 1287 402 446 1248 443 1246 445 1246 445 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1235 454 1236 454 444 1248 1235 459 1234 454 443 1249 441 1249 442 1249 1234 456 441 1248 443 1250 440 7985 1234 456 1234 454 497 1195 1234 458 1235 455 490 1200 496 1194 491 1199 1237 454 491 1200 497 1194 491 7937 1234 455 1235 455 490 1201 1234 459 1235 455 490 1200 497 1195 496 1196 1234 455 416 1274 497 1193 492 7937 1233 455 1235 454 417 1275 1234 459 1234 455 441 1250 441 1248 442 1249 1236 455 441 1249 442 1250 441 7985 1234 455 1235 454 443 1249 1235 458 1236 455 441 1249 442 1248 442 1250 1233 454 443 1247 443 1247 443 7983 1235 455 1235 454 443 1247 1236 458 1236 454 444 1250 441 1249 442 1248 1235 454 443 1248 443 1249 442 7984 1235 455 1235 455 442 1249 1235 459 1234 455 416 1275 415 1274 491 1201 1235 454 416 1276 415 1274 417 8011 1233 455 1235 456 414 1276 1234 457 1236 453 417 1275 416 1276 414 1274 1236 454 416 1276 415 1275 416 8011 1234 455 1235 453 394 1299 1235 458 1236 454 392 1300 391 1298 393 1298 1236 455 392 1298 393 1300 391 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1233 456 1234 454 416 1276 1235 458 1236 455 415 1275 416 1274 417 1274 417 1274 1237 454 416 1274 417 8009 1234 456 1234 456 414 1275 1236 458 1236 454 416 1276 415 1276 392 1298 393 1299 1235 453 394 1298 393 8030 1235 455 1235 453 393 1299 1235 459 1235 455 391 1299 392 1299 392 1298 392 1299 1235 454 392 1300 391 8032 1235 454 1236 456 390 1300 1235 458 1236 454 392 1298 393 1299 392 1300 391 1298 1237 453 393 1298 393 8035 1235 455 1235 453 393 1299 1236 458 1236 454 393 1297 394 1299 392 1297 394 1298 1236 456 391 1297 394 8035 1234 453 1237 453 393 1299 1235 461 1232 452 394 1299 392 1297 393 1300 391 1300 1234 453 393 1298 393 8034 1236 453 1237 453 393 1298 1236 457 1237 453 393 1298 393 1299 392 1299 392 1299 1235 454 393 1298 393 +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 11 00 00 00 +# +name: Speed_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 0D 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 09 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 654 1906 626 1880 1921 610 1922 637 626 1906 626 1880 652 1880 1920 612 652 1881 1920 637 1895 638 625 1881 651 1881 651 1906 1895 610 654 1906 626 1907 1894 638 1894 610 654 1906 626 1882 650 1878 1923 609 654 22169 625 1881 651 1880 1921 637 1895 610 653 1878 654 1906 626 1880 1921 637 626 1906 1895 612 1920 637 626 1906 626 1880 653 1906 1895 610 653 1879 653 1907 1894 609 1923 637 626 1906 626 1906 626 1879 1922 637 626 22140 654 1881 651 1906 1895 609 1923 637 626 1906 626 1877 655 1906 1895 612 651 1880 1921 612 1920 638 625 1906 626 1881 651 1906 1895 638 625 1880 652 1879 1922 638 1894 611 652 1879 653 1906 626 1906 1895 610 653 22169 625 1880 652 1879 1922 610 1922 613 650 1879 653 1880 652 1906 1895 612 651 1906 1895 610 1922 637 626 1882 650 1907 625 1878 1923 612 652 1906 626 1906 1895 638 1894 611 653 1879 653 1906 626 1880 1921 610 653 22141 653 1907 625 1880 1921 612 1920 637 626 1879 653 1906 626 1879 1922 637 626 1880 1921 609 1923 638 625 1907 625 1884 648 1879 1922 637 626 1906 626 1906 1895 612 1920 637 626 1879 653 1878 654 1906 1895 612 651 22169 625 1906 626 1907 1894 610 1922 637 626 1881 651 1906 626 1879 1922 638 626 1906 1895 613 1919 638 625 1907 625 1880 652 1906 1895 638 625 1907 625 1879 1922 611 1921 638 625 1906 626 1907 625 1907 1894 611 652 22143 651 1881 651 1906 1895 611 1921 637 626 1906 626 1880 652 1907 1894 611 652 1879 1922 612 1920 611 652 1907 625 1906 626 1881 1920 637 627 1907 625 1906 1895 638 1894 638 625 1880 652 1877 655 1906 1895 638 625 22143 651 1907 625 1906 1895 609 1923 608 655 1906 626 1878 654 1906 1894 637 627 1878 1923 637 1895 637 626 1879 653 1907 625 1907 1894 611 653 1906 626 1880 1921 637 1895 612 652 1907 625 1907 625 1881 1920 611 653 22168 626 1879 653 1906 1895 638 1894 609 654 1907 625 1906 626 1879 1922 638 625 1880 1921 638 1894 608 655 1878 654 1906 626 1907 1894 637 626 1906 626 1906 1895 638 1894 608 656 1906 626 1907 625 1880 1921 639 625 22168 626 1906 626 1880 1921 610 1922 638 625 1906 626 1906 626 1881 1920 638 626 1906 1895 637 1895 612 652 1906 626 1878 654 1906 1895 637 626 1877 655 1907 1894 638 1894 610 654 1906 626 1906 626 1907 1894 609 654 22145 649 1879 653 1880 1921 637 1895 611 653 1906 626 1907 625 1878 1923 638 625 1906 1895 610 1922 637 626 1878 654 1881 651 1881 1920 610 654 1907 625 1906 1895 637 1895 637 626 1906 626 1880 652 1906 1895 610 654 22140 654 1882 650 1881 1920 609 1923 611 653 1879 653 1879 653 1879 1922 637 626 1906 1895 613 1919 613 651 1906 626 1880 652 1881 1920 637 626 1883 649 1907 1894 638 1894 609 655 1907 625 1879 653 1906 1895 638 626 22146 649 1906 626 1906 1895 637 1895 638 625 1879 653 1906 626 1906 1895 610 653 1907 1893 638 1894 610 654 1906 626 1906 626 1882 1919 611 653 1907 625 1881 1920 611 1921 610 654 1880 652 1906 626 1907 1894 610 654 22169 625 1906 626 1906 1895 611 1921 610 654 1906 626 1906 626 1881 1920 610 653 1906 1895 637 1895 638 625 1906 626 1880 652 1906 1895 613 651 1907 625 1906 1895 611 1921 638 625 1906 626 1880 652 1881 1920 615 649 22169 625 1878 654 1881 1920 637 1895 612 651 1907 625 1907 625 1906 1895 612 651 1906 1895 612 1920 638 625 1879 653 1906 626 1906 1895 614 650 1907 625 1906 1895 611 1921 638 625 1882 650 1879 653 1878 1923 610 653 22142 653 1878 654 1880 1921 612 1920 613 650 1906 626 1906 626 1881 1920 611 653 1879 1922 612 1920 610 653 1880 652 1906 626 1906 1895 613 650 1907 625 1881 1920 638 1894 614 649 1881 651 1907 625 1881 1920 611 652 22169 625 1906 626 1880 1921 612 1920 608 656 1906 626 1881 651 1879 1922 609 654 1881 1920 612 1920 614 649 1880 652 1906 626 1907 1894 637 627 1906 626 1907 1894 613 1919 611 652 1881 651 1879 653 1906 1895 610 654 22169 625 1880 652 1906 1895 637 1895 609 654 1881 651 1879 653 1905 1896 609 654 1907 1894 614 1918 637 626 1906 626 1880 652 1906 1895 614 649 1906 626 1879 1922 609 1923 638 626 1880 652 1906 626 1906 1895 637 626 22145 649 1879 653 1880 1921 612 1920 608 655 1882 650 1906 626 1906 1895 638 625 1879 1922 637 1895 638 626 1879 653 1880 652 1907 1894 615 649 1906 626 1882 1919 612 1920 612 651 1906 626 1879 653 1881 1920 638 625 22142 652 1879 653 1878 1923 610 1922 612 652 1879 653 1879 653 1879 1922 611 653 1878 1923 609 1923 610 654 1880 652 1906 626 1882 1919 609 655 1906 626 1906 1895 612 1920 611 652 1880 652 1906 626 1880 1921 609 655 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 627 1905 627 1906 1895 610 1923 611 653 1906 627 1878 655 1906 1895 609 1923 636 1896 636 628 1906 1895 611 653 1878 654 1879 1922 610 654 1877 1924 637 1895 637 627 1906 1895 637 627 1882 650 1906 1895 637 627 22143 654 1878 654 1906 1895 612 1921 611 653 1879 654 1880 653 1880 1921 637 1895 610 1923 637 627 1906 1895 609 655 1906 627 1877 1924 609 654 1906 1895 636 1896 611 653 1879 1922 637 626 1906 626 1906 1895 609 655 22170 627 1905 627 1906 1895 636 1896 611 653 1879 654 1880 652 1878 1923 637 1895 611 1922 608 655 1879 1922 637 627 1880 653 1879 1922 610 654 1879 1922 611 1921 607 657 1878 1923 610 653 1905 627 1906 1895 609 655 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 654 1906 599 1906 1895 665 1867 638 626 1908 625 1909 651 1906 1867 638 1895 638 1894 639 653 1881 652 1879 653 1906 1895 610 654 1906 1868 637 1895 664 1896 609 655 1878 655 1905 627 1878 1923 637 627 1879 1922 20875 653 1878 655 1880 1921 612 1921 612 652 1880 652 1880 653 1905 1896 611 1921 613 1920 609 655 1878 655 1905 627 1880 1921 637 627 1880 1921 610 1923 610 1922 611 653 1905 627 1878 655 1882 1919 637 627 1879 1922 20875 653 1906 627 1883 1918 636 1896 610 654 1881 652 1879 653 1906 1895 637 1895 611 1921 610 654 1879 654 1879 654 1881 1920 609 655 1878 1923 612 1920 608 1925 610 654 1882 651 1906 626 1880 1921 610 654 1879 1922 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 574 1958 574 1934 1868 689 1843 663 601 1957 575 1931 602 1931 1871 689 574 1932 1870 661 602 1932 1870 662 1871 661 602 1930 603 1931 601 1930 603 1929 1873 689 574 1931 1871 689 1843 689 574 1958 574 1930 603 22195 602 1958 574 1932 1870 660 1873 661 602 1932 601 1957 575 1958 1844 689 574 1932 1870 661 602 1931 1871 689 1844 661 602 1932 601 1929 603 1958 574 1958 1844 662 601 1930 1872 688 1844 663 600 1933 599 1933 600 22222 575 1932 600 1932 1870 661 1871 662 601 1958 574 1931 602 1930 1871 660 603 1958 1844 688 575 1931 1871 661 1871 688 575 1931 602 1958 574 1931 602 1931 1871 661 602 1932 1870 661 1872 689 574 1930 602 1932 601 22198 599 1932 601 1958 1843 663 1870 661 602 1931 601 1931 602 1958 1844 660 603 1931 1871 662 601 1930 1871 663 1870 661 602 1931 602 1957 575 1930 603 1931 1871 662 601 1929 1873 664 1868 661 603 1931 602 1932 601 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1269 409 1272 433 402 1253 1264 417 1264 451 405 1259 432 1256 1271 418 438 1253 428 1263 428 1262 429 8016 1267 411 1270 409 436 1245 1272 409 1272 443 402 1261 430 1258 1269 420 436 1255 436 1254 437 1253 438 8046 1268 410 1271 407 428 1253 1264 417 1264 425 431 1259 432 1256 1271 417 428 1263 428 1262 429 1261 430 8026 1267 410 1271 409 436 1244 1262 418 1263 425 431 1259 432 1256 1271 418 438 1252 429 1261 430 1260 431 8027 1267 411 1270 435 410 1244 1262 418 1263 452 404 1259 432 1256 1271 418 438 1252 429 1261 430 1260 431 8014 1269 408 1273 432 403 1251 1266 414 1267 449 407 1255 436 1252 1265 425 431 1259 432 1258 433 1257 434 8050 1264 413 1268 437 408 1246 1271 409 1272 443 402 1260 431 1257 1270 418 438 1253 428 1262 429 1260 431 8025 1268 408 1273 406 429 1251 1266 415 1266 448 408 1256 435 1252 1265 424 432 1258 433 1256 435 1255 436 8017 1266 410 1271 435 410 1243 1263 416 1265 451 405 1257 434 1254 1273 443 402 1260 431 1259 432 1258 433 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1270 407 1264 441 404 1248 1269 413 1268 445 411 1251 430 1260 431 1259 432 1255 1272 444 401 1260 431 7984 1268 408 1263 441 404 1248 1269 439 1242 418 427 1260 431 1259 432 1257 434 1254 1263 426 430 1257 434 8018 1266 411 1270 434 401 1251 1266 442 1239 420 436 1252 429 1261 430 1259 432 1255 1272 417 428 1259 432 7992 1271 405 1266 438 407 1244 1262 444 1237 423 433 1255 436 1253 428 1261 430 1257 1270 446 410 1251 430 7998 1265 411 1270 434 401 1251 1266 442 1239 420 436 1252 429 1261 430 1259 432 1255 1272 444 401 1259 432 7982 1270 405 1266 439 406 1245 1272 435 1236 424 432 1256 435 1254 437 1252 429 1259 1268 447 409 1252 429 8022 1272 404 1267 438 407 1244 1262 444 1237 422 434 1254 437 1252 429 1260 431 1256 1271 444 401 1259 432 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 12 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1F 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8997 4462 577 1659 577 541 576 540 578 540 577 541 577 540 578 539 578 540 578 540 578 1658 578 1658 578 1657 578 1658 578 1657 579 1658 578 1658 578 1659 577 1658 578 540 578 539 579 540 578 540 578 540 578 540 578 540 578 539 578 1658 578 1659 577 1658 578 1658 578 1658 578 1658 578 1659 577 1658 578 541 577 540 577 540 578 540 578 539 579 540 578 540 578 539 579 1658 578 1658 578 1658 578 1658 578 1658 578 1658 578 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8998 4462 575 1659 576 540 577 540 578 540 578 540 577 540 577 541 576 540 577 540 577 1658 577 1656 579 1658 577 1657 578 1658 577 1657 578 1657 578 1657 578 1658 577 1658 578 540 578 539 579 539 579 541 577 540 578 539 578 540 578 539 578 1657 578 1657 579 1658 578 1657 579 1658 578 1657 578 1657 579 1657 578 539 579 539 578 540 577 540 577 540 577 539 578 540 577 539 578 1659 576 1658 578 1658 577 1657 578 1657 604 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8998 4462 577 1660 577 538 580 541 577 541 577 540 578 541 577 540 578 540 578 540 578 1659 578 1658 579 1659 578 1658 578 1660 577 1659 578 1659 578 1658 579 1659 578 541 577 540 578 1658 579 539 579 540 578 541 577 541 577 541 577 1659 577 1660 577 540 578 1658 579 1659 578 1660 577 1659 577 1658 578 540 578 541 577 1659 577 540 578 541 577 540 578 541 577 540 578 1659 577 1659 577 540 578 1658 578 1660 576 1658 578 +# +name: Timer +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9001 4465 577 1659 579 540 578 539 579 540 578 541 577 540 578 540 578 541 577 540 578 1658 579 1659 578 1659 578 1660 577 1659 578 1659 578 1658 579 1658 579 1658 579 540 578 1658 579 540 578 540 578 540 578 540 578 541 577 539 579 1660 577 539 579 1658 578 1659 578 1659 578 1658 578 1658 578 1658 578 540 578 1659 577 539 579 540 578 540 578 540 578 540 578 540 578 1659 577 540 578 1658 578 1659 577 1658 578 1658 578 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8997 4461 578 1658 579 539 579 541 577 539 579 540 578 540 578 539 579 540 578 540 578 1658 578 1658 578 1658 578 1657 579 1658 578 1658 578 1658 578 1658 578 1658 578 1659 578 1659 578 539 579 541 577 540 578 540 578 540 578 541 577 540 578 540 578 1658 579 1659 578 1658 579 1659 578 1659 578 1658 578 1657 579 1657 579 539 579 540 578 541 576 539 579 540 578 540 578 540 578 540 577 1658 578 1658 578 1658 578 1658 578 +# +name: Rotate +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 83 00 00 00 +# +name: Rotate +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 9C 00 00 00 +# +name: Timer +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 87 00 00 00 +# +name: Mode +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 86 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1239 476 1211 476 390 1268 420 1268 418 1267 420 1267 1240 476 389 1268 419 1267 420 1266 421 1266 421 8016 1240 476 1211 476 390 1267 421 1267 419 1267 420 1266 1241 476 390 1268 419 1267 420 1266 448 1240 446 1238 102 6648 1241 476 1211 476 390 1268 420 1266 420 1268 419 1266 1241 476 390 1268 420 1267 420 1267 447 1240 446 7990 1239 476 1211 476 390 1269 419 1266 420 1267 420 1266 1241 476 390 1268 420 1266 421 1266 448 1240 447 7990 1238 476 1211 476 390 1267 421 1267 419 1268 419 1267 1240 476 390 1269 419 1267 420 1266 448 1240 446 1236 104 6649 1239 476 1211 476 390 1268 420 1267 419 1267 420 1265 1242 476 390 1268 420 1267 420 1266 447 1239 422 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1202 481 1202 481 437 1248 1200 483 1201 481 437 1247 436 1247 436 1247 436 1247 436 1248 435 1247 1201 7032 1202 482 1201 481 437 1247 1178 506 1177 505 436 1248 435 1248 435 1248 435 1246 437 1248 435 1247 1179 7056 1178 504 1179 506 434 1248 1178 505 1178 505 436 1247 436 1247 436 1247 436 1248 435 1247 436 1247 1179 7056 1178 505 1178 505 435 1247 1179 505 1178 504 436 1247 436 1248 434 1249 435 1247 436 1246 437 1248 1178 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1309 373 1310 373 493 1192 1308 374 1309 372 495 1191 492 1189 1311 375 491 1190 494 1189 494 1191 492 7740 1310 373 1310 373 494 1192 1308 376 1307 374 493 1191 492 1190 1310 376 491 1190 493 1219 464 1190 493 7743 1308 374 1309 376 491 1190 1309 375 1308 377 491 1190 493 1192 1257 426 491 1219 464 1192 491 1190 493 7742 1258 427 1256 426 491 1219 1230 425 1258 424 493 1190 493 1192 1258 453 464 1192 491 1193 490 1220 463 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 55 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1226 441 1228 442 441 1228 1227 472 1198 441 443 1231 439 1228 442 1227 443 1227 442 1226 443 1227 1228 7154 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1197 472 1198 442 414 1260 1223 472 1198 444 413 1285 385 1256 1227 472 385 1285 385 1285 385 1256 414 7968 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9358 4535 573 574 575 572 577 570 579 568 571 576 573 574 575 572 577 571 578 596 553 565 574 1695 574 1696 573 1697 572 1697 572 1698 571 1700 579 567 572 1697 572 1699 580 568 571 576 573 574 575 571 578 1718 551 1694 575 572 577 569 580 1689 580 1690 579 1690 579 1693 576 558 581 41436 9369 2240 578 95786 9363 2236 572 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9302 4546 571 576 573 571 578 567 572 573 576 569 570 575 574 571 578 567 572 574 575 568 571 1699 569 1700 568 1702 576 1693 575 1694 574 1698 570 573 576 1693 575 1695 573 1699 569 576 573 572 577 566 573 1697 571 1701 577 567 572 573 576 567 572 1699 569 1700 578 1694 574 557 572 41653 9292 2245 573 96004 9293 2241 577 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9413 4549 577 572 577 573 576 574 575 575 574 576 573 577 572 577 582 568 581 569 580 567 582 1693 575 1699 579 1695 573 1701 577 1697 581 1693 575 1701 577 573 576 571 578 1699 579 570 579 571 578 570 579 1724 554 566 573 1702 576 1700 578 569 580 1695 573 1701 577 1699 579 583 546 41516 9407 2244 573 95961 9506 2241 586 +# +name: Speed_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1329 376 1303 374 462 1193 1331 377 1302 376 460 1192 462 1218 462 1218 462 1218 487 1193 1330 376 459 7907 1300 380 1299 380 456 1224 1299 380 1299 380 456 1224 456 1224 456 1224 456 1224 456 1225 1298 381 455 8172 1298 380 1299 380 456 1224 1299 380 1299 381 455 1225 455 1225 455 1225 455 1225 455 1225 1298 381 455 7912 1298 381 1298 381 455 1225 1298 381 1298 381 455 1225 454 1225 455 1225 455 1225 455 1225 1298 381 455 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1329 378 1301 378 458 1195 1329 375 1304 378 456 1196 460 1220 1329 379 457 1196 484 1221 458 1222 457 7910 1298 382 1297 382 454 1226 1297 382 1297 382 454 1226 454 1226 1297 382 454 1226 454 1226 454 1226 454 8158 1297 382 1297 382 454 1226 1297 382 1297 382 454 1226 454 1226 1297 382 453 1227 453 1227 453 1227 453 7913 1296 382 1297 383 453 1227 1296 383 1296 383 453 1227 453 1227 1296 383 453 1227 453 1227 453 1228 452 +# +name: Mode +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1245 415 1250 436 386 1274 1224 410 1245 441 381 1278 387 1272 383 1250 405 1255 1254 407 405 1281 384 8184 1248 413 1252 434 388 1272 1216 418 1247 439 383 1250 405 1280 385 1248 407 1279 1219 415 407 1278 387 8179 1253 408 1247 439 383 1250 1248 412 1243 444 378 1255 410 1275 380 1280 385 1275 1223 411 411 1249 406 +# +name: Speed_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 11 00 00 00 From 1df959fecd9af63b00cd29765f086bb6a5eb7aed Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:18:34 +0100 Subject: [PATCH 086/236] Update projectors.ir Updated projectors + temp fix still --- .../resources/infrared/assets/projectors.ir | 262 +++++++++++++++++- 1 file changed, 260 insertions(+), 2 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/projectors.ir b/applications/main/infrared/resources/infrared/assets/projectors.ir index a3ec8a6a64..a1814e1a7f 100644 --- a/applications/main/infrared/resources/infrared/assets/projectors.ir +++ b/applications/main/infrared/resources/infrared/assets/projectors.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 18th Feb, 2024 -# Last Checked 18th Feb, 2024 +# Last Updated 27th Sept, 2024 +# Last Checked 27th Sept, 2024 # # TEMP FIX FOR POWER # @@ -1555,3 +1555,261 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 9010 4253 564 566 566 1671 566 1699 565 565 539 568 564 566 565 539 566 1699 565 1672 565 566 566 1672 564 567 565 1672 565 567 565 567 564 541 564 1698 566 539 565 567 565 567 562 542 565 1699 564 539 567 1699 565 540 564 1698 566 1672 565 1698 566 1672 565 567 565 1671 565 566 566 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 97 68 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 98 67 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 48 50 00 00 +command: 02 FD 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 48 50 00 00 +command: 02 FD 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 48 50 00 00 +command: 27 D8 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2288 611 571 587 1153 587 572 587 572 1167 572 1168 571 587 573 587 572 587 572 589 570 587 572 587 572 587 572 588 571 77346 2287 611 571 588 1152 588 571 588 571 1168 571 1168 571 588 571 588 571 588 571 589 570 587 572 588 572 587 572 587 572 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2288 611 571 587 1153 587 572 587 572 1167 572 1168 571 587 573 587 572 587 572 589 570 587 572 587 572 587 572 588 571 77346 2287 611 571 588 1152 588 571 588 571 1168 571 1168 571 588 571 588 571 588 571 589 570 587 572 588 572 587 572 587 572 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2288 610 572 588 1151 587 573 588 571 1169 570 586 573 587 572 587 1152 587 573 586 1153 588 572 587 573 586 573 587 572 76771 2289 610 572 588 1151 587 573 587 572 1166 573 587 572 587 572 587 1152 588 571 588 1151 587 573 587 572 587 573 588 571 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2310 588 571 589 1151 588 571 589 570 1168 572 1169 1150 588 1151 588 1151 590 570 588 1152 588 572 589 570 588 572 588 572 75033 2288 609 573 587 1152 587 572 586 574 1168 571 1167 1152 587 1152 586 1153 587 572 587 1152 587 572 588 571 587 572 587 572 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2288 610 572 586 1153 587 573 587 572 1166 574 1168 571 1167 1152 588 572 587 1152 588 572 587 572 587 573 587 572 588 571 75611 2288 610 572 587 1152 588 572 588 572 1167 572 1166 573 1167 1152 587 573 587 1152 588 572 588 571 586 573 586 573 585 574 +# +name: Mute +type: parsed +protocol: NECext +address: 83 55 00 00 +command: AD 52 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 55 00 00 +command: B1 4E 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 55 00 00 +command: B3 4C 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 0B 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 0B 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 0A F5 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 4A B5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 48 B7 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 0E F1 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 6A 00 00 +command: 40 BF 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 6A 00 00 +command: 40 BF 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: AD 52 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: AD 52 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 09 F6 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 03 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 09 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 0C 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4506 570 582 542 582 518 606 518 606 518 606 518 606 518 606 518 610 518 1698 548 1698 546 1700 546 1700 546 1698 546 1700 546 1696 548 1702 570 1674 546 610 516 1698 546 606 542 582 542 582 544 1674 546 610 520 606 542 1674 570 582 542 1676 546 1698 570 1674 572 582 520 1698 546 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4506 570 582 542 582 518 606 518 606 518 606 518 606 518 606 518 610 518 1698 548 1698 546 1700 546 1700 546 1698 546 1700 546 1696 548 1702 570 1674 546 610 516 1698 546 606 542 582 542 582 544 1674 546 610 520 606 542 1674 570 582 542 1676 546 1698 570 1674 572 582 520 1698 546 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9044 4484 572 580 544 580 544 580 544 580 542 582 544 580 520 604 542 586 544 1674 570 1674 570 1676 570 1674 572 1672 570 1674 546 1700 568 1678 570 582 542 1672 572 580 542 580 542 1674 566 582 542 1672 570 584 538 1674 564 580 534 1674 556 1674 556 580 530 1672 558 582 524 1676 552 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1171 433 566 433 881 433 2381 433 1486 434 565 434 1486 433 1776 433 2380 433 565 434 2381 433 1170 434 87358 1220 1171 433 566 433 883 431 2381 433 1486 433 567 432 1487 432 1775 434 2381 432 566 433 2380 434 1171 433 86252 1221 1172 432 565 434 880 435 2381 433 1486 433 565 434 1487 432 1776 433 2380 434 566 433 2379 434 1170 434 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1171 433 566 433 881 433 2381 433 1486 434 565 434 1486 433 1776 433 2380 433 565 434 2381 433 1170 434 87358 1220 1171 433 566 433 883 431 2381 433 1486 433 567 432 1487 432 1775 434 2381 432 566 433 2380 434 1171 433 86252 1221 1172 432 565 434 880 435 2381 433 1486 433 565 434 1487 432 1776 433 2380 434 566 433 2379 434 1170 434 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1173 431 566 433 882 432 2382 432 1487 432 566 433 565 434 566 433 2671 432 2670 433 2381 433 566 433 87411 324 937 325 358 325 647 326 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1172 432 566 433 882 433 2381 432 1486 434 566 433 565 434 882 433 1486 434 2669 434 2065 433 566 433 87779 324 936 326 358 325 647 326 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 13 00 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 21 DE 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 21 DE 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 09 F6 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 09 F6 00 00 From 6507f6870e40df44bc2aa41fa3be09a8a6b7d33e Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:19:59 +0100 Subject: [PATCH 087/236] Update tv.ir Updated tvs with new additions --- .../infrared/resources/infrared/assets/tv.ir | 118 +++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index d9aaac130b..a26bf4b416 100755 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 18th Feb, 2024 -# Last Checked 18th Feb, 2024 +# Last Updated 27th Sept, 2024 +# Last Checked 27th Sept, 2024 # name: Power type: parsed @@ -3563,3 +3563,117 @@ type: parsed protocol: NEC address: 40 00 00 00 command: 17 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 15 EA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 16 E9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 1A E5 00 00 +# +name: Power +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0B 00 00 00 +# +name: Mute +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 49 00 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 0C F3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 10 EF 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 0E F1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 11 EE 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 0E F1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 1A E5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 49 B6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 43 BC 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 51 AE 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 4D B2 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5B A4 00 00 From 180d1f04711dee208fa18160ab9ab69d277dfd35 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Sat, 28 Sep 2024 21:48:48 +0300 Subject: [PATCH 088/236] iso14443_4a poller logic enhanced --- lib/nfc/helpers/iso14443_4_layer.c | 90 +++++++++++++++++-- lib/nfc/helpers/iso14443_4_layer.h | 4 + .../iso14443_4a/iso14443_4a_poller.h | 63 +++++++++++++ .../iso14443_4a/iso14443_4a_poller_i.c | 31 +++++++ targets/f7/api_symbols.csv | 5 +- 5 files changed, 185 insertions(+), 8 deletions(-) diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 3e8c6e83a3..2095499380 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -2,10 +2,42 @@ #include -#define ISO14443_4_BLOCK_PCB (1U << 1) -#define ISO14443_4_BLOCK_PCB_I (0U) -#define ISO14443_4_BLOCK_PCB_R (5U << 5) -#define ISO14443_4_BLOCK_PCB_S (3U << 6) +#define ISO14443_4_BLOCK_PCB (1U << 1) +#define ISO14443_4_BLOCK_PCB_MASK (0x03) + +#define ISO14443_4_BLOCK_PCB_I (0U) +#define ISO14443_4_BLOCK_PCB_I_NAD_OFFSET (2) +#define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_I_NAD_MASK (1U << ISO14443_4_BLOCK_PCB_I_NAD_OFFSET) +#define ISO14443_4_BLOCK_PCB_I_CID_MASK (1U << ISO14443_4_BLOCK_PCB_I_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_I_CHAIN_MASK (1U << ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET) + +#define ISO14443_4_BLOCK_PCB_R_MASK (5U << 5) +#define ISO14443_4_BLOCK_PCB_R_NACK_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_R_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_R_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_R_NACK_MASK (1U << ISO14443_4_BLOCK_PCB_R_NACK_OFFSET) + +#define ISO14443_4_BLOCK_PCB_S_MASK (3U << 6) +#define ISO14443_4_BLOCK_PCB_S_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) + +#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & mask) == mask) + +#define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) + +#define ISO14443_4_BLOCK_PCB_IS_S_BLOCK(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_S_MASK) + +#define ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_I_CHAIN_MASK) + +#define ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_NACK_MASK) struct Iso14443_4Layer { uint8_t pcb; @@ -31,9 +63,31 @@ void iso14443_4_layer_free(Iso14443_4Layer* instance) { void iso14443_4_layer_reset(Iso14443_4Layer* instance) { furi_assert(instance); + instance->pcb_prev = 0; instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB; } +void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present) { + uint8_t block_pcb = instance->pcb & ISO14443_4_BLOCK_PCB_MASK; + instance->pcb = ISO14443_4_BLOCK_PCB_I | (chaining << ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET) | + (CID_present << ISO14443_4_BLOCK_PCB_I_CID_OFFSET) | block_pcb; +} + +void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present) { + furi_assert(instance); + uint8_t block_pcb = instance->pcb & ISO14443_4_BLOCK_PCB_MASK; + instance->pcb = ISO14443_4_BLOCK_PCB_R_MASK | + (!acknowledged << ISO14443_4_BLOCK_PCB_R_NACK_OFFSET) | + (CID_present << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) | block_pcb; +} + +void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present) { + furi_assert(instance); + uint8_t des_wtx = !deselect ? (ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK) : 0; + instance->pcb = ISO14443_4_BLOCK_PCB_S_MASK | des_wtx | + (CID_present << ISO14443_4_BLOCK_PCB_S_CID_OFFSET) | ISO14443_4_BLOCK_PCB; +} + void iso14443_4_layer_encode_block( Iso14443_4Layer* instance, const BitBuffer* input_data, @@ -46,6 +100,11 @@ void iso14443_4_layer_encode_block( iso14443_4_layer_update_pcb(instance); } +static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_data) { + const uint8_t* data = bit_buffer_get_data(block_data); + return data[0]; +} + bool iso14443_4_layer_decode_block( Iso14443_4Layer* instance, BitBuffer* output_data, @@ -55,9 +114,26 @@ bool iso14443_4_layer_decode_block( bool ret = false; do { - if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; - bit_buffer_copy_right(output_data, block_data, 1); - ret = true; + if(ISO14443_4_BLOCK_PCB_IS_R_BLOCK(instance->pcb_prev)) { + const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); + ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && + (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); + instance->pcb &= ISO14443_4_BLOCK_PCB_MASK; + iso14443_4_layer_update_pcb(instance); + } else if(ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(instance->pcb_prev)) { + const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); + ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && + (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); + instance->pcb &= ~(ISO14443_4_BLOCK_PCB_I_CHAIN_MASK); + } else if(ISO14443_4_BLOCK_PCB_IS_S_BLOCK(instance->pcb_prev)) { + ret = bit_buffer_starts_with_byte(block_data, instance->pcb_prev); + if(bit_buffer_get_size_bytes(block_data) > 1) + bit_buffer_copy_right(output_data, block_data, 1); + } else { + if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; + bit_buffer_copy_right(output_data, block_data, 1); + ret = true; + } } while(false); return ret; diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h index 437c2e8a65..67a7f37fe2 100644 --- a/lib/nfc/helpers/iso14443_4_layer.h +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -14,6 +14,10 @@ void iso14443_4_layer_free(Iso14443_4Layer* instance); void iso14443_4_layer_reset(Iso14443_4Layer* instance); +void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present); +void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present); +void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present); + void iso14443_4_layer_encode_block( Iso14443_4Layer* instance, const BitBuffer* input_data, diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index fef565e514..80a4c15400 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -56,6 +56,69 @@ Iso14443_4aError iso14443_4a_poller_send_block( const BitBuffer* tx_buffer, BitBuffer* rx_buffer); +/** + * @brief Transmit and receive Iso14443_4a chained block in poller mode. Also it + * automatically modifies PCB packet byte with appropriate bits then resets them back + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer. The fwt parameter is calculated during activation procedure. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_chain_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Transmit Iso14443_4a R-block in poller mode. This block never contains + * data, but can contain CID and NAD, therefore in tx_buffer only two bytes can be added. + * The first one will represent CID, the second one will represent NAD. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with R-block repsonse + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] acknowledged Sets appropriate bit in PCB byte. True - ACK, false - NAK + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_receive_ready_block( + Iso14443_4aPoller* instance, + bool acknowledged, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Transmit Iso14443_4a S-block in poller mode. S-block used to exchange control + * information between the card and the reader. Two different types of S-blocks + * are defined: + * - Waiting time extension containing a 1 byte long INF field and (deselect = false) + * - DESELECT containing no INF field (deselect = true) + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with R-block repsonse + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] deselect Sets appropriate bit in PCB byte. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_supervisory_block( + Iso14443_4aPoller* instance, + bool deselect, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + /** * @brief Send HALT command to the card. * diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index a9453b0390..427973f4af 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -114,3 +114,34 @@ Iso14443_4aError iso14443_4a_poller_send_block( return error; } + +Iso14443_4aError iso14443_4a_poller_send_chain_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + iso14443_4_layer_set_i_block(instance->iso14443_4_layer, true, false); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} + +Iso14443_4aError iso14443_4a_poller_send_receive_ready_block( + Iso14443_4aPoller* instance, + bool acknowledged, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + bool CID_present = bit_buffer_get_size_bytes(tx_buffer) != 0; + iso14443_4_layer_set_r_block(instance->iso14443_4_layer, acknowledged, CID_present); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} + +Iso14443_4aError iso14443_4a_poller_send_supervisory_block( + Iso14443_4aPoller* instance, + bool deselect, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + bool CID_present = bit_buffer_get_size_bytes(tx_buffer) != 0; + iso14443_4_layer_set_s_block(instance->iso14443_4_layer, deselect, CID_present); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 160c065e98..ca74ece8a5 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,75.0,, +Version,+,75.3,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -2125,6 +2125,9 @@ Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller* Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*" Function,+,iso14443_4a_poller_send_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_chain_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_receive_ready_block,Iso14443_4aError,"Iso14443_4aPoller*, _Bool, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_supervisory_block,Iso14443_4aError,"Iso14443_4aPoller*, _Bool, const BitBuffer*, BitBuffer*" Function,+,iso14443_4a_reset,void,Iso14443_4aData* Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*" Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t" From bdb402b0687a4510b2f7ebdaa5a209504b123d8d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 29 Sep 2024 04:05:09 +0300 Subject: [PATCH 089/236] frequency analyzer multiple fixes fix scenes crash, fix button logic fix wrong calls to int module make worker more OFW like, remove ext module unused code --- .../subghz_frequency_analyzer_worker.c | 129 ++++++------------ .../subghz_frequency_analyzer_worker.h | 4 +- .../subghz/views/subghz_frequency_analyzer.c | 6 +- 3 files changed, 50 insertions(+), 89 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c index 3894f022fb..81b785ab9b 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -28,10 +28,6 @@ struct SubGhzFrequencyAnalyzerWorker { FrequencyRSSI frequency_rssi_buf; SubGhzSetting* setting; - const SubGhzDevice* radio_device; - FuriHalSpiBusHandle* spi_bus; - bool ext_radio; - float filVal; float trigger_level; @@ -39,16 +35,14 @@ struct SubGhzFrequencyAnalyzerWorker { void* context; }; -static void subghz_frequency_analyzer_worker_load_registers( - FuriHalSpiBusHandle* spi_bus, - const uint8_t data[][2]) { - furi_hal_spi_acquire(spi_bus); +static void subghz_frequency_analyzer_worker_load_registers(const uint8_t data[][2]) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); size_t i = 0; while(data[i][0]) { - cc1101_write_reg(spi_bus, data[i][0], data[i][1]); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, data[i][0], data[i][1]); i++; } - furi_hal_spi_release(spi_bus); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } // running average with adaptive coefficient @@ -82,35 +76,29 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { float rssi_temp = 0; uint32_t frequency_temp = 0; - FuriHalSpiBusHandle* spi_bus = instance->spi_bus; - const SubGhzDevice* radio_device = instance->radio_device; - //Start CC1101 - // furi_hal_subghz_reset(); - subghz_devices_reset(radio_device); + furi_hal_subghz_reset(); - furi_hal_spi_acquire(spi_bus); - cc1101_flush_rx(spi_bus); - cc1101_flush_tx(spi_bus); - - // TODO probably can be used device.load_preset(FuriHalSubGhzPresetCustom, ...) for external cc1101 - cc1101_write_reg(spi_bus, CC1101_IOCFG0, CC1101IocfgHW); - cc1101_write_reg(spi_bus, CC1101_MDMCFG3, + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_rx(&furi_hal_spi_bus_handle_subghz); + cc1101_flush_tx(&furi_hal_spi_bus_handle_subghz); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, CC1101_MDMCFG3, 0b01111111); // symbol rate cc1101_write_reg( - spi_bus, + &furi_hal_spi_bus_handle_subghz, CC1101_AGCCTRL2, 0b00000111); // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAGN_TARGET 42 dB cc1101_write_reg( - spi_bus, + &furi_hal_spi_bus_handle_subghz, CC1101_AGCCTRL1, 0b00001000); // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 1000 - Absolute carrier sense threshold disabled cc1101_write_reg( - spi_bus, + &furi_hal_spi_bus_handle_subghz, CC1101_AGCCTRL0, 0b00110000); // 00 - No hysteresis, medium asymmetric dead zone, medium gain ; 11 - 64 samples agc; 00 - Normal AGC, 00 - 4dB boundary - furi_hal_spi_release(spi_bus); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); @@ -123,32 +111,32 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { frequency_rssi.rssi_coarse = -127.0f; frequency_rssi.rssi_fine = -127.0f; - // furi_hal_subghz_idle(); - subghz_devices_idle(radio_device); - subghz_frequency_analyzer_worker_load_registers(spi_bus, subghz_preset_ook_650khz); + furi_hal_subghz_idle(); + subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_650khz); // First stage: coarse scan for(size_t i = 0; i < subghz_setting_get_frequency_count(instance->setting); i++) { uint32_t current_frequency = subghz_setting_get_frequency(instance->setting, i); - // if(furi_hal_subghz_is_frequency_valid(current_frequency) && - if(subghz_devices_is_frequency_valid(radio_device, current_frequency) && + if(furi_hal_subghz_is_frequency_valid(current_frequency) && (((current_frequency != 467750000) && (current_frequency != 464000000)) && (current_frequency <= 920000000))) { - furi_hal_spi_acquire(spi_bus); - cc1101_switch_to_idle(spi_bus); - frequency = cc1101_set_frequency(spi_bus, current_frequency); + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + frequency = cc1101_set_frequency( + &furi_hal_spi_bus_handle_subghz, + subghz_setting_get_frequency(instance->setting, i)); - cc1101_calibrate(spi_bus); + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); - furi_check(cc1101_wait_status_state(spi_bus, CC1101StateIDLE, 10000)); + furi_check(cc1101_wait_status_state( + &furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000)); - cc1101_switch_to_rx(spi_bus); - furi_hal_spi_release(spi_bus); + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_delay_ms(2); - // rssi = furi_hal_subghz_get_rssi(); - rssi = subghz_devices_get_rssi(radio_device); + rssi = furi_hal_subghz_get_rssi(); rssi_avg += rssi; rssi_avg_samples++; @@ -172,30 +160,28 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { // Second stage: fine scan if(frequency_rssi.rssi_coarse > instance->trigger_level) { - // furi_hal_subghz_idle(); - subghz_devices_idle(radio_device); - subghz_frequency_analyzer_worker_load_registers(spi_bus, subghz_preset_ook_58khz); + furi_hal_subghz_idle(); + subghz_frequency_analyzer_worker_load_registers(subghz_preset_ook_58khz); //for example -0.3 ... 433.92 ... +0.3 step 20KHz for(uint32_t i = frequency_rssi.frequency_coarse - 300000; i < frequency_rssi.frequency_coarse + 300000; i += 20000) { - // if(furi_hal_subghz_is_frequency_valid(i)) { - if(subghz_devices_is_frequency_valid(radio_device, i)) { - furi_hal_spi_acquire(spi_bus); - cc1101_switch_to_idle(spi_bus); - frequency = cc1101_set_frequency(spi_bus, i); + if(furi_hal_subghz_is_frequency_valid(i)) { + furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz); + cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz); + frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, i); - cc1101_calibrate(spi_bus); + cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); - furi_check(cc1101_wait_status_state(spi_bus, CC1101StateIDLE, 10000)); + furi_check(cc1101_wait_status_state( + &furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000)); - cc1101_switch_to_rx(spi_bus); - furi_hal_spi_release(spi_bus); + cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); + furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); furi_delay_ms(2); - // rssi = furi_hal_subghz_get_rssi(); - rssi = subghz_devices_get_rssi(radio_device); + rssi = furi_hal_subghz_get_rssi(); FURI_LOG_T(TAG, "#:%lu:%f", frequency, (double)rssi); @@ -272,10 +258,8 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { } //Stop CC1101 - // furi_hal_subghz_idle(); - // furi_hal_subghz_sleep(); - subghz_devices_idle(radio_device); - subghz_devices_sleep(radio_device); + furi_hal_subghz_idle(); + furi_hal_subghz_sleep(); return 0; } @@ -284,12 +268,8 @@ SubGhzFrequencyAnalyzerWorker* subghz_frequency_analyzer_worker_alloc(void* cont furi_assert(context); SubGhzFrequencyAnalyzerWorker* instance = malloc(sizeof(SubGhzFrequencyAnalyzerWorker)); - instance->thread = furi_thread_alloc(); - furi_thread_set_name(instance->thread, "SubGhzFAWorker"); - furi_thread_set_stack_size(instance->thread, 2048); - furi_thread_set_context(instance->thread, instance); - furi_thread_set_callback(instance->thread, subghz_frequency_analyzer_worker_thread); - + instance->thread = furi_thread_alloc_ex( + "SubGhzFAWorker", 2048, subghz_frequency_analyzer_worker_thread, instance); SubGhz* subghz = context; instance->setting = subghz_txrx_get_setting(subghz->txrx); instance->trigger_level = subghz->last_settings->frequency_analyzer_trigger; @@ -314,29 +294,10 @@ void subghz_frequency_analyzer_worker_set_pair_callback( instance->context = context; } -void subghz_frequency_analyzer_worker_start( - SubGhzFrequencyAnalyzerWorker* instance, - SubGhzTxRx* txrx) { +void subghz_frequency_analyzer_worker_start(SubGhzFrequencyAnalyzerWorker* instance) { furi_assert(instance); furi_assert(!instance->worker_running); - /* - SubGhzRadioDeviceType radio_type = subghz_txrx_radio_device_get(txrx); - - if(radio_type == SubGhzRadioDeviceTypeExternalCC1101) { - instance->spi_bus = &furi_hal_spi_bus_handle_external; - instance->ext_radio = true; - } else if(radio_type == SubGhzRadioDeviceTypeInternal) { - */ - instance->spi_bus = &furi_hal_spi_bus_handle_subghz; - /* - instance->ext_radio = false; - } else { - furi_crash("Wrong subghz radio type"); - } - */ - instance->radio_device = subghz_devices_get_by_name(subghz_txrx_radio_device_get_name(txrx)); - instance->worker_running = true; furi_thread_start(instance->thread); diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h index 6533571d57..090ca81b33 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_worker.h @@ -47,9 +47,7 @@ void subghz_frequency_analyzer_worker_set_pair_callback( * @param instance SubGhzFrequencyAnalyzerWorker instance * @param txrx pointer to SubGhzTxRx */ -void subghz_frequency_analyzer_worker_start( - SubGhzFrequencyAnalyzerWorker* instance, - SubGhzTxRx* txrx); +void subghz_frequency_analyzer_worker_start(SubGhzFrequencyAnalyzerWorker* instance); /** Stop SubGhzFrequencyAnalyzerWorker * diff --git a/applications/main/subghz/views/subghz_frequency_analyzer.c b/applications/main/subghz/views/subghz_frequency_analyzer.c index 2ce2dfbbff..b070d6f831 100644 --- a/applications/main/subghz/views/subghz_frequency_analyzer.c +++ b/applications/main/subghz/views/subghz_frequency_analyzer.c @@ -254,7 +254,9 @@ bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { instance->selected_index = (instance->selected_index + 1) % instance->max_index; need_redraw = true; } - } else if(event->key == InputKeyOk) { + } else if( + (event->type != InputTypeRelease && event->type != InputTypeRepeat) && + event->key == InputKeyOk) { need_redraw = true; bool updated = false; uint32_t frequency_to_save; @@ -454,7 +456,7 @@ void subghz_frequency_analyzer_enter(void* context) { (SubGhzFrequencyAnalyzerWorkerPairCallback)subghz_frequency_analyzer_pair_callback, instance); - subghz_frequency_analyzer_worker_start(instance->worker, instance->txrx); + subghz_frequency_analyzer_worker_start(instance->worker); instance->rssi_last = 0; instance->selected_index = 0; From 73172b1090464c84c0da6a18af82256afe1e3575 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 29 Sep 2024 04:11:53 +0300 Subject: [PATCH 090/236] upd changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ba1209e0..c5073cc14a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ - Enforce int module (like in OFW) usage due to lack of required hardware on external boards (PathIsolate (+rf switch for multiple paths)) and incorrect usage and/or understanding the purpose of frequency analyzer app by users, it should be used only to get frequency of the remote placed around 1-10cm around flipper's left corner - Fix possible GSM mobile towers signal interference by limiting upper frequency to 920mhz max - Fix duplicated frequency lists and use user config for nearest frequency selector too + - Fix buttons logic, fix crash - Protocol improvements: - Princeton support for second button encoding type (8bit) - GangQi fix serial check - Hollarm add more button codes (thanks to @mishamyte for captures) +* Infrared: Update universal remote assets (by @amec0e | PR #813) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * OFW PR 3885: Add API to enforce ISO15693 mode (by @aaronjamt) From 79dc467ded5dd332b4d262dac8a45eb8f87dd09a Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Tue, 1 Oct 2024 13:26:35 +0300 Subject: [PATCH 091/236] New state added to avoid moving to blank screen --- .../nfc/helpers/protocol_support/nfc_protocol_support.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 7a07404fdc..0d63dc56bb 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -559,6 +559,7 @@ static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) { */ enum { NfcSceneEmulateStateWidget, /**< Widget view is displayed. */ + NfcSceneEmulateStateWidgetLog, /**< Widget view with Log button is displayed */ NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */ }; @@ -633,12 +634,14 @@ static bool "Log", nfc_protocol_support_common_widget_callback, instance); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog); } // Update TextBox data text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); consumed = true; } else if(event.event == GuiButtonTypeCenter) { - if(state == NfcSceneEmulateStateWidget) { + if(state == NfcSceneEmulateStateWidgetLog) { view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); scene_manager_set_scene_state( instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox); @@ -649,7 +652,7 @@ static bool if(state == NfcSceneEmulateStateTextBox) { view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); scene_manager_set_scene_state( - instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog); consumed = true; } } From 09a7cc2b46e7d4a2eaafface07a3b18968543ca9 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:10:19 +0900 Subject: [PATCH 092/236] [FL-3805] Fix EM4100 T5577 writing block order (#3904) --- lib/lfrfid/protocols/protocol_em4100.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index eca14c0466..8851a5369c 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -335,8 +335,8 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { request->t5577.block[0] = (LFRFID_T5577_MODULATION_MANCHESTER | protocol_em4100_get_t5577_bitrate(protocol) | (2 << LFRFID_T5577_MAXBLOCK_SHIFT)); - request->t5577.block[1] = protocol->encoded_data; - request->t5577.block[2] = protocol->encoded_data >> 32; + request->t5577.block[1] = protocol->encoded_data >> 32; + request->t5577.block[2] = protocol->encoded_data; request->t5577.blocks_to_write = 3; result = true; } From 3c93761d1d45467af08de10def6040098caf849e Mon Sep 17 00:00:00 2001 From: jay candel Date: Thu, 3 Oct 2024 00:28:24 +0800 Subject: [PATCH 093/236] [IR] universal remote additions (#3922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * multiple new additions Hisense K321UW, Soniq E55V13A, Soniq E32W13B and 2 others * updated with proper names Viano STV65UHD4K Hisense K321UW Hisense EN2B27 Soniq E55V13A Soniq E32W13B * format tv.ir * Update tv.ir * new universal ac additions Maytag M6X06F2A Panasonic CS-E9HKR * new universal audio additions Sony MHC_GSX75 Elac EA101EQ-G Philips FW750C Pioneer VSX-D1-S * remove final # audio.ir * Scripts: update deprecated methods use in python scripts * Scripts: add comment reading support to fff, preserve comments in infrared cleanup script * Scripts: improved infrared files cleanup script * Scripts: add missing new line at the end of file in infrared file cleanup script * Infrared: cleanup universal remotes Co-authored-by: あく --- .../infrared/resources/infrared/assets/ac.ir | 76 ++++++-- .../resources/infrared/assets/audio.ir | 169 ++++++++++------ .../resources/infrared/assets/projector.ir | 56 +++--- .../infrared/resources/infrared/assets/tv.ir | 181 +++++++++++------- scripts/flipper/utils/fff.py | 13 +- scripts/infrared.py | 30 ++- scripts/sconsdist.py | 2 +- scripts/update.py | 8 +- 8 files changed, 345 insertions(+), 190 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index df958ffba2..0a182c5d94 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -796,31 +796,31 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3302 1639 405 423 407 420 410 1234 405 421 409 1210 440 387 432 420 410 417 413 1231 408 1238 412 388 431 422 408 392 438 1233 406 1238 412 389 430 396 434 419 411 390 440 413 406 394 436 391 439 388 431 421 409 417 413 414 405 421 409 392 438 1233 406 421 409 417 413 414 405 395 435 418 412 388 432 421 409 1236 414 387 432 420 410 390 440 388 431 1213 437 1208 431 1239 411 1234 405 1213 437 1208 431 1214 436 1235 415 386 433 420 410 1234 405 422 408 418 412 389 431 396 434 1211 439 388 432 422 408 418 412 1233 406 1238 412 415 404 422 408 1237 413 388 432 422 408 1236 414 1205 434 1210 440 1205 434 392 438 390 440 1231 408 392 438 415 404 396 434 392 438 415 404 422 408 419 411 416 414 412 407 394 436 417 413 414 405 421 409 417 413 1232 407 419 411 390 440 1204 435 418 412 415 415 412 407 419 411 1208 431 421 409 418 412 388 432 421 409 392 438 415 404 395 435 1211 439 388 432 1213 437 416 414 386 433 1212 438 415 404 396 434 392 438 416 414 413 406 420 410 417 413 1231 409 418 412 389 430 1240 410 391 439 1205 434 420 410 390 440 387 432 420 410 417 413 -# +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 3293 1649 405 396 434 419 411 1207 432 395 435 1236 414 413 406 394 436 417 413 1232 407 1212 438 415 404 396 434 419 411 1234 405 1239 411 417 413 414 405 421 409 419 411 389 430 423 407 393 437 416 414 387 432 394 436 417 413 388 431 421 409 1236 414 387 432 421 409 418 412 414 405 422 408 418 412 415 404 1240 410 391 439 414 405 421 409 419 411 1234 405 1239 411 1208 431 1239 411 1235 404 1214 436 1210 440 1205 434 419 411 416 414 1204 435 418 412 415 404 396 434 393 437 1235 404 396 434 420 410 416 414 1231 408 1210 440 387 432 421 409 1235 415 414 405 395 435 418 412 1232 407 420 410 1208 431 422 408 1237 413 415 404 396 434 419 411 416 414 413 406 420 410 417 413 414 405 421 409 418 412 415 404 422 408 419 411 416 414 413 406 1238 412 415 404 422 408 1237 413 414 405 421 409 418 412 416 414 1231 408 418 412 415 404 422 408 419 411 416 414 413 406 420 410 1235 404 423 407 420 410 1234 405 422 408 1210 440 414 405 421 409 392 438 415 404 422 408 420 410 417 413 1231 408 419 411 416 414 413 406 1237 413 415 404 1239 411 417 413 1232 407 420 410 417 413 -# +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.330000 data: 3302 1614 440 414 405 421 409 1235 414 413 406 1238 411 415 404 422 408 419 411 1234 405 1240 409 418 412 415 404 422 408 1237 412 1232 407 420 410 417 413 414 405 422 408 419 411 416 414 413 406 420 410 417 413 414 405 421 409 418 412 415 404 1239 410 418 412 415 404 422 408 419 411 416 414 413 406 420 410 1234 405 422 408 419 411 416 414 414 405 1239 411 1207 432 1239 410 1235 414 1230 409 1236 413 1232 407 1237 412 415 415 412 407 1237 412 414 405 422 408 419 411 416 414 1231 408 419 411 416 414 413 406 1238 411 1206 433 421 409 418 412 1206 433 421 409 418 412 1206 433 1238 412 1207 432 1213 436 390 440 1232 407 420 410 417 413 414 405 421 409 418 412 415 404 422 408 419 411 416 414 413 406 421 409 418 412 415 404 422 408 419 411 1233 406 421 409 418 412 1232 407 420 410 418 412 389 430 422 408 1210 440 414 405 395 435 418 412 415 404 423 407 420 410 416 414 414 405 422 408 418 412 415 404 1240 409 1235 414 413 406 420 410 417 413 414 405 422 408 419 411 416 414 1231 408 418 412 415 404 1240 409 1209 440 387 432 1212 437 1234 405 1214 435 1209 440 1204 435 -# +# name: Heat_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 3301 1615 439 415 404 422 408 1210 439 414 405 1239 410 416 414 414 405 395 435 1209 440 1205 434 419 411 417 413 414 405 1212 437 1207 432 422 408 419 411 416 414 414 405 421 409 418 412 415 404 422 408 393 437 416 414 413 406 420 410 417 413 1205 434 420 410 417 413 414 405 421 409 418 412 415 404 422 408 1210 439 414 405 422 408 419 411 417 413 1205 434 1236 413 1206 433 1211 438 1207 432 1212 437 1209 440 1204 435 418 412 415 415 1204 435 418 412 415 404 422 408 419 411 1208 441 412 407 420 410 417 413 1205 434 1237 412 414 405 422 408 1210 439 415 404 396 434 419 411 1207 432 1213 436 417 413 1205 434 420 410 417 413 1231 408 419 411 416 414 413 406 421 409 418 412 414 405 422 408 419 411 415 404 424 406 421 409 418 412 415 404 1239 410 417 413 414 405 1239 410 416 414 413 406 422 408 419 411 1233 406 421 409 418 412 415 404 423 407 419 411 416 414 413 406 1238 411 416 414 413 406 421 409 1235 414 1230 409 418 412 415 415 412 407 420 410 417 413 414 405 422 408 1236 413 413 406 421 409 1235 414 1204 435 1210 439 1206 433 1212 437 1207 432 395 435 1236 413 -# +# name: Heat_lo type: raw frequency: 38000 duty_cycle: 0.330000 data: 3295 1646 408 420 410 390 440 1205 434 393 437 1234 405 421 409 419 411 415 415 1230 409 1210 440 414 405 421 409 418 412 1233 406 1212 437 416 414 413 406 394 436 418 412 415 404 422 408 393 437 416 414 413 406 420 410 417 413 414 405 421 409 1236 413 414 405 421 409 418 412 415 404 423 407 419 411 416 414 1231 408 418 412 415 415 412 407 421 409 1235 414 1204 435 1209 441 1205 434 1210 440 1205 434 1212 437 1207 432 395 435 419 411 1233 406 421 409 418 412 415 404 422 408 1238 411 415 404 396 434 419 411 1234 405 1239 410 417 413 414 405 1213 436 417 413 414 405 1213 436 1235 404 1214 435 1209 441 413 406 421 409 418 412 1206 433 394 436 418 412 388 431 422 408 419 411 415 415 386 433 420 410 418 412 414 405 422 408 419 411 389 430 1241 408 418 412 415 404 1240 409 417 413 414 405 395 435 419 411 1233 406 395 435 418 412 415 404 422 408 419 411 416 414 413 406 421 409 1236 413 413 406 394 436 1235 414 1230 409 418 412 415 404 422 408 420 410 417 413 414 405 421 409 1236 413 413 406 420 410 417 413 1232 407 1237 412 416 414 1230 409 1236 413 1205 434 1210 439 -# +# name: Off type: raw frequency: 38000 @@ -834,25 +834,25 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4349 4437 549 1615 551 1614 551 1614 551 1617 549 531 550 530 551 1615 550 531 550 532 549 530 551 531 550 530 551 1615 550 1614 551 531 550 1615 551 529 552 531 550 530 551 533 548 530 551 530 551 1616 549 1615 550 1616 550 1615 550 1614 551 1616 550 1615 551 1615 550 531 550 531 550 530 551 529 552 530 551 530 551 529 552 530 551 531 550 1615 550 532 549 1615 550 1616 550 531 550 531 550 530 551 530 551 529 552 532 549 530 551 530 551 531 550 529 552 531 550 1615 551 530 551 530 551 530 551 531 550 530 551 531 550 530 551 531 550 531 550 531 550 1616 550 1618 547 532 549 529 552 530 551 1615 551 1615 550 5379 4350 4436 550 1616 549 1615 551 1614 552 1615 550 529 552 530 551 1614 552 530 551 529 552 531 550 531 550 531 550 1614 552 1614 551 530 551 1615 550 530 551 530 551 530 551 530 551 531 550 532 549 1616 549 1615 551 1614 552 1615 550 1614 551 1616 550 1614 552 1615 550 529 552 530 551 530 551 530 551 531 550 531 550 530 551 530 551 531 550 1615 550 530 551 1615 550 1615 551 530 551 530 551 530 551 530 551 530 551 530 551 529 552 530 551 531 550 532 549 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 531 550 531 550 531 550 530 551 531 550 1615 551 1615 551 532 549 531 550 531 550 1616 549 1614 552 -# +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 4350 4438 549 1615 551 1614 552 1616 549 1616 550 530 551 531 550 1615 551 530 551 529 552 531 550 530 551 531 550 1614 551 1616 550 531 550 1616 549 530 551 531 550 530 551 529 552 530 551 531 550 1616 549 1616 550 1616 549 1616 550 1615 551 1614 551 1614 552 1615 551 530 551 531 550 530 551 531 550 531 550 529 552 532 549 531 550 530 551 1613 552 530 551 531 550 529 552 532 549 530 551 530 551 531 550 531 550 530 551 530 551 530 551 531 550 530 551 531 550 531 550 1615 551 529 552 530 551 530 551 530 551 530 551 530 551 530 551 530 551 532 549 531 550 531 550 532 549 531 550 531 550 530 551 530 551 5132 4351 4435 552 1616 550 1615 550 1615 551 1613 553 531 550 530 551 1615 550 530 551 531 550 531 550 530 551 532 549 1616 550 1616 549 530 551 1615 551 530 551 531 550 530 551 530 551 530 551 531 550 1615 551 1615 551 1614 551 1615 550 1615 551 1615 550 1615 550 1616 550 530 551 530 551 531 550 532 549 530 551 530 551 531 550 531 550 531 550 1615 550 530 551 530 551 530 551 529 552 531 550 530 551 531 550 531 550 530 551 530 551 531 550 530 551 530 551 530 551 531 550 1616 550 530 551 529 552 530 551 531 550 532 549 530 551 530 551 529 552 531 550 529 552 530 551 530 551 531 550 531 550 529 552 531 550 -# +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.330000 data: 4350 4436 550 1617 549 1615 550 1615 550 1617 548 530 551 531 550 1615 551 531 550 531 550 530 551 530 551 531 550 1614 552 1615 550 530 551 1614 551 531 550 531 550 531 550 529 552 532 549 530 551 1617 549 1616 549 1615 551 1619 547 1615 550 1615 550 1616 549 1616 550 530 551 531 550 530 551 530 551 531 550 530 551 529 552 529 552 530 551 1617 548 533 548 1615 551 1613 552 530 551 531 550 531 550 530 551 530 551 532 549 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 531 550 531 550 532 549 531 550 530 551 531 550 533 548 531 550 530 551 1617 548 1616 549 530 551 531 550 532 549 532 549 532 549 5200 4349 4436 550 1615 551 1615 551 1615 550 1616 550 531 550 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 1616 549 1615 551 530 551 1615 551 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 1616 550 1616 550 1615 550 1617 548 1616 549 1616 550 1615 550 531 550 530 551 531 550 531 550 532 549 530 551 531 550 531 550 532 549 1616 550 531 550 1616 550 1615 550 531 550 530 551 531 550 531 550 531 550 531 550 531 550 532 549 532 549 531 550 532 549 531 550 1616 550 531 550 530 551 532 549 532 549 530 551 532 549 531 550 532 549 531 550 1616 549 1617 549 531 550 530 551 531 550 532 549 532 549 -# +# name: Heat_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 4350 4437 547 1618 548 1620 546 1620 546 1619 547 534 547 535 546 1619 547 534 547 536 545 536 545 535 546 535 546 1619 547 1620 545 534 523 1644 546 535 522 559 546 535 546 534 547 535 546 535 545 1620 546 1620 546 1620 546 1619 547 1619 546 1619 547 1620 545 1620 546 535 546 534 547 537 520 558 523 558 547 534 547 536 521 559 522 559 522 1644 546 535 546 535 522 560 545 536 521 559 522 559 522 558 523 559 522 560 521 559 522 559 522 560 521 559 522 561 520 1644 521 1645 520 559 522 559 522 559 522 559 522 559 522 560 521 560 521 560 521 561 520 559 522 560 521 559 522 559 522 559 522 1644 522 559 522 5341 4349 4439 520 1645 521 1645 521 1646 519 1645 521 560 521 561 520 1645 521 560 521 560 521 559 522 560 521 561 520 1646 520 1645 521 561 520 1645 521 561 520 560 521 560 521 560 521 560 521 561 520 1644 522 1644 522 1645 520 1645 521 1645 521 1645 520 1646 520 1644 522 561 520 560 521 560 521 561 520 560 521 561 520 561 520 561 520 560 521 1646 520 562 519 561 520 561 520 562 519 560 521 560 521 561 520 561 520 560 521 560 521 561 520 560 521 560 521 562 519 1646 520 1645 521 561 520 561 520 561 520 560 521 560 521 561 520 560 521 559 522 560 521 561 520 561 520 560 521 562 519 559 522 1645 521 561 520 -# +# name: Heat_lo type: raw frequency: 38000 @@ -866,33 +866,79 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4387 4398 547 1609 547 530 547 1610 547 1611 545 530 547 530 547 1608 547 530 548 530 547 1611 545 532 546 532 547 1609 547 1610 547 531 547 1608 548 530 547 530 548 530 547 1610 546 1609 547 1610 547 1609 547 1609 547 1611 545 1609 548 1610 546 530 547 530 548 529 549 531 547 531 546 531 547 1608 548 1610 547 1608 548 533 545 1608 548 532 546 532 546 1611 545 532 547 532 545 530 548 1608 547 530 549 1608 547 1609 548 5203 4386 4398 547 1609 546 530 547 1609 546 1607 548 531 547 531 547 1609 547 530 548 531 547 1609 547 531 547 531 547 1608 547 1613 544 531 546 1609 547 531 547 531 547 532 546 1609 547 1609 546 1609 547 1609 547 1608 547 1608 548 1608 548 1609 547 530 547 530 547 530 547 532 546 530 547 530 548 1610 546 1608 547 1609 547 530 547 1609 547 530 547 530 548 1609 546 530 548 530 547 532 546 1610 546 531 546 1608 548 1608 548 -# +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 4388 4398 547 1608 548 531 546 1610 546 1609 547 530 547 529 548 1608 548 532 547 530 548 1612 544 529 549 530 548 1608 547 1609 547 531 546 1608 548 1607 549 529 549 1608 549 1609 548 1608 548 1608 548 1611 545 1608 548 530 548 1609 547 531 547 530 548 530 548 531 547 529 549 530 548 530 547 531 547 530 548 530 547 529 549 530 548 532 547 530 548 1609 547 1610 547 1608 548 1609 547 1608 548 1608 548 1608 548 1608 548 5203 4388 4396 549 1609 547 529 549 1610 546 1608 548 529 549 530 547 1609 547 530 548 529 549 1608 548 531 547 532 546 1609 547 1609 547 530 548 1609 548 1609 548 529 548 1608 548 1609 548 1609 547 1609 547 1608 548 1609 547 532 546 1608 548 531 548 531 548 530 548 530 548 531 547 530 548 531 548 531 547 530 548 530 548 530 548 531 547 529 549 529 549 1609 548 1608 548 1609 547 1608 548 1608 548 1608 548 1607 549 1607 549 -# +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.330000 data: 4384 4400 572 1585 571 505 572 1583 573 1584 572 508 570 503 575 1584 572 505 572 506 572 1583 573 504 573 506 571 1586 570 1585 572 532 546 1586 570 1585 571 506 571 1585 571 1583 573 1586 570 1583 573 1584 572 1589 569 505 572 1585 571 506 571 506 573 506 572 505 573 532 545 504 574 509 570 1611 545 506 572 1582 574 506 572 507 571 507 571 507 570 1584 572 507 571 1587 569 506 572 1584 572 1585 571 1583 573 1612 544 5179 4386 4400 570 1584 572 507 571 1583 572 1585 571 506 572 506 572 1584 572 505 572 504 574 1584 572 507 571 504 574 1583 573 1585 572 507 571 1584 572 1610 545 508 571 1587 569 1583 573 1583 573 1585 571 1585 572 1585 572 505 572 1584 572 505 573 507 572 506 571 504 574 505 573 505 574 508 571 1585 571 507 571 1585 571 506 571 506 572 504 574 505 572 1586 570 507 571 1586 570 505 573 1584 572 1585 571 1587 569 1584 573 -# +# name: Heat_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 4386 4398 575 1582 574 503 575 1583 573 1582 574 505 573 504 574 1582 574 506 572 508 570 1583 573 504 574 505 573 1583 573 1584 573 505 573 1582 575 1583 574 504 574 1582 574 1583 573 1583 573 1583 573 1585 571 1586 572 504 573 1584 572 504 573 505 573 505 573 505 573 504 573 506 571 1583 574 505 573 1583 573 1583 573 1584 572 1583 573 505 572 505 573 504 574 1583 574 505 573 505 573 504 574 505 572 1584 572 1584 573 5178 4387 4400 571 1583 573 504 574 1584 572 1584 572 507 572 504 574 1582 574 505 572 505 573 1583 573 504 574 504 574 1582 574 1584 573 503 574 1583 573 1582 574 505 573 1583 573 1582 575 1583 573 1610 546 1584 572 1583 573 505 573 1610 546 506 572 505 573 504 574 504 574 505 573 505 573 1584 573 505 573 1582 574 1584 572 1583 573 1583 573 504 574 503 575 504 574 1585 571 507 571 504 573 506 572 505 572 1584 572 1585 571 -# +# name: Heat_lo type: raw frequency: 38000 duty_cycle: 0.330000 data: 4388 4399 547 1608 548 530 548 1610 547 1610 547 529 549 529 548 1608 548 530 548 530 548 1607 549 533 545 531 548 1608 548 1610 546 531 547 1609 547 1608 548 529 549 1609 547 1609 547 1609 547 1609 547 1609 547 1608 548 529 548 1638 519 530 548 530 548 530 548 529 550 528 549 530 548 530 548 1608 548 530 548 1609 548 1610 547 1609 547 531 546 529 549 1608 548 530 548 1609 548 530 548 529 548 530 548 1609 548 1609 548 5205 4387 4398 547 1609 548 531 546 1609 547 1609 547 530 548 531 546 1609 547 531 548 530 573 1583 573 507 571 506 572 1583 573 1582 574 504 574 1581 575 1582 574 506 572 1583 574 1583 573 1583 573 1585 571 1584 572 1585 570 507 571 1582 574 505 574 532 545 505 573 505 572 506 571 505 573 505 573 1584 572 506 572 1583 573 1584 572 1583 573 505 572 504 573 1583 573 505 573 1586 571 506 572 505 573 507 572 1583 573 1584 572 -# +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4388 4399 572 1583 573 532 546 1585 571 1583 574 503 575 505 573 1584 572 504 574 505 573 1584 573 506 572 504 573 1584 573 1584 572 505 574 1611 545 506 573 1583 573 1585 571 1586 545 1609 547 531 547 1611 545 1608 548 1607 549 530 548 529 548 531 548 532 546 1610 546 533 545 530 547 1609 547 1610 547 1609 547 533 545 529 548 530 548 530 547 531 546 530 547 530 548 533 544 1608 548 1608 548 1610 546 1606 550 1609 547 5203 4388 4397 548 1609 547 531 547 1608 548 1608 548 530 548 530 548 1608 548 531 547 531 547 1610 546 531 547 530 548 1609 547 1611 546 532 547 1609 547 531 547 1608 548 1610 546 1609 547 1608 548 530 547 1609 547 1608 548 1609 547 531 546 530 548 530 547 530 547 1608 548 532 547 534 545 1608 548 1608 548 1609 547 530 548 531 547 531 547 532 546 531 546 531 547 532 546 530 548 1608 547 1608 548 1610 546 1608 548 1608 548 +# +# Model: Panasonic CS-E9HKR +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3466 1748 445 367 502 1292 444 422 444 420 445 422 443 420 445 421 445 420 445 363 503 420 445 422 444 420 445 424 445 1292 445 421 445 451 414 421 444 421 444 421 445 422 443 423 446 1292 443 1292 445 1292 445 421 497 372 444 1293 445 420 445 421 444 421 444 421 445 421 444 421 444 420 445 422 444 420 445 421 444 421 444 421 445 422 443 356 509 420 445 421 445 429 436 421 444 421 444 414 452 420 445 421 444 421 444 423 443 421 444 421 444 421 444 423 443 420 445 424 445 1291 444 1294 444 421 444 421 444 421 444 422 444 423 445 9989 3463 1752 444 424 445 1293 443 422 444 392 473 421 444 416 449 423 443 421 444 421 444 421 444 423 443 422 443 425 444 1292 444 423 443 422 443 419 446 421 444 423 443 421 444 426 443 1292 444 1294 443 1294 443 421 444 425 444 1294 444 422 443 421 444 422 443 424 442 421 444 421 444 421 444 423 444 421 444 422 443 421 444 423 443 425 444 1293 444 421 444 421 444 1293 444 423 446 1292 445 321 546 421 444 421 444 385 480 423 443 425 444 1291 445 1293 443 422 445 422 443 421 444 421 496 370 444 422 443 421 496 370 443 421 444 1291 497 1239 444 1284 504 1237 447 1293 444 425 444 1293 496 371 494 1241 495 370 496 369 497 297 517 421 496 370 495 369 496 370 496 370 495 370 495 369 496 372 494 370 495 369 496 370 495 370 496 370 495 373 495 1240 495 1241 495 1240 497 369 496 371 494 371 443 422 496 369 496 368 497 370 496 369 496 372 497 1239 496 1241 496 1241 496 370 495 310 555 371 495 371 494 368 497 370 495 370 496 361 504 370 495 369 496 372 494 372 494 369 496 371 494 371 495 373 495 1240 497 370 495 371 495 370 495 369 496 370 495 370 495 1242 495 373 492 370 495 366 500 369 496 371 495 370 495 371 495 369 496 371 494 370 495 371 495 371 494 371 494 370 495 372 494 380 488 1241 496 374 495 1241 495 1240 495 1240 495 1241 495 1242 494 1244 494 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3467 1751 442 425 444 1294 443 424 442 422 443 423 442 422 443 422 444 421 444 421 444 424 441 423 443 421 444 425 444 1294 443 424 442 423 442 422 443 425 440 423 443 423 442 427 442 1292 443 1322 415 1293 444 422 443 425 443 1295 443 424 441 421 444 423 442 424 442 425 440 423 442 423 442 424 442 292 573 421 444 423 442 422 444 422 443 422 443 423 442 425 441 423 442 425 440 421 444 424 442 422 443 422 443 422 443 424 442 422 443 422 443 422 443 424 442 421 444 426 443 1292 443 1294 444 423 442 422 443 423 442 424 442 426 442 9989 3465 1753 442 425 444 1292 445 424 442 424 441 421 444 422 443 422 444 423 442 423 442 352 513 423 443 423 442 425 443 1296 441 423 443 422 443 422 443 424 441 423 443 422 443 427 442 1293 442 1322 415 1293 444 421 444 425 443 1294 444 422 443 422 443 421 444 425 441 423 442 422 443 421 444 423 443 421 444 422 443 422 443 424 442 425 444 1261 475 422 443 422 443 1292 444 1293 442 1295 441 424 442 422 443 422 443 427 442 1322 415 1292 444 1292 444 1294 442 423 443 422 443 421 444 422 443 423 443 421 444 421 444 423 442 421 444 1291 444 1293 443 1294 442 1195 541 1293 444 425 443 1293 443 422 444 1322 414 421 444 422 443 423 443 422 443 423 442 422 443 425 441 422 443 422 443 423 442 423 443 422 443 422 443 421 444 423 444 421 444 424 444 1292 444 1293 444 1294 442 422 443 421 444 423 443 422 443 421 444 422 443 424 442 421 444 426 443 1291 444 1293 443 1293 444 422 443 421 444 422 444 421 444 422 443 421 444 423 443 423 442 421 444 422 443 423 444 421 444 421 444 421 444 422 444 425 444 1294 443 422 443 423 444 424 441 422 443 422 443 422 443 1294 443 422 443 421 444 424 442 421 444 413 452 421 444 423 443 421 444 422 443 422 443 422 444 421 444 422 443 422 443 423 443 426 443 1294 442 421 444 423 442 1293 443 1293 443 421 444 422 444 362 505 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3467 1751 444 424 444 1291 446 422 444 420 445 420 445 419 446 421 445 378 487 421 444 420 445 420 446 421 444 423 446 1321 415 451 415 420 445 420 445 419 446 421 445 419 446 422 447 1290 445 1291 445 1291 446 420 445 424 445 1293 445 420 445 420 445 420 445 421 445 420 445 420 445 419 446 421 445 420 445 421 444 420 445 421 445 420 445 420 445 420 445 421 445 419 446 420 445 420 445 420 446 420 445 421 444 419 446 422 444 420 445 420 445 421 444 421 445 421 444 424 445 1290 445 1292 446 420 445 421 444 420 445 423 443 424 444 9988 3465 1751 446 423 445 1291 446 421 445 420 445 420 445 420 445 422 444 420 445 420 445 421 444 421 446 421 444 424 445 1291 446 421 445 421 444 420 445 420 445 420 446 421 444 423 446 1291 444 1293 444 1292 445 420 445 423 446 1294 444 419 446 420 445 421 444 422 444 420 445 421 444 420 445 421 445 421 444 420 445 420 445 422 444 421 444 420 445 420 445 421 444 1320 415 1291 445 1292 444 422 444 420 445 419 446 420 445 421 445 421 444 424 445 1291 446 421 445 420 445 420 445 421 444 421 445 421 444 421 444 420 445 420 445 1291 445 1292 443 1290 445 1291 446 1292 445 424 444 1293 444 421 444 1292 445 422 443 421 444 381 485 420 445 420 445 420 445 422 444 421 444 420 445 421 444 421 445 421 444 421 444 420 445 421 445 421 444 424 445 1291 444 1292 445 1291 446 420 445 419 446 422 444 421 444 420 445 420 445 421 445 420 445 423 446 1291 444 1291 446 1292 445 420 445 420 445 422 444 420 445 420 445 420 445 422 444 421 444 420 445 419 446 420 446 420 445 419 446 419 446 421 445 423 445 1293 444 419 446 421 445 420 445 419 446 420 445 420 445 1292 445 420 445 420 445 421 445 420 445 419 446 419 446 420 446 419 446 420 445 420 445 420 446 420 445 420 445 420 445 421 445 419 446 420 445 423 446 1292 445 1290 445 1290 445 1292 444 1291 445 1293 445 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3463 1751 443 424 445 1293 443 422 444 420 445 421 444 421 444 422 444 421 444 421 444 421 444 353 513 422 443 425 444 1293 444 422 444 421 444 420 445 421 444 422 444 421 444 425 444 1291 444 1294 443 1293 444 421 444 424 445 1294 444 420 445 421 444 416 449 422 444 421 444 421 444 421 444 422 444 420 445 421 444 422 443 422 444 421 444 421 444 420 445 373 493 420 445 421 444 420 445 422 444 420 445 422 443 421 444 422 444 421 444 421 444 422 443 421 445 421 444 424 445 1291 444 1294 444 421 444 420 445 420 445 422 444 424 444 9991 3463 1751 445 425 444 1293 444 422 444 421 444 420 445 421 444 423 443 421 444 421 444 421 444 422 444 421 444 424 445 1293 444 422 444 420 445 422 443 421 444 424 442 421 444 425 444 1292 443 1292 445 1292 445 421 444 424 445 1293 445 421 444 421 444 421 444 422 444 421 444 421 444 422 443 422 444 423 442 421 444 423 442 423 443 425 443 1293 444 423 442 421 444 1292 444 422 443 425 443 1295 443 421 444 368 549 373 443 1293 496 1240 495 1240 495 1241 496 371 495 370 495 370 495 370 495 371 495 370 495 369 496 371 494 371 494 1240 442 1293 495 1240 496 1241 496 1240 497 373 496 1241 496 371 494 1184 553 370 495 370 495 370 496 370 443 423 494 370 443 424 495 370 442 423 442 422 443 424 442 423 442 423 442 425 440 424 442 424 441 427 442 1294 441 1294 443 1294 443 424 441 423 442 425 442 421 444 423 442 424 441 425 441 424 441 426 443 1293 442 1295 442 1295 441 424 441 394 471 425 441 423 442 423 442 424 441 424 442 424 441 423 442 423 442 424 442 423 442 424 441 423 442 424 442 428 440 1295 442 424 441 425 441 424 441 424 441 423 442 424 441 1294 443 424 441 424 441 424 442 423 442 423 442 423 442 425 441 424 441 424 441 424 441 424 442 424 441 449 416 423 442 424 442 427 442 1295 442 424 441 424 441 1295 442 427 442 1295 441 425 441 426 441 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3461 1753 439 429 440 1298 439 427 439 426 439 426 439 426 439 427 439 426 439 426 439 426 439 427 439 426 439 430 438 1298 438 428 438 426 439 427 438 427 438 427 439 427 438 430 414 1321 439 1299 413 1323 414 451 414 455 414 1324 414 452 413 451 414 453 413 436 430 452 413 452 413 451 414 453 413 452 413 451 414 452 413 454 412 452 413 452 413 452 413 453 413 452 413 452 413 452 413 454 413 453 412 452 413 452 413 453 413 452 413 452 413 452 413 453 413 452 413 456 412 1323 412 1325 413 452 413 453 412 453 412 454 412 455 390 10044 3438 1779 412 457 412 1324 413 454 412 453 412 453 412 453 412 454 412 453 412 453 412 454 411 455 411 453 412 457 389 1347 390 477 389 476 389 476 389 476 389 402 465 475 390 479 390 1346 389 1348 389 1347 390 476 389 479 390 1348 390 476 389 476 389 476 389 477 389 475 390 476 389 476 389 477 389 476 389 476 389 476 389 478 388 480 389 1347 390 475 390 476 389 1347 390 476 389 479 389 1349 389 476 389 476 389 476 389 477 389 476 389 479 390 1348 389 477 389 476 389 476 389 476 389 476 390 476 389 476 389 476 389 476 389 1346 389 1347 389 1346 389 1347 390 1346 390 479 390 1347 390 476 389 1374 363 474 415 454 388 480 386 475 414 454 387 474 391 476 414 455 387 473 392 476 414 453 389 476 389 475 390 475 390 476 390 476 390 479 389 1346 389 1347 390 1348 389 476 389 476 389 477 389 476 389 476 389 475 390 477 389 440 425 479 389 1345 390 1347 390 1347 390 476 389 475 390 477 389 476 389 476 389 476 389 477 389 476 389 476 389 475 390 477 389 476 389 476 389 476 389 477 389 479 390 1347 390 476 389 477 389 476 389 476 389 476 389 476 389 1347 390 476 389 476 389 477 389 503 362 476 389 476 389 477 389 475 390 476 389 476 389 477 389 476 389 476 389 476 389 477 389 479 389 1347 390 479 390 1347 390 1348 389 476 389 476 389 477 389 477 390 +# name: Off type: raw frequency: 38000 duty_cycle: 0.330000 -data: 4388 4399 572 1583 573 532 546 1585 571 1583 574 503 575 505 573 1584 572 504 574 505 573 1584 573 506 572 504 573 1584 573 1584 572 505 574 1611 545 506 573 1583 573 1585 571 1586 545 1609 547 531 547 1611 545 1608 548 1607 549 530 548 529 548 531 548 532 546 1610 546 533 545 530 547 1609 547 1610 547 1609 547 533 545 529 548 530 548 530 547 531 546 530 547 530 548 533 544 1608 548 1608 548 1610 546 1606 550 1609 547 5203 4388 4397 548 1609 547 531 547 1608 548 1608 548 530 548 530 548 1608 548 531 547 531 547 1610 546 531 547 530 548 1609 547 1611 546 532 547 1609 547 531 547 1608 548 1610 546 1609 547 1608 548 530 547 1609 547 1608 548 1609 547 531 546 530 548 530 547 530 547 1608 548 532 547 534 545 1608 548 1608 548 1609 547 530 548 531 547 531 547 532 546 531 546 531 547 532 546 530 548 1608 547 1608 548 1610 546 1608 548 1608 548 \ No newline at end of file +data: 3489 1725 493 375 493 1167 569 375 491 373 492 372 493 374 492 373 493 372 493 373 442 423 492 375 493 372 492 378 490 1245 443 423 493 374 491 375 490 374 442 424 492 374 491 379 440 1293 492 1245 443 1294 442 423 491 379 441 1296 491 375 441 423 442 423 442 425 441 423 442 425 440 424 441 425 441 424 441 421 444 424 441 425 441 423 442 423 442 423 442 453 413 424 442 451 414 424 441 426 440 424 441 424 441 424 441 425 441 424 441 424 441 424 441 426 440 425 440 427 442 1322 413 1296 442 424 441 423 442 424 441 424 442 427 441 9994 3463 1755 440 428 441 1296 441 426 440 425 440 426 439 425 440 426 440 423 442 425 440 425 440 372 494 425 440 428 440 1297 440 426 440 425 440 426 439 426 439 427 439 425 440 429 439 1297 439 1297 439 1297 440 426 439 429 440 1299 439 426 439 426 439 428 437 427 439 426 439 426 439 427 438 427 439 427 438 427 438 427 438 429 437 427 414 451 414 451 414 418 447 1323 414 452 413 455 414 1325 413 452 413 452 413 452 413 453 413 452 413 456 413 1324 413 453 413 452 413 453 412 452 413 454 412 453 412 453 412 453 412 452 413 1324 412 1323 412 1324 412 1324 412 1325 412 456 413 1324 412 404 461 1325 412 453 412 453 412 454 412 453 412 453 412 454 411 455 411 453 412 453 412 453 412 454 412 453 412 453 412 453 412 454 413 453 412 457 412 1323 413 1324 413 1324 412 453 412 453 412 454 413 453 412 453 412 453 412 454 412 452 413 456 412 1323 413 1324 413 1323 414 453 412 452 413 453 413 452 413 452 413 452 413 453 413 450 415 452 413 452 413 453 413 452 413 451 439 427 438 428 439 430 439 1298 439 426 439 428 438 426 439 428 437 426 439 427 438 1298 439 427 438 426 439 427 439 426 439 426 439 425 440 427 440 426 439 426 439 426 439 425 441 426 439 425 440 425 440 427 439 426 439 425 440 429 439 1297 440 1297 440 425 440 425 440 426 441 427 440 +# +# Model: Maytag M6X06F2A +# +name: Off +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 02 00 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index 6303278acf..f3ff853333 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # -# Model: NoName Unknown Audio remote +# Model: Yamaha RAV15 and NoName Unknown Audio remote name: Play type: parsed protocol: NEC @@ -69,37 +69,6 @@ protocol: NECext address: 84 79 00 00 command: 02 FD 00 00 # -# Model: Yamaha RAV15 -name: Play -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 43 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 15 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 07 00 00 00 -# -name: Next -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 40 00 00 00 -# -name: Prev -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 44 00 00 00 -# # Model: Yamaha RX-V375 name: Power type: parsed @@ -200,43 +169,43 @@ address: 04 00 00 00 command: 09 00 00 00 # # Model: Samsung HW-K450 Soundbar -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4637 4376 612 419 584 420 584 420 583 421 582 1427 531 1477 531 472 532 472 557 1452 556 1451 557 1451 557 1452 556 447 557 448 556 449 555 449 555 4453 554 450 554 450 554 451 553 450 554 451 553 451 553 451 553 450 554 1455 553 1454 554 1454 554 451 553 1454 554 1454 554 1455 553 1454 554 451 553 450 554 450 554 1455 553 55439 4554 4458 555 449 555 449 555 450 554 450 554 1455 553 1454 554 451 553 450 554 1454 554 1454 554 1454 554 1455 553 450 554 451 553 451 553 451 553 4453 554 451 553 451 552 451 553 451 553 451 553 451 553 451 553 451 553 1455 553 1455 553 1455 553 451 553 1455 553 1455 553 1455 553 1455 553 451 553 451 552 451 553 1455 553 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4636 4380 612 392 612 394 610 394 610 419 583 1399 557 1452 556 473 556 448 556 1428 581 1453 555 1453 555 1453 555 449 555 450 554 451 553 452 552 4457 551 452 552 452 552 452 552 452 552 452 552 1457 551 452 552 1457 551 452 552 452 552 453 551 1457 552 1457 551 452 552 1457 551 452 552 1457 551 1457 551 1457 552 452 552 55450 4551 4461 553 451 553 452 552 452 552 452 552 1456 552 1456 552 452 552 452 552 1456 552 1456 552 1456 552 1456 552 452 552 452 552 453 551 453 551 4456 551 453 551 453 551 453 551 453 551 453 551 1457 551 453 551 1457 552 453 551 454 550 454 550 1457 552 1457 551 454 550 1457 551 454 550 1458 551 1457 551 1458 550 454 550 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4640 4405 583 420 583 421 582 421 583 422 581 1427 531 1478 530 473 531 472 557 1452 557 1452 556 1452 556 1452 556 448 556 448 556 449 555 450 554 4454 554 451 553 451 553 451 553 451 553 1455 554 1455 553 1455 553 451 553 1455 553 1456 553 1456 553 451 553 451 553 451 553 451 554 1455 554 451 553 452 553 451 553 1456 553 55447 4556 4458 555 449 555 450 554 450 554 450 554 1455 553 1455 553 451 553 451 553 1455 553 1455 553 1455 553 1455 553 451 553 451 553 451 553 451 553 4454 553 451 553 450 554 451 553 450 554 1455 553 1455 553 1455 553 451 553 1455 553 1455 553 1455 553 451 553 451 553 451 553 450 554 1455 553 451 553 451 553 451 553 1455 553 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4636 4378 613 393 611 392 612 393 611 393 557 1451 558 1450 610 420 583 421 557 1427 581 1452 556 1452 556 1452 556 448 555 449 555 450 554 450 554 4455 553 451 553 451 553 451 553 451 553 451 553 451 553 452 552 1456 553 1456 552 1456 553 1456 552 451 553 1456 553 1456 552 1456 553 451 553 451 553 452 552 451 553 1456 552 55452 4553 4461 553 450 554 451 553 451 553 451 553 1456 553 1456 552 451 553 451 553 1456 552 1455 553 1455 553 1455 553 451 553 451 553 451 553 451 553 4456 552 451 553 451 553 451 553 451 553 451 553 451 553 451 553 1456 552 1455 553 1455 553 1455 553 451 553 1455 553 1456 552 1456 552 451 553 451 553 451 553 451 553 1456 552 -# +# name: Prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 255 113623 4638 4378 613 391 612 392 559 446 558 446 558 1477 531 1477 532 472 532 472 532 1476 532 1476 532 1476 532 1477 531 473 555 449 555 449 555 450 554 4455 554 450 554 450 554 450 554 450 554 1455 554 1455 554 450 554 1455 554 450 555 450 554 450 554 1455 554 451 553 451 553 1455 554 450 554 1455 554 1456 553 1455 554 450 554 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 4557 4430 611 392 610 394 559 445 559 446 558 1451 558 1477 531 448 556 472 532 1476 532 1477 532 1477 531 1477 531 473 556 449 555 449 555 450 554 4454 554 450 554 450 554 450 554 450 555 450 554 450 554 1455 554 1455 554 450 554 450 554 450 554 1455 554 1455 554 1455 553 451 553 450 554 1455 554 1455 554 1455 554 450 554 55458 4555 4459 554 450 554 450 554 450 554 450 554 1455 553 1455 553 450 554 450 554 1455 553 1455 553 1455 553 1455 553 450 554 450 554 450 554 450 554 4454 554 450 554 450 554 450 554 451 553 450 554 450 554 1455 553 1455 553 451 553 450 554 450 554 1455 553 1455 553 1455 553 450 554 451 553 1455 554 1455 553 1455 553 450 554 -# +# name: Mute type: raw frequency: 38000 @@ -285,7 +254,7 @@ type: parsed protocol: NECext address: 10 E7 00 00 command: 41 BE 00 00 -# +# # Model: Grundig CMS 5000 name: Power type: parsed @@ -304,7 +273,7 @@ type: parsed protocol: NECext address: 30 FC 00 00 command: 0D F2 00 00 -# +# name: Vol_dn type: parsed protocol: NECext @@ -316,19 +285,19 @@ type: parsed protocol: NECext address: 30 FC 00 00 command: 13 EC 00 00 -# +# name: Prev type: parsed protocol: NECext address: 30 FC 00 00 command: 11 EE 00 00 -# +# name: Mute type: parsed protocol: NECext address: 30 FC 00 00 command: 0C F3 00 00 -# +# # Model: Panasonic SA-PM193 name: Power type: parsed @@ -347,31 +316,31 @@ type: parsed protocol: Kaseikyo address: AA 02 20 00 command: A0 00 00 00 -# +# name: Vol_up type: parsed protocol: Kaseikyo address: A0 02 20 00 command: 00 02 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo address: A0 02 20 00 command: 10 02 00 00 -# +# name: Next type: parsed protocol: Kaseikyo address: AC 02 20 01 command: A1 00 00 00 -# +# name: Prev type: parsed protocol: Kaseikyo address: AC 02 20 01 command: 91 00 00 00 -# +# name: Mute type: parsed protocol: Kaseikyo @@ -476,33 +445,125 @@ protocol: NEC address: 00 00 00 00 command: 85 00 00 00 # -#Sony audio remote RM-SC3 +# Sony audio remote RM-SC3 name: Power type: parsed protocol: SIRC address: 10 00 00 00 command: 15 00 00 00 -# +# name: Vol_up type: parsed protocol: SIRC address: 10 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: SIRC address: 10 00 00 00 command: 13 00 00 00 -# +# name: Play type: parsed protocol: SIRC20 address: 3A 07 00 00 command: 32 00 00 00 -# +# name: Pause type: parsed protocol: SIRC20 address: 3A 07 00 00 command: 39 00 00 00 +# +# Model: Sony MHC_GSX75 +# +name: Pause +type: parsed +protocol: SIRC20 +address: 3A 07 00 00 +command: 38 00 00 00 +# +# Model: Elac EA101EQ-G +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 55 00 00 00 +# +# Model: Philips FW750C +# +name: Vol_up +type: parsed +protocol: RC5 +address: 10 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 10 00 00 00 +command: 11 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 35 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 36 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 10 00 00 00 +command: 0D 00 00 00 +# +name: Power +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 0C 00 00 00 +# +# Model: Pioneer VSX-D1-S +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5C A3 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1E E1 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/projector.ir b/applications/main/infrared/resources/infrared/assets/projector.ir index dcfcb06ffa..5a161d153c 100644 --- a/applications/main/infrared/resources/infrared/assets/projector.ir +++ b/applications/main/infrared/resources/infrared/assets/projector.ir @@ -1,6 +1,6 @@ Filetype: IR library file Version: 1 -# +# # Model: Smart name: Power type: parsed @@ -14,7 +14,7 @@ type: parsed protocol: NECext address: 83 55 00 00 command: 90 6F 00 00 -# +# # Model: Epson name: Power type: parsed @@ -82,7 +82,7 @@ type: parsed protocol: NECext address: 00 30 00 00 command: 83 7C 00 00 -# +# name: Vol_up type: parsed protocol: NECext @@ -106,13 +106,13 @@ type: parsed protocol: NECext address: 87 4E 00 00 command: 29 D6 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 87 4E 00 00 command: 08 F7 00 00 -# +# name: Vol_dn type: parsed protocol: NECext @@ -360,12 +360,6 @@ protocol: NECext address: 33 00 00 00 command: 0B F4 00 00 # -name: Power -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 90 6F 00 00 -# name: Vol_dn type: parsed protocol: NECext @@ -629,19 +623,19 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 219 27658 217 27663 216 27658 216 27634 216 27642 215 27646 217 27662 217 27637 216 27649 216 27649 218 27656 217 27658 215 27640 214 27636 217 27649 216 27644 218 27635 217 27630 215 27645 216 27631 215 27632 216 27650 216 27628 217 27630 214 27627 217 27623 215 27632 215 27641 216 27634 214 27633 215 27648 215 27648 217 27651 215 27635 216 27629 216 27630 216 2021 9254 4461 618 542 618 542 618 542 618 1675 619 1676 618 541 619 541 619 542 618 1677 617 543 617 543 617 1678 616 568 592 1702 592 1702 618 1676 618 542 618 542 618 543 617 1677 617 543 617 544 616 1678 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1678 616 544 616 1678 616 40239 9200 2247 617 99930 110 27739 119 27738 123 27750 126 27738 175 27617 214 27716 203 27604 213 27639 217 27631 214 27722 136 27753 119 27736 175 27618 246 27683 177 27619 245 27685 171 55486 244 27693 158 27635 241 27695 170 27693 129 27717 340 27530 113 27757 106 27751 124 27728 172 27707 126 27666 215 27708 123 27733 123 -# +# name: Vol_dn type: parsed protocol: NECext address: 18 E9 00 00 command: 49 B6 00 00 -# +# name: Power type: parsed protocol: NEC @@ -653,13 +647,13 @@ type: parsed protocol: NEC address: 02 00 00 00 command: 48 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 02 00 00 00 command: 40 00 00 00 -# +# name: Mute type: parsed protocol: NEC @@ -683,7 +677,7 @@ type: parsed protocol: NECext address: B8 57 00 00 command: 1E E1 00 00 -# +# name: Vol_up type: parsed protocol: NECext @@ -701,13 +695,13 @@ type: parsed protocol: NEC address: 32 00 00 00 command: 8F 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 32 00 00 00 command: 8C 00 00 00 -# +# name: Mute type: raw frequency: 38000 @@ -719,43 +713,37 @@ type: parsed protocol: NEC address: 00 00 00 00 command: A8 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 88 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 9C 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 8C 00 00 00 # -name: Power -type: parsed -protocol: NECext -address: 87 45 00 00 -command: 17 E8 00 00 -# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 9064 4354 666 1559 666 1562 662 1586 638 475 636 477 635 477 635 478 635 1590 635 1591 634 478 635 1591 634 478 634 478 635 478 634 1591 635 478 634 1591 634 478 635 478 634 478 635 1591 634 478 634 1591 635 478 634 478 634 1591 634 1591 635 1591 634 478 635 1591 634 478 634 1591 635 40957 9035 2144 634 95483 9047 2155 632 95484 9048 2153 633 -# +# name: Vol_dn type: parsed protocol: NECext address: 87 45 00 00 command: 50 AF 00 00 -# +# name: Mute type: raw frequency: 38000 @@ -767,13 +755,13 @@ type: parsed protocol: NECext address: FF FF 00 00 command: E8 17 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: FF FF 00 00 command: BD 42 00 00 -# +# name: Vol_dn type: parsed protocol: NECext @@ -785,13 +773,13 @@ type: parsed protocol: Kaseikyo address: 41 54 32 00 command: 05 00 00 00 -# +# name: Vol_up type: parsed protocol: Kaseikyo address: 41 54 32 00 command: 70 01 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 9df664a7b8..724cb90cec 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1687,14 +1687,6 @@ protocol: NEC address: 40 00 00 00 command: 10 00 00 00 # -# Philips OLED 934/12 -# -name: Power -type: parsed -protocol: RC6 -address: 00 00 00 00 -command: 0C 00 00 00 -# name: Mute type: parsed protocol: RC6 @@ -1725,44 +1717,6 @@ protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 # -# Model TCL 50P715X1 -# -name: Power -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: 54 00 00 00 -# -name: Mute -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: FC 00 00 00 -# -name: Vol_up -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: F4 00 00 00 -# -name: Vol_dn -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: 74 00 00 00 -# -name: Ch_next -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: B4 00 00 00 -# -name: Ch_prev -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: 34 00 00 00 -# # Model: JTC Genesis 5.5 # name: Mute @@ -1829,26 +1783,6 @@ protocol: NEC address: 01 00 00 00 command: 17 00 00 00 # -# Visio TV -# -name: Power -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 08 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 02 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 03 00 00 00 -# name: Ch_next type: parsed protocol: NEC @@ -1861,12 +1795,6 @@ protocol: NEC address: 04 00 00 00 command: 01 00 00 00 # -name: Mute -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 09 00 00 00 -# # Emerson TV # name: Power @@ -1942,4 +1870,111 @@ type: parsed protocol: NECext address: EA C7 00 00 command: 33 CC 00 00 - +# +# Model: Soniq E32W13B +# +name: Power +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 0E F1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 1A E5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 49 B6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 43 BC 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 51 AE 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 4D B2 00 00 +# +# Model: Hisense EN2B27 +# +name: Power +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 0D F2 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 0E F1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 44 BB 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 43 BC 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 4A B5 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 4B B4 00 00 +# +# Model: Viano STV65UHD4K +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1E E1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 06 F9 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5B A4 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 18 E7 00 00 diff --git a/scripts/flipper/utils/fff.py b/scripts/flipper/utils/fff.py index 3175a1b004..44727dc622 100644 --- a/scripts/flipper/utils/fff.py +++ b/scripts/flipper/utils/fff.py @@ -32,6 +32,16 @@ def readKeyValue(self): raise Exception("Unexpected line: not `key:value`") return data[0].strip(), data[1].strip() + def readComment(self): + if self.cursor == len(self.lines): + raise EOFError() + line = self.lines[self.cursor].strip() + if line.startswith("#"): + self.cursor += 1 + return line[1:].strip() + else: + return None + def readKey(self, key: str): k, v = self.readKeyValue() if k != key: @@ -67,7 +77,7 @@ def writeEmptyLine(self): self.writeLine("") def writeComment(self, text: str): - if text: + if text and len(text): self.writeLine(f"# {text}") else: self.writeLine("#") @@ -104,3 +114,4 @@ def load(self, filename: str): def save(self, filename: str): with open(filename, "w", newline="\n") as file: file.write("\n".join(self.lines)) + file.write("\n") diff --git a/scripts/infrared.py b/scripts/infrared.py index 9fa44a90aa..af5d91a567 100755 --- a/scripts/infrared.py +++ b/scripts/infrared.py @@ -27,37 +27,51 @@ def cleanup(self): return 1 data = [] - unique = {} + unique_combo = {} + unique_payload = {} while True: try: d = {} + d["comments"] = [] + while (comment := f.readComment()) is not None: + d["comments"].append(comment) d["name"] = f.readKey("name") d["type"] = f.readKey("type") - key = None + key_combo = f'{d["name"]}' + key_payload = None if d["type"] == "parsed": d["protocol"] = f.readKey("protocol") d["address"] = f.readKey("address") d["command"] = f.readKey("command") - key = f'{d["protocol"]}{d["address"]}{d["command"]}' + key_payload = f'{d["protocol"]}{d["address"]}{d["command"]}' + key_combo += key_payload elif d["type"] == "raw": d["frequency"] = f.readKey("frequency") d["duty_cycle"] = f.readKey("duty_cycle") d["data"] = f.readKey("data") - key = f'{d["frequency"]}{d["duty_cycle"]}{d["data"]}' + key_payload = f'{d["frequency"]}{d["duty_cycle"]}{d["data"]}' + key_combo += key_payload else: raise Exception(f'Unknown type: {d["type"]}') - if not key in unique: - unique[key] = d + + if not key_combo in unique_combo: + unique_combo[key_combo] = d data.append(d) + # Check payload only + if not key_payload in unique_payload: + unique_payload[key_payload] = d + else: + self.logger.warning(f"Duplicate payload, check manually: {d}") else: - self.logger.warn(f"Duplicate key: {key}") + self.logger.info(f"Duplicate data removed: {d}") except EOFError: break # Form new file f = FlipperFormatFile() f.setHeader(filetype, version) for i in data: - f.writeComment(None) + for comment in i["comments"]: + f.writeComment(comment) f.writeKey("name", i["name"]) f.writeKey("type", i["type"]) if i["type"] == "parsed": diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 314eabecd5..8759c3b843 100755 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -121,7 +121,7 @@ def copy(self) -> int: try: shutil.rmtree(self.output_dir_path) except Exception as ex: - self.logger.warn(f"Failed to clean output directory: {ex}") + self.logger.warning(f"Failed to clean output directory: {ex}") if not exists(self.output_dir_path): self.logger.debug(f"Creating output directory {self.output_dir_path}") diff --git a/scripts/update.py b/scripts/update.py index 47a5eeb27b..2b66207442 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -152,7 +152,7 @@ def generate(self): return 3 if not self.layout_check(updater_stage_size, dfu_size, radio_addr): - self.logger.warn("Memory layout looks suspicious") + self.logger.warning("Memory layout looks suspicious") if self.args.disclaimer != "yes": self.show_disclaimer() return 2 @@ -205,7 +205,7 @@ def generate(self): def layout_check(self, stage_size, fw_size, radio_addr): if stage_size > self.UPDATER_SIZE_THRESHOLD: - self.logger.warn( + self.logger.warning( f"Updater size {stage_size}b > {self.UPDATER_SIZE_THRESHOLD}b and is not loadable on older firmwares!" ) @@ -217,13 +217,13 @@ def layout_check(self, stage_size, fw_size, radio_addr): self.logger.debug(f"Expected reserved space size: {fw2stack_gap}") fw2stack_gap_pages = fw2stack_gap / self.FLASH_PAGE_SIZE if fw2stack_gap_pages < 0: - self.logger.warn( + self.logger.warning( f"Firmware image overlaps C2 region and is not programmable!" ) return False elif fw2stack_gap_pages < self.MIN_GAP_PAGES: - self.logger.warn( + self.logger.warning( f"Expected reserved flash size is too small (~{int(fw2stack_gap_pages)} page(s), need >={self.MIN_GAP_PAGES} page(s))" ) return False From 7fc209f0016f2cb7d33339423c93825dae0c17e5 Mon Sep 17 00:00:00 2001 From: IRecabarren Date: Wed, 2 Oct 2024 13:41:40 -0300 Subject: [PATCH 094/236] New layout for BadUSB (es-LA) (#3916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add files via upload * Readme: add SAST section Co-authored-by: あく --- ReadMe.md | 4 ++++ .../resources/badusb/assets/layouts/es-LA.kl | Bin 0 -> 256 bytes 2 files changed, 4 insertions(+) create mode 100644 applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl diff --git a/ReadMe.md b/ReadMe.md index f4c0dffedc..15235e6fcf 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -120,3 +120,7 @@ Also, see `ReadMe.md` files inside those directories for further details. - Website: [flipperzero.one](https://flipperzero.one) - Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) - Kickstarter: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) + +## SAST Tools + +- [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. diff --git a/applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl new file mode 100644 index 0000000000000000000000000000000000000000..2a6c78adcb13098a557c0278329df7bb0802e798 GIT binary patch literal 256 zcmaLLHx9y30KiboqK8iCHFN=y5-F7NABOv%fjJA_o}t{0JJ<61s|Opm?o8Z!Fy-Xg z%#0Tw-W=9my2Ih<500`&AJVnwv24sv1`x11BZ@`9XoOA%()Af wu3TG~hTI~2%tM7LHR?2I(juZwhb}$(3>Y#ZX3T^sGv+K Date: Wed, 2 Oct 2024 19:11:13 +0200 Subject: [PATCH 095/236] Prevent idle priority threads from potentially starving the FreeRTOS idle task (#3909) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FuriThread: Make FuriThreadPriorityIdle equal to the FreeRTOS one, remove FuriThreadPriorityNone This magic constant was meaningless, FuriThreadPriorityNormal is now assigned by default instead. * Make furi_thread_list_process private Its 'runtime' parameter is to be obtained from FreeRTOS, which means apps cannot do it. * DirectDraw: Remove an useless include and fix memory leak Makes this debug app compileable with uFBT out of the box Co-authored-by: あく --- applications/debug/direct_draw/direct_draw.c | 4 ++-- furi/core/thread.c | 16 +++++++++------- furi/core/thread.h | 5 ++--- furi/core/thread_list.c | 2 +- furi/core/thread_list.h | 8 -------- furi/core/thread_list_i.h | 19 +++++++++++++++++++ targets/f18/api_symbols.csv | 3 +-- targets/f7/api_symbols.csv | 3 +-- 8 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 furi/core/thread_list_i.h diff --git a/applications/debug/direct_draw/direct_draw.c b/applications/debug/direct_draw/direct_draw.c index bc1e554597..1e45a6d949 100644 --- a/applications/debug/direct_draw/direct_draw.c +++ b/applications/debug/direct_draw/direct_draw.c @@ -1,6 +1,5 @@ #include #include -#include #include #define BUFFER_SIZE (32U) @@ -42,10 +41,11 @@ static DirectDraw* direct_draw_alloc(void) { static void direct_draw_free(DirectDraw* instance) { furi_pubsub_unsubscribe(instance->input, instance->input_subscription); - instance->canvas = NULL; gui_direct_draw_release(instance->gui); furi_record_close(RECORD_GUI); furi_record_close(RECORD_INPUT_EVENTS); + + free(instance); } static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) { diff --git a/furi/core/thread.c b/furi/core/thread.c index 60cc628acb..65787c0e0f 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -1,5 +1,5 @@ #include "thread.h" -#include "thread_list.h" +#include "thread_list_i.h" #include "kernel.h" #include "memmgr.h" #include "memmgr_heap.h" @@ -65,6 +65,9 @@ struct FuriThread { // IMPORTANT: container MUST be the FIRST struct member static_assert(offsetof(FuriThread, container) == 0); +// Our idle priority should be equal to the one from FreeRTOS +static_assert(FuriThreadPriorityIdle == tskIDLE_PRIORITY); + static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); static int32_t __furi_thread_stdout_flush(FuriThread* thread); @@ -145,6 +148,8 @@ static void furi_thread_init_common(FuriThread* thread) { furi_thread_set_appid(thread, "driver"); } + thread->priority = FuriThreadPriorityNormal; + FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); if(mode == FuriHalRtcHeapTrackModeAll) { thread->heap_trace_enabled = true; @@ -269,7 +274,7 @@ void furi_thread_set_context(FuriThread* thread, void* context) { void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) { furi_check(thread); furi_check(thread->state == FuriThreadStateStopped); - furi_check(priority >= FuriThreadPriorityIdle && priority <= FuriThreadPriorityIsr); + furi_check(priority <= FuriThreadPriorityIsr); thread->priority = priority; } @@ -281,9 +286,7 @@ FuriThreadPriority furi_thread_get_priority(FuriThread* thread) { void furi_thread_set_current_priority(FuriThreadPriority priority) { furi_check(priority <= FuriThreadPriorityIsr); - - UBaseType_t new_priority = priority ? priority : FuriThreadPriorityNormal; - vTaskPrioritySet(NULL, new_priority); + vTaskPrioritySet(NULL, priority); } FuriThreadPriority furi_thread_get_current_priority(void) { @@ -345,7 +348,6 @@ void furi_thread_start(FuriThread* thread) { furi_thread_set_state(thread, FuriThreadStateStarting); uint32_t stack_depth = thread->stack_size / sizeof(StackType_t); - UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal; thread->is_active = true; @@ -355,7 +357,7 @@ void furi_thread_start(FuriThread* thread) { thread->name, stack_depth, thread, - priority, + thread->priority, thread->stack_buffer, &thread->container) == (TaskHandle_t)thread); } diff --git a/furi/core/thread.h b/furi/core/thread.h index e8cdeaeafb..d90ece85d6 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -30,11 +30,10 @@ typedef enum { * @brief Enumeration of possible FuriThread priorities. */ typedef enum { - FuriThreadPriorityNone = 0, /**< Uninitialized, choose system default */ - FuriThreadPriorityIdle = 1, /**< Idle priority */ + FuriThreadPriorityIdle = 0, /**< Idle priority */ FuriThreadPriorityLowest = 14, /**< Lowest */ FuriThreadPriorityLow = 15, /**< Low */ - FuriThreadPriorityNormal = 16, /**< Normal */ + FuriThreadPriorityNormal = 16, /**< Normal, system default */ FuriThreadPriorityHigh = 17, /**< High */ FuriThreadPriorityHighest = 18, /**< Highest */ FuriThreadPriorityIsr = diff --git a/furi/core/thread_list.c b/furi/core/thread_list.c index 5355a896ca..e542c192b0 100644 --- a/furi/core/thread_list.c +++ b/furi/core/thread_list.c @@ -84,7 +84,7 @@ FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, Fur } void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick) { - furi_check(instance); + furi_assert(instance); instance->runtime_previous = instance->runtime_current; instance->runtime_current = runtime; diff --git a/furi/core/thread_list.h b/furi/core/thread_list.h index d01aa24a04..f51aae8cd4 100644 --- a/furi/core/thread_list.h +++ b/furi/core/thread_list.h @@ -68,14 +68,6 @@ FuriThreadListItem* furi_thread_list_get_at(FuriThreadList* instance, size_t pos */ FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread); -/** Process items in the FuriThreadList instance - * - * @param instance The instance - * @param[in] runtime The runtime of the system since start - * @param[in] tick The tick when processing happened - */ -void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick); - /** Get percent of time spent in ISR * * @param instance The instance diff --git a/furi/core/thread_list_i.h b/furi/core/thread_list_i.h new file mode 100644 index 0000000000..44fe0a9cbc --- /dev/null +++ b/furi/core/thread_list_i.h @@ -0,0 +1,19 @@ +#pragma once + +#include "thread_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Process items in the FuriThreadList instance + * + * @param instance The instance + * @param[in] runtime The runtime of the system since start + * @param[in] tick The tick when processing happened + */ +void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 1d9ac28698..e808f0748d 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,74.0,, +Version,+,75.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1652,7 +1652,6 @@ Function,+,furi_thread_list_free,void,FuriThreadList* Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" Function,+,furi_thread_list_get_isr_time,float,FuriThreadList* Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" -Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t" Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index cb3037e48f..7317f7f047 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,74.0,, +Version,+,75.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1866,7 +1866,6 @@ Function,+,furi_thread_list_free,void,FuriThreadList* Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" Function,+,furi_thread_list_get_isr_time,float,FuriThreadList* Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" -Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t" Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" From 00c1611c33919069b7a3af679dbb6afc6a92bd3d Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 3 Oct 2024 02:51:07 +0900 Subject: [PATCH 096/236] Improve bit_buffer.h docs (#3783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve bit_buffer.h docs * Toolbox: update doxygen comments fix spelling * Toolbox: update bit lib docs Co-authored-by: あく Co-authored-by: gornekich --- lib/toolbox/bit_buffer.h | 378 +++++++++++++++++++++------------------ 1 file changed, 208 insertions(+), 170 deletions(-) diff --git a/lib/toolbox/bit_buffer.h b/lib/toolbox/bit_buffer.h index 5c50e729c6..bd95ec95ce 100644 --- a/lib/toolbox/bit_buffer.h +++ b/lib/toolbox/bit_buffer.h @@ -1,3 +1,9 @@ +/** Bit Buffer + * + * Various bits and bytes manipulation tools. + * + * @file bit_buffer.h + */ #pragma once #include @@ -10,118 +16,128 @@ extern "C" { typedef struct BitBuffer BitBuffer; -// Construction, deletion, reset - -/** - * Allocate a BitBuffer instance. +/** Allocate a BitBuffer instance. * - * @param [in] capacity_bytes maximum buffer capacity, in bytes - * @return pointer to the allocated BitBuffer instance + * @param[in] capacity_bytes maximum buffer capacity, in bytes + * + * @return pointer to the allocated BitBuffer instance */ BitBuffer* bit_buffer_alloc(size_t capacity_bytes); -/** - * Delete a BitBuffer instance. +/** Delete a BitBuffer instance. * - * @param [in,out] buf pointer to a BitBuffer instance + * @param[in,out] buf pointer to a BitBuffer instance */ void bit_buffer_free(BitBuffer* buf); -/** - * Clear all data from a BitBuffer instance. +/** Clear all data from a BitBuffer instance. * - * @param [in,out] buf pointer to a BitBuffer instance + * @param[in,out] buf pointer to a BitBuffer instance */ void bit_buffer_reset(BitBuffer* buf); // Copy and write -/** - * Copy another BitBuffer instance's contents to this one, replacing - * all of the original data. - * The destination capacity must be no less than the source data size. +/** Copy another BitBuffer instance's contents to this one, replacing all of the + * original data. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] other pointer to a BitBuffer instance to copy from - * @note + * @warning The destination capacity must be no less than the source data + * size. + * + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] other pointer to a BitBuffer instance to copy from */ void bit_buffer_copy(BitBuffer* buf, const BitBuffer* other); -/** - * Copy all BitBuffer instance's contents to this one, starting from start_index, - * replacing all of the original data. - * The destination capacity must be no less than the source data size - * counting from start_index. +/** Copy all BitBuffer instance's contents to this one, starting from + * start_index, replacing all of the original data. + * + * @warning The destination capacity must be no less than the source data + * size counting from start_index. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] other pointer to a BitBuffer instance to copy from - * @param [in] start_index index to begin copying source data from + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] other pointer to a BitBuffer instance to copy from + * @param[in] start_index index to begin copying source data from */ void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_index); -/** - * Copy all BitBuffer instance's contents to this one, ending with end_index, +/** Copy all BitBuffer instance's contents to this one, ending with end_index, * replacing all of the original data. - * The destination capacity must be no less than the source data size - * counting to end_index. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] other pointer to a BitBuffer instance to copy from - * @param [in] end_index index to end copying source data at + * @warning The destination capacity must be no less than the source data + * size counting to end_index. + * + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] other pointer to a BitBuffer instance to copy from + * @param[in] end_index index to end copying source data at */ void bit_buffer_copy_left(BitBuffer* buf, const BitBuffer* other, size_t end_index); -/** - * Copy a byte array to a BitBuffer instance, replacing all of the original data. - * The destination capacity must be no less than the source data size. +/** Copy a byte array to a BitBuffer instance, replacing all of the original + * data. + * + * @warning The destination capacity must be no less than the source data + * size. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] data pointer to the byte array to be copied - * @param [in] size_bytes size of the data to be copied, in bytes + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] data pointer to the byte array to be copied + * @param[in] size_bytes size of the data to be copied, in bytes */ void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); -/** - * Copy a byte array to a BitBuffer instance, replacing all of the original data. - * The destination capacity must be no less than the source data size. +/** Copy a byte array to a BitBuffer instance, replacing all of the original + * data. + * + * @warning The destination capacity must be no less than the source data + * size. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] data pointer to the byte array to be copied - * @param [in] size_bits size of the data to be copied, in bits + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] data pointer to the byte array to be copied + * @param[in] size_bits size of the data to be copied, in bits */ void bit_buffer_copy_bits(BitBuffer* buf, const uint8_t* data, size_t size_bits); -/** - * Copy a byte with parity array to a BitBuffer instance, replacing all of the original data. - * The destination capacity must be no less than the source data size. +/** Copy a byte with parity array to a BitBuffer instance, replacing all of the + * original data. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] data pointer to the byte array to be copied - * @param [in] size_bitss size of the data to be copied, in bits + * @warning The destination capacity must be no less than the source data + * size. + * + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] data pointer to the byte array to be copied + * @param[in] size_bits size of the data to be copied, in bits + * @note Parity bits are placed starting with the most significant bit + * of each byte and moving up. + * @note Example: DDDDDDDD PDDDDDDD DPDDDDDD DDP... */ void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size_t size_bits); -/** - * Write a BitBuffer instance's entire contents to an arbitrary memory location. - * The destination memory must be allocated. Additionally, the destination - * capacity must be no less than the source data size. +/** Write a BitBuffer instance's entire contents to an arbitrary memory location. * - * @param [in] buf pointer to a BitBuffer instance to write from - * @param [out] dest pointer to the destination memory location - * @param [in] size_bytes maximum destination data size, in bytes + * @warning The destination memory must be allocated. Additionally, the + * destination capacity must be no less than the source data size. + * + * @param[in] buf pointer to a BitBuffer instance to write from + * @param[out] dest pointer to the destination memory location + * @param[in] size_bytes maximum destination data size, in bytes */ void bit_buffer_write_bytes(const BitBuffer* buf, void* dest, size_t size_bytes); -/** - * Write a BitBuffer instance's entire contents to an arbitrary memory location. +/** Write a BitBuffer instance's entire contents to an arbitrary memory location. + * * Additionally, place a parity bit after each byte. - * The destination memory must be allocated. Additionally, the destination - * capacity must be no less than the source data size plus parity. * - * @param [in] buf pointer to a BitBuffer instance to write from - * @param [out] dest pointer to the destination memory location - * @param [in] size_bytes maximum destination data size, in bytes - * @param [out] bits_written actual number of bits writen, in bits + * @warning The destination memory must be allocated. Additionally, the + * destination capacity must be no less than the source data size + * plus parity. + * + * @param[in] buf pointer to a BitBuffer instance to write from + * @param[out] dest pointer to the destination memory location + * @param[in] size_bytes maximum destination data size, in bytes + * @param[out] bits_written actual number of bits written, in bits + * @note Parity bits are placed starting with the most significant bit of + * each byte and moving up. + * @note Example: DDDDDDDD PDDDDDDD DPDDDDDD DDP... */ void bit_buffer_write_bytes_with_parity( const BitBuffer* buf, @@ -129,15 +145,17 @@ void bit_buffer_write_bytes_with_parity( size_t size_bytes, size_t* bits_written); -/** - * Write a slice of BitBuffer instance's contents to an arbitrary memory location. - * The destination memory must be allocated. Additionally, the destination - * capacity must be no less than the requested slice size. - * - * @param [in] buf pointer to a BitBuffer instance to write from - * @param [out] dest pointer to the destination memory location - * @param [in] start_index index to begin copying source data from - * @param [in] size_bytes data slice size, in bytes +/** Write a slice of BitBuffer instance's contents to an arbitrary memory + * location. + * + * @warning The destination memory must be allocated. Additionally, the + * destination capacity must be no less than the requested slice + * size. + * + * @param[in] buf pointer to a BitBuffer instance to write from + * @param[out] dest pointer to the destination memory location + * @param[in] start_index index to begin copying source data from + * @param[in] size_bytes data slice size, in bytes */ void bit_buffer_write_bytes_mid( const BitBuffer* buf, @@ -147,176 +165,196 @@ void bit_buffer_write_bytes_mid( // Checks -/** - * Check whether a BitBuffer instance contains a partial byte (i.e. the bit count - * is not divisible by 8). +/** Check whether a BitBuffer instance contains a partial byte (i.e.\ the bit + * count is not divisible by 8). * - * @param [in] buf pointer to a BitBuffer instance to be checked - * @return true if the instance contains a partial byte, false otherwise + * @param[in] buf pointer to a BitBuffer instance to be checked + * + * @return true if the instance contains a partial byte, false otherwise */ bool bit_buffer_has_partial_byte(const BitBuffer* buf); -/** - * Check whether a BitBuffer instance's contents start with the designated byte. +/** Check whether a BitBuffer instance's contents start with the designated byte. * - * @param [in] buf pointer to a BitBuffer instance to be checked - * @param [in] byte byte value to be checked against - * @return true if data starts with designated byte, false otherwise + * @param[in] buf pointer to a BitBuffer instance to be checked + * @param[in] byte byte value to be checked against + * + * @return true if data starts with designated byte, false otherwise */ bool bit_buffer_starts_with_byte(const BitBuffer* buf, uint8_t byte); // Getters -/** - * Get a BitBuffer instance's capacity (i.e. the maximum possible amount of data), in bytes. +/** Get a BitBuffer instance's capacity (i.e.\ the maximum possible amount of + * data), in bytes. * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return capacity, in bytes + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return capacity, in bytes */ size_t bit_buffer_get_capacity_bytes(const BitBuffer* buf); -/** - * Get a BitBuffer instance's data size (i.e. the amount of stored data), in bits. - * Might be not divisible by 8 (see bit_buffer_is_partial_byte). +/** Get a BitBuffer instance's data size (i.e.\ the amount of stored data), in + * bits. + * + * @warning Might be not divisible by 8 (see bit_buffer_is_partial_byte). * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return data size, in bits. + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return data size, in bits. */ size_t bit_buffer_get_size(const BitBuffer* buf); /** - * Get a BitBuffer instance's data size (i.e. the amount of stored data), in bytes. - * If a partial byte is present, it is also counted. + * Get a BitBuffer instance's data size (i.e.\ the amount of stored data), in + * bytes. + * + * @warning If a partial byte is present, it is also counted. * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return data size, in bytes. + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return data size, in bytes. */ size_t bit_buffer_get_size_bytes(const BitBuffer* buf); -/** - * Get a byte value at a specified index in a BitBuffer instance. - * The index must be valid (i.e. less than the instance's data size in bytes). +/** Get a byte value at a specified index in a BitBuffer instance. + * + * @warning The index must be valid (i.e.\ less than the instance's data size + * in bytes). + * + * @param[in] buf pointer to a BitBuffer instance to be queried + * @param[in] index index of the byte in question * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @param [in] index index of the byte in question + * @return byte value */ uint8_t bit_buffer_get_byte(const BitBuffer* buf, size_t index); -/** - * Get a byte value starting from the specified bit index in a BitBuffer instance. - * The resulting byte might correspond to a single byte (if the index is a multiple - * of 8), or two overlapping bytes combined. - * The index must be valid (i.e. less than the instance's data size in bits). +/** Get a byte value starting from the specified bit index in a BitBuffer + * instance. + * + * @warning The resulting byte might correspond to a single byte (if the + * index is a multiple of 8), or two overlapping bytes combined. The + * index must be valid (i.e.\ less than the instance's data size in + * bits). * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @param [in] index bit index of the byte in question + * @param[in] buf pointer to a BitBuffer instance to be queried + * @param[in] index_bits bit index of the byte in question + * + * @return byte value */ uint8_t bit_buffer_get_byte_from_bit(const BitBuffer* buf, size_t index_bits); -/** - * Get the pointer to a BitBuffer instance's underlying data. +/** Get the pointer to a BitBuffer instance's underlying data. * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return pointer to the underlying data + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return pointer to the underlying data */ const uint8_t* bit_buffer_get_data(const BitBuffer* buf); -/** - * Get the pointer to a BitBuffer instance's underlying data. +/** Get the pointer to the parity data of a BitBuffer instance. * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return pointer to the underlying data + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return pointer to the parity data */ const uint8_t* bit_buffer_get_parity(const BitBuffer* buf); // Setters -/** - * Set byte value at a specified index in a BitBuffer instance. - * The index must be valid (i.e. less than the instance's data size in bytes). +/** Set byte value at a specified index in a BitBuffer instance. * - * @param [in,out] buf pointer to a BitBuffer instance to be modified - * @param [in] index index of the byte in question - * @param [in] byte byte value to be set at index + * @warning The index must be valid (i.e.\ less than the instance's data + * size in bytes). + * + * @param[in,out] buf pointer to a BitBuffer instance to be modified + * @param[in] index index of the byte in question + * @param[in] byte byte value to be set at index */ void bit_buffer_set_byte(BitBuffer* buf, size_t index, uint8_t byte); -/** - * Set byte and parity bit value at a specified index in a BitBuffer instance. - * The index must be valid (i.e. less than the instance's data size in bytes). +/** Set byte and parity bit value at a specified index in a BitBuffer instance. + * + * @warning The index must be valid (i.e.\ less than the instance's data + * size in bytes). * - * @param [in,out] buf pointer to a BitBuffer instance to be modified - * @param [in] index index of the byte in question - * @param [in] byte byte value to be set at index - * @param [in] parity parity bit value to be set at index + * @param[in,out] buff pointer to a BitBuffer instance to be modified + * @param[in] index index of the byte in question + * @param[in] byte byte value to be set at index + * @param[in] parity parity bit value to be set at index */ void bit_buffer_set_byte_with_parity(BitBuffer* buff, size_t index, uint8_t byte, bool parity); -/** - * Resize a BitBuffer instance to a new size, in bits. - * @warning May cause bugs. Use only if absolutely necessary. +/** Resize a BitBuffer instance to a new size, in bits. + * + * @warning May cause bugs. Use only if absolutely necessary. * - * @param [in,out] buf pointer to a BitBuffer instance to be resized - * @param [in] new_size the new size of the buffer, in bits + * @param[in,out] buf pointer to a BitBuffer instance to be resized + * @param[in] new_size the new size of the buffer, in bits */ void bit_buffer_set_size(BitBuffer* buf, size_t new_size); -/** - * Resize a BitBuffer instance to a new size, in bytes. - * @warning May cause bugs. Use only if absolutely necessary. +/** Resize a BitBuffer instance to a new size, in bytes. + * + * @warning May cause bugs. Use only if absolutely necessary. * - * @param [in,out] buf pointer to a BitBuffer instance to be resized - * @param [in] new_size_bytes the new size of the buffer, in bytes + * @param[in,out] buf pointer to a BitBuffer instance to be resized + * @param[in] new_size_bytes the new size of the buffer, in bytes */ void bit_buffer_set_size_bytes(BitBuffer* buf, size_t new_size_bytes); // Modification -/** - * Append all BitBuffer's instance contents to this one. The destination capacity - * must be no less than its original data size plus source data size. +/** Append all BitBuffer's instance contents to this one. + * + * @warning The destination capacity must be no less than its original + * data size plus source data size. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] other pointer to a BitBuffer instance to be appended + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] other pointer to a BitBuffer instance to be appended */ void bit_buffer_append(BitBuffer* buf, const BitBuffer* other); -/** - * Append a BitBuffer's instance contents to this one, starting from start_index. - * The destination capacity must be no less than the source data size - * counting from start_index. +/** Append a BitBuffer's instance contents to this one, starting from + * start_index. + * + * @warning The destination capacity must be no less than the source data + * size counting from start_index. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] other pointer to a BitBuffer instance to be appended - * @param [in] start_index index to begin copying source data from + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] other pointer to a BitBuffer instance to be appended + * @param[in] start_index index to begin copying source data from */ void bit_buffer_append_right(BitBuffer* buf, const BitBuffer* other, size_t start_index); -/** - * Append a byte to a BitBuffer instance. - * The destination capacity must be no less its original data size plus one. +/** Append a byte to a BitBuffer instance. + * + * @warning The destination capacity must be no less its original data + * size plus one. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] byte byte value to be appended + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] byte byte value to be appended */ void bit_buffer_append_byte(BitBuffer* buf, uint8_t byte); -/** - * Append a byte array to a BitBuffer instance. - * The destination capacity must be no less its original data size plus source data size. +/** Append a byte array to a BitBuffer instance. + * + * @warning The destination capacity must be no less its original data + * size plus source data size. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] data pointer to the byte array to be appended - * @param [in] size_bytes size of the data to be appended, in bytes + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] data pointer to the byte array to be appended + * @param[in] size_bytes size of the data to be appended, in bytes */ void bit_buffer_append_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); -/** - * Append a bit to a BitBuffer instance. - * The destination capacity must be sufficient to accomodate the additional bit. +/** Append a bit to a BitBuffer instance. + * + * @warning The destination capacity must be sufficient to accommodate the + * additional bit. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] bit bit value to be appended + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] bit bit value to be appended */ void bit_buffer_append_bit(BitBuffer* buf, bool bit); From 00f356469efddfcbe347cf3a709918cd287be3b2 Mon Sep 17 00:00:00 2001 From: noproto Date: Thu, 3 Oct 2024 19:27:20 -0400 Subject: [PATCH 097/236] Fix log levels --- .../protocols/mf_classic/mf_classic_poller.c | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 322526dfe3..1a2e43deec 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -580,7 +580,7 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) size_t next_key_index = (current_key_index + 1) % mf_classic_backdoor_keys_count; uint8_t backdoor_version = mf_classic_backdoor_keys[next_key_index].type - 1; - FURI_LOG_E(TAG, "Trying backdoor v%d", backdoor_version); + FURI_LOG_D(TAG, "Trying backdoor v%d", backdoor_version); dict_attack_ctx->current_key = mf_classic_backdoor_keys[next_key_index].key; // Attempt backdoor authentication @@ -588,11 +588,11 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) instance, 0, &dict_attack_ctx->current_key, MfClassicKeyTypeA, NULL, true); if((next_key_index == 0) && (error == MfClassicErrorProtocol || error == MfClassicErrorTimeout)) { - FURI_LOG_E(TAG, "No backdoor identified"); + FURI_LOG_D(TAG, "No backdoor identified"); dict_attack_ctx->backdoor = MfClassicBackdoorNone; instance->state = MfClassicPollerStateRequestKey; } else if(error == MfClassicErrorNone) { - FURI_LOG_E(TAG, "Backdoor identified: v%d", backdoor_version); + FURI_LOG_I(TAG, "Backdoor identified: v%d", backdoor_version); dict_attack_ctx->backdoor = mf_classic_backdoor_keys[next_key_index].type; instance->state = MfClassicPollerStateBackdoorReadSector; } else if( @@ -1071,12 +1071,10 @@ NfcCommand mf_classic_poller_handler_nested_analyze_prng(MfClassicPoller* instan if(hard_nt_count >= MF_CLASSIC_NESTED_NT_HARD_MINIMUM) { dict_attack_ctx->prng_type = MfClassicPrngTypeHard; - // FIXME: E -> D - FURI_LOG_E(TAG, "Detected Hard PRNG"); + FURI_LOG_D(TAG, "Detected Hard PRNG"); } else { dict_attack_ctx->prng_type = MfClassicPrngTypeWeak; - // FIXME: E -> D - FURI_LOG_E(TAG, "Detected Weak PRNG"); + FURI_LOG_D(TAG, "Detected Weak PRNG"); } instance->state = MfClassicPollerStateNestedController; @@ -1092,11 +1090,9 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt(MfClassicPoller* instance if(error != MfClassicErrorNone) { dict_attack_ctx->prng_type = MfClassicPrngTypeNoTag; - // FIXME: E -> D FURI_LOG_E(TAG, "Failed to collect nt"); } else { - // FIXME: E -> D - FURI_LOG_E(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]); + FURI_LOG_T(TAG, "nt: %02x%02x%02x%02x", nt.data[0], nt.data[1], nt.data[2], nt.data[3]); uint32_t nt_data = bit_lib_bytes_to_num_be(nt.data, sizeof(MfClassicNt)); if(!add_nested_nonce( &dict_attack_ctx->nested_nonce, @@ -1150,7 +1146,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) return command; } - FURI_LOG_E(TAG, "Full authentication successful"); + FURI_LOG_D(TAG, "Full authentication successful"); nt_prev = bit_lib_bytes_to_num_be(auth_ctx.nt.data, sizeof(MfClassicNt)); @@ -1183,7 +1179,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) dict_attack_ctx->calibrated = true; - FURI_LOG_E(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc); + FURI_LOG_D(TAG, "Static encrypted tag calibrated. Decrypted nonce: %08lx", nt_enc); instance->state = MfClassicPollerStateNestedController; return command; @@ -1226,14 +1222,14 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) } if(dict_attack_ctx->static_encrypted) { - FURI_LOG_E(TAG, "Static encrypted nonce detected"); + FURI_LOG_D(TAG, "Static encrypted nonce detected"); dict_attack_ctx->calibrated = true; instance->state = MfClassicPollerStateNestedController; return command; } // Find the distance between each nonce - FURI_LOG_E(TAG, "Calculating distance between nonces"); + FURI_LOG_D(TAG, "Calculating distance between nonces"); uint64_t known_key = bit_lib_bytes_to_num_be(dict_attack_ctx->nested_known_key.data, 6); uint8_t valid_distances = 0; for(uint32_t collection_cycle = 1; collection_cycle < MF_CLASSIC_NESTED_CALIBRATION_COUNT; @@ -1244,8 +1240,8 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) for(int i = 0; i < 65535; i++) { uint32_t nth_successor = crypto1_prng_successor(nt_prev, i); if(nth_successor == decrypted_nt_enc) { - FURI_LOG_E(TAG, "nt_enc (plain) %08lx", nth_successor); - FURI_LOG_E(TAG, "dist from nt prev: %i", i); + FURI_LOG_D(TAG, "nt_enc (plain) %08lx", nth_successor); + FURI_LOG_D(TAG, "dist from nt prev: %i", i); distances[valid_distances++] = i; nt_prev = nth_successor; found = true; @@ -1312,7 +1308,7 @@ NfcCommand mf_classic_poller_handler_nested_calibrate(MfClassicPoller* instance) mf_classic_poller_halt(instance); uint16_t d_dist = dict_attack_ctx->d_max - dict_attack_ctx->d_min; - FURI_LOG_E( + FURI_LOG_D( TAG, "Calibration completed: min=%u max=%u static=%s", dict_attack_ctx->d_min, @@ -1375,7 +1371,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst break; } - FURI_LOG_E(TAG, "Full authentication successful"); + FURI_LOG_D(TAG, "Full authentication successful"); // Step 2: Perform nested authentication a variable number of times to get nt_enc at a different PRNG offset // eg. Collect most commonly observed nonce from 3 auths to known sector and 4th to target, then separately the @@ -1429,7 +1425,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst // Ensure this isn't the same nonce as the previous collection if((dict_attack_ctx->nested_nonce.count == 1) && (dict_attack_ctx->nested_nonce.nonces[0].nt_enc == nt_enc)) { - FURI_LOG_E(TAG, "Duplicate nonce, dismissing collection attempt"); + FURI_LOG_D(TAG, "Duplicate nonce, dismissing collection attempt"); break; } @@ -1448,7 +1444,7 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst nth_successor, nth_successor ^ nt_enc, parity)) { found_nt_cnt++; if(found_nt_cnt > 1) { - FURI_LOG_E(TAG, "Ambiguous nonce, dismissing collection attempt"); + FURI_LOG_D(TAG, "Ambiguous nonce, dismissing collection attempt"); break; } found_nt = nth_successor; @@ -1489,24 +1485,24 @@ NfcCommand mf_classic_poller_handler_nested_collect_nt_enc(MfClassicPoller* inst FURI_LOG_E(TAG, "Failed to add nested nonce to array. OOM?"); } - FURI_LOG_E( + FURI_LOG_D( TAG, "Target: %u (nonce pair %u, key type %s, block %u)", dict_attack_ctx->nested_target_key, nonce_pair_index, (target_key_type == MfClassicKeyTypeA) ? "A" : "B", target_block); - FURI_LOG_E(TAG, "cuid: %08lx", cuid); - FURI_LOG_E(TAG, "nt_enc: %08lx", nt_enc); - FURI_LOG_E( + FURI_LOG_T(TAG, "cuid: %08lx", cuid); + FURI_LOG_T(TAG, "nt_enc: %08lx", nt_enc); + FURI_LOG_T( TAG, "parity: %u%u%u%u", ((parity >> 3) & 1), ((parity >> 2) & 1), ((parity >> 1) & 1), (parity & 1)); - FURI_LOG_E(TAG, "nt_enc prev: %08lx", nt_prev); - FURI_LOG_E(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); + FURI_LOG_T(TAG, "nt_enc prev: %08lx", nt_prev); + FURI_LOG_T(TAG, "nt_enc prev decrypted: %08lx", decrypted_nt_prev); } while(false); instance->state = MfClassicPollerStateNestedController; @@ -1609,7 +1605,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc break; } - FURI_LOG_E(TAG, "Full authentication successful"); + FURI_LOG_D(TAG, "Full authentication successful"); // Step 2: Collect nested nt and parity error = mf_classic_poller_auth_nested( @@ -1661,7 +1657,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc dict_attack_ctx->mf_classic_user_dict, is_weak); if(key_candidate != NULL) { - FURI_LOG_E( + FURI_LOG_I( TAG, "Found key candidate %06llx", bit_lib_bytes_to_num_be(key_candidate->data, sizeof(MfClassicKey))); @@ -1677,7 +1673,7 @@ NfcCommand mf_classic_poller_handler_nested_dict_attack(MfClassicPoller* instanc } } - FURI_LOG_E( + FURI_LOG_D( TAG, "Target: %u (key type %s, block %u) cuid: %08lx", dict_attack_ctx->nested_target_key, @@ -1942,7 +1938,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance dict_attack_ctx->nested_target_key = 0; if(mf_classic_is_card_read(instance->data)) { // All keys have been collected - FURI_LOG_E(TAG, "All keys collected and sectors read"); + FURI_LOG_D(TAG, "All keys collected and sectors read"); dict_attack_ctx->nested_phase = MfClassicNestedPhaseFinished; instance->state = MfClassicPollerStateSuccess; return command; @@ -2074,7 +2070,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (!(is_weak) && (dict_attack_ctx->attempt_count >= MF_CLASSIC_NESTED_HARD_RETRY_MAXIMUM))) { // Unpredictable, skip - FURI_LOG_E(TAG, "Failed to collect nonce, skipping key"); + FURI_LOG_W(TAG, "Failed to collect nonce, skipping key"); if(dict_attack_ctx->nested_nonce.nonces) { free(dict_attack_ctx->nested_nonce.nonces); dict_attack_ctx->nested_nonce.nonces = NULL; From 97bf92e9b97f0144839ea47ebd598cca04a10fc8 Mon Sep 17 00:00:00 2001 From: Astra Date: Fri, 4 Oct 2024 22:07:51 +0900 Subject: [PATCH 098/236] Remove BLE from BadUSB --- applications/main/bad_usb/application.fam | 2 +- applications/main/bad_usb/bad_usb_app.c | 8 - applications/main/bad_usb/bad_usb_app_i.h | 1 - .../main/bad_usb/helpers/bad_usb_hid.c | 155 +----------------- .../main/bad_usb/helpers/bad_usb_hid.h | 1 - .../bad_usb/scenes/bad_usb_scene_config.c | 88 ---------- .../bad_usb/scenes/bad_usb_scene_config.h | 1 - .../main/bad_usb/scenes/bad_usb_scene_work.c | 4 +- .../main/bad_usb/views/bad_usb_view.c | 2 +- 9 files changed, 6 insertions(+), 256 deletions(-) delete mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_config.c diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 8d3909fccc..9844e248df 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -7,7 +7,7 @@ App( icon="A_BadUsb_14", order=70, resources="resources", - fap_libs=["assets", "ble_profile"], + fap_libs=["assets"], fap_icon="icon.png", fap_category="USB", ) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 2d2d4be86c..1ee92bdf3f 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -35,7 +35,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { FuriString* temp_str = furi_string_alloc(); uint32_t version = 0; - uint32_t interface = 0; if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) { do { @@ -45,8 +44,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { break; if(!flipper_format_read_string(fff, "layout", temp_str)) break; - if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break; - if(interface > BadUsbHidInterfaceBle) break; state = true; } while(0); @@ -56,7 +53,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { if(state) { furi_string_set(app->keyboard_layout, temp_str); - app->interface = interface; Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo layout_file_info; @@ -68,7 +64,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { } } else { furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); - app->interface = BadUsbHidInterfaceUsb; } furi_string_free(temp_str); @@ -84,9 +79,6 @@ static void bad_usb_save_settings(BadUsbApp* app) { fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION)) break; if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; - uint32_t interface_id = app->interface; - if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1)) - break; } while(0); } diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index a4dd57d8b9..e63d0044c0 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -41,7 +41,6 @@ struct BadUsbApp { BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; - BadUsbHidInterface interface; FuriHalUsbInterface* usb_if_prev; }; diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index 5d7076314a..4973947696 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -1,12 +1,9 @@ #include "bad_usb_hid.h" #include -#include #include #define TAG "BadUSB HID" -#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" - void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) { furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); return NULL; @@ -72,155 +69,7 @@ static const BadUsbHidApi hid_api_usb = { .release_all = hid_usb_release_all, .get_led_state = hid_usb_get_led_state, }; - -typedef struct { - Bt* bt; - FuriHalBleProfileBase* profile; - HidStateCallback state_callback; - void* callback_context; - bool is_connected; -} BleHidInstance; - -static const BleProfileHidParams ble_hid_params = { - .device_name_prefix = "BadUSB", - .mac_xor = 0x0002, -}; - -static void hid_ble_connection_status_callback(BtStatus status, void* context) { - furi_assert(context); - BleHidInstance* ble_hid = context; - ble_hid->is_connected = (status == BtStatusConnected); - if(ble_hid->state_callback) { - ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); - } -} - -void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { - UNUSED(hid_cfg); - BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); - ble_hid->bt = furi_record_open(RECORD_BT); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - - ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); - furi_check(ble_hid->profile); - - furi_hal_bt_start_advertising(); - - bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); - - return ble_hid; -} - -void hid_ble_deinit(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - - bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(ble_hid->bt); - - furi_check(bt_profile_restore_default(ble_hid->bt)); - furi_record_close(RECORD_BT); - free(ble_hid); -} - -void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - ble_hid->state_callback = cb; - ble_hid->callback_context = context; -} - -bool hid_ble_is_connected(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_hid->is_connected; -} - -bool hid_ble_kb_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_press(ble_hid->profile, button); -} - -bool hid_ble_kb_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_release(ble_hid->profile, button); -} - -bool hid_ble_consumer_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_press(ble_hid->profile, button); -} - -bool hid_ble_consumer_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_release(ble_hid->profile, button); -} - -bool hid_ble_release_all(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - bool state = ble_profile_hid_kb_release_all(ble_hid->profile); - state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); - return state; -} - -uint8_t hid_ble_get_led_state(void* inst) { - UNUSED(inst); - FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); - return 0; -} - -static const BadUsbHidApi hid_api_ble = { - .init = hid_ble_init, - .deinit = hid_ble_deinit, - .set_state_callback = hid_ble_set_state_callback, - .is_connected = hid_ble_is_connected, - - .kb_press = hid_ble_kb_press, - .kb_release = hid_ble_kb_release, - .consumer_press = hid_ble_consumer_press, - .consumer_release = hid_ble_consumer_release, - .release_all = hid_ble_release_all, - .get_led_state = hid_ble_get_led_state, -}; - const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) { - if(interface == BadUsbHidInterfaceUsb) { - return &hid_api_usb; - } else { - return &hid_api_ble; - } -} - -void bad_usb_hid_ble_remove_pairing(void) { - Bt* bt = furi_record_open(RECORD_BT); - bt_disconnect(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - furi_hal_bt_stop_advertising(); - - bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - bt_forget_bonded_devices(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(bt); - - furi_check(bt_profile_restore_default(bt)); - furi_record_close(RECORD_BT); + UNUSED(interface); + return &hid_api_usb; } diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index 71d3a58e79..770230c632 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -9,7 +9,6 @@ extern "C" { typedef enum { BadUsbHidInterfaceUsb, - BadUsbHidInterfaceBle, } BadUsbHidInterface; typedef struct { diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c deleted file mode 100644 index 1acf3acb15..0000000000 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "../bad_usb_app_i.h" - -enum SubmenuIndex { - ConfigIndexKeyboardLayout, - ConfigIndexInterface, - ConfigIndexBleUnpair, -}; - -const char* const interface_mode_text[2] = { - "USB", - "BLE", -}; - -void bad_usb_scene_config_select_callback(void* context, uint32_t index) { - BadUsbApp* bad_usb = context; - if(index != ConfigIndexInterface) { - view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index); - } -} - -void bad_usb_scene_config_interface_callback(VariableItem* item) { - BadUsbApp* bad_usb = variable_item_get_context(item); - furi_assert(bad_usb); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, interface_mode_text[index]); - bad_usb->interface = index; - - view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ConfigIndexInterface); -} - -static void draw_menu(BadUsbApp* bad_usb) { - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_reset(var_item_list); - - variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL); - - VariableItem* item = variable_item_list_add( - var_item_list, "Interface", 2, bad_usb_scene_config_interface_callback, bad_usb); - if(bad_usb->interface == BadUsbHidInterfaceUsb) { - variable_item_set_current_value_index(item, 0); - variable_item_set_current_value_text(item, interface_mode_text[0]); - } else { - variable_item_set_current_value_index(item, 1); - variable_item_set_current_value_text(item, interface_mode_text[1]); - variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL); - } -} - -void bad_usb_scene_config_on_enter(void* context) { - BadUsbApp* bad_usb = context; - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_set_enter_callback( - var_item_list, bad_usb_scene_config_select_callback, bad_usb); - draw_menu(bad_usb); - variable_item_list_set_selected_item(var_item_list, 0); - - view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig); -} - -bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) { - BadUsbApp* bad_usb = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == ConfigIndexKeyboardLayout) { - scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout); - } else if(event.event == ConfigIndexInterface) { - draw_menu(bad_usb); - } else if(event.event == ConfigIndexBleUnpair) { - bad_usb_hid_ble_remove_pairing(); - } else { - furi_crash("Unknown key type"); - } - } - - return consumed; -} - -void bad_usb_scene_config_on_exit(void* context) { - BadUsbApp* bad_usb = context; - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_reset(var_item_list); -} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h index 423aedc51b..e640bb556b 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.h +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.h @@ -1,5 +1,4 @@ ADD_SCENE(bad_usb, file_select, FileSelect) ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, error, Error) -ADD_SCENE(bad_usb, config, Config) ADD_SCENE(bad_usb, config_layout, ConfigLayout) diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 0a383f0295..20e6d75e31 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -20,7 +20,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { bad_usb_script_close(app->bad_usb_script); app->bad_usb_script = NULL; - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); } consumed = true; } else if(event.event == InputKeyOk) { @@ -39,7 +39,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { void bad_usb_scene_work_on_enter(void* context) { BadUsbApp* app = context; - app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); + app->bad_usb_script = bad_usb_script_open(app->file_path, BadUsbHidInterfaceUsb); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); FuriString* file_name; diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 728f843487..7fb0b1434e 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -47,7 +47,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || (state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); - elements_button_left(canvas, "Config"); + elements_button_left(canvas, "Layout"); } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); if(!model->pause_wait) { From 8bd1c598de294d86be52604e6852c5f5dc0004bd Mon Sep 17 00:00:00 2001 From: Astra Date: Fri, 4 Oct 2024 22:08:08 +0900 Subject: [PATCH 099/236] Add the BadBLE app --- applications/system/bad_ble/application.fam | 12 + .../system/bad_ble/assets/Bad_BLE_48x22.png | Bin 0 -> 1097 bytes applications/system/bad_ble/bad_ble_app.c | 196 +++++ applications/system/bad_ble/bad_ble_app.h | 11 + applications/system/bad_ble/bad_ble_app_i.h | 53 ++ .../system/bad_ble/helpers/bad_ble_hid.c | 157 ++++ .../system/bad_ble/helpers/bad_ble_hid.h | 34 + .../system/bad_ble/helpers/ducky_script.c | 716 ++++++++++++++++++ .../system/bad_ble/helpers/ducky_script.h | 55 ++ .../bad_ble/helpers/ducky_script_commands.c | 241 ++++++ .../system/bad_ble/helpers/ducky_script_i.h | 76 ++ .../bad_ble/helpers/ducky_script_keycodes.c | 133 ++++ applications/system/bad_ble/icon.png | Bin 0 -> 96 bytes .../system/bad_ble/scenes/bad_ble_scene.c | 30 + .../system/bad_ble/scenes/bad_ble_scene.h | 29 + .../bad_ble/scenes/bad_ble_scene_config.c | 59 ++ .../bad_ble/scenes/bad_ble_scene_config.h | 7 + .../scenes/bad_ble_scene_config_layout.c | 49 ++ .../scenes/bad_ble_scene_confirm_unpair.c | 53 ++ .../bad_ble/scenes/bad_ble_scene_error.c | 65 ++ .../scenes/bad_ble_scene_file_select.c | 46 ++ .../scenes/bad_ble_scene_unpair_done.c | 37 + .../bad_ble/scenes/bad_ble_scene_work.c | 65 ++ .../system/bad_ble/views/bad_ble_view.c | 284 +++++++ .../system/bad_ble/views/bad_ble_view.h | 26 + 25 files changed, 2434 insertions(+) create mode 100644 applications/system/bad_ble/application.fam create mode 100644 applications/system/bad_ble/assets/Bad_BLE_48x22.png create mode 100644 applications/system/bad_ble/bad_ble_app.c create mode 100644 applications/system/bad_ble/bad_ble_app.h create mode 100644 applications/system/bad_ble/bad_ble_app_i.h create mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.c create mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.h create mode 100644 applications/system/bad_ble/helpers/ducky_script.c create mode 100644 applications/system/bad_ble/helpers/ducky_script.h create mode 100644 applications/system/bad_ble/helpers/ducky_script_commands.c create mode 100644 applications/system/bad_ble/helpers/ducky_script_i.h create mode 100644 applications/system/bad_ble/helpers/ducky_script_keycodes.c create mode 100644 applications/system/bad_ble/icon.png create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.h create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.h create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_error.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_file_select.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_work.c create mode 100644 applications/system/bad_ble/views/bad_ble_view.c create mode 100644 applications/system/bad_ble/views/bad_ble_view.h diff --git a/applications/system/bad_ble/application.fam b/applications/system/bad_ble/application.fam new file mode 100644 index 0000000000..e00e6eefcc --- /dev/null +++ b/applications/system/bad_ble/application.fam @@ -0,0 +1,12 @@ +App( + appid="bad_ble", + name="Bad BLE", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_ble_app", + stack_size=2 * 1024, + icon="A_BadUsb_14", + fap_libs=["assets", "ble_profile"], + fap_icon="icon.png", + fap_icon_assets="assets", + fap_category="Bluetooth", +) diff --git a/applications/system/bad_ble/assets/Bad_BLE_48x22.png b/applications/system/bad_ble/assets/Bad_BLE_48x22.png new file mode 100644 index 0000000000000000000000000000000000000000..0421d33c7d33bc7a1fef807906805a624682c885 GIT binary patch literal 1097 zcmbVL&1=*^6pz#vDXxl#f(2zrMJ$+PGD&tfq1%sbx^|&k*X>%1c<5|0+lFmEnoQla z2f;!Qg(A|E7lneLAQbed2dg*FiU*JW13gF&UaXULw|Xc&7)aim$@{(E`&=(i-b{7( zc5@t;Dvp~KCLw!!&UCQvoBrMBOkAR4Gjz(Y(LP3qpIbM$6egA<6j-BK0Y9&|-M#;Q0z7mqD zmC35L(y(-!9~uM$vB4a;gkli83tnWzIsU+}!Sv)>;=zFlZRGgFpk_*CFzSZ{=%OrG z5F(J#MV!WIMNen(4S*n0C8$U+Ey%K=Ap>fl_3(@^wChI2EVSs@DaX%K8W@t)Y&ONF zBKqN+BT*D&3W@xs&|)pA_qD;Sd?ynnhAbN6R_L(4 z;04pSmR#b|qM75_AJKMfL$NcuW?@Z`HLM6~Izxo4g@~X-l~^(&&?0u*&vE#?BEtgA zs+xk3ERPlPrk>9ipsBH*Xy!<|jV*c+#hyjl+z#VzVU-iH#%M?|^~0*~FSG}tJnvIK zn)d^Mkf?#{C2Tofa?_u5=`d-Ngw6wE7ee0!2W2*#V?xLvOzP>nCfFLW1XYEqP_ye$ zsMQoCE3%SN)hyq_+y7fWi3KGkv+~dEw6EAeChE~Lu*13#Umhl+XpU6l@3q+-Ze!!@i&IE_@@nmL>cSe>92c%OhNmZ1FK1QwV-NNG zT)(Doy0+X4b~xuZ_j>E=-gid8 M6~`vc?fmTOAF2pg_W%F@ literal 0 HcmV?d00001 diff --git a/applications/system/bad_ble/bad_ble_app.c b/applications/system/bad_ble/bad_ble_app.c new file mode 100644 index 0000000000..f243371986 --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app.c @@ -0,0 +1,196 @@ +#include "bad_ble_app_i.h" +#include +#include +#include +#include +#include + +#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/.badble.settings" +#define BAD_BLE_SETTINGS_FILE_TYPE "Flipper BadBLE Settings File" +#define BAD_BLE_SETTINGS_VERSION 1 +#define BAD_BLE_SETTINGS_DEFAULT_LAYOUT BAD_BLE_APP_PATH_LAYOUT_FOLDER "/en-US.kl" + +static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_ble_app_back_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_ble_app_tick_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_ble_load_settings(BadBleApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + bool state = false; + + FuriString* temp_str = furi_string_alloc(); + uint32_t version = 0; + + if(flipper_format_file_open_existing(fff, BAD_BLE_SETTINGS_PATH)) { + do { + if(!flipper_format_read_header(fff, temp_str, &version)) break; + if((strcmp(furi_string_get_cstr(temp_str), BAD_BLE_SETTINGS_FILE_TYPE) != 0) || + (version != BAD_BLE_SETTINGS_VERSION)) + break; + + if(!flipper_format_read_string(fff, "layout", temp_str)) break; + + state = true; + } while(0); + } + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); + + if(state) { + furi_string_set(app->keyboard_layout, temp_str); + + Storage* fs_api = furi_record_open(RECORD_STORAGE); + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat( + fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); + furi_record_close(RECORD_STORAGE); + if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) { + furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); + } + } else { + furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); + } + + furi_string_free(temp_str); +} + +static void bad_ble_save_settings(BadBleApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_always(fff, BAD_BLE_SETTINGS_PATH)) { + do { + if(!flipper_format_write_header_cstr( + fff, BAD_BLE_SETTINGS_FILE_TYPE, BAD_BLE_SETTINGS_VERSION)) + break; + if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; + } while(0); + } + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); +} + +BadBleApp* bad_ble_app_alloc(char* arg) { + BadBleApp* app = malloc(sizeof(BadBleApp)); + + app->bad_ble_script = NULL; + + app->file_path = furi_string_alloc(); + app->keyboard_layout = furi_string_alloc(); + if(arg && strlen(arg)) { + furi_string_set(app->file_path, arg); + } + + bad_ble_load_settings(app); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->dialogs = furi_record_open(RECORD_DIALOGS); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_ble_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_ble_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_ble_app_back_event_callback); + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, BadBleAppViewPopup, popup_get_view(app->popup)); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + BadBleAppViewConfig, + variable_item_list_get_view(app->var_item_list)); + + app->bad_ble_view = bad_ble_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewWork, bad_ble_view_get_view(app->bad_ble_view)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + if(!furi_string_empty(app->file_path)) { + scene_manager_next_scene(app->scene_manager, BadBleSceneWork); + } else { + furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect); + } + + return app; +} + +void bad_ble_app_free(BadBleApp* app) { + furi_assert(app); + + if(app->bad_ble_script) { + bad_ble_script_close(app->bad_ble_script); + app->bad_ble_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork); + bad_ble_view_free(app->bad_ble_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewPopup); + popup_free(app->popup); + + // Config menu + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig); + variable_item_list_free(app->var_item_list); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + + bad_ble_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + free(app); +} + +int32_t bad_ble_app(void* p) { + BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p); + + view_dispatcher_run(bad_ble_app->view_dispatcher); + + bad_ble_app_free(bad_ble_app); + return 0; +} diff --git a/applications/system/bad_ble/bad_ble_app.h b/applications/system/bad_ble/bad_ble_app.h new file mode 100644 index 0000000000..11954836e5 --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BadBleApp BadBleApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/bad_ble_app_i.h b/applications/system/bad_ble/bad_ble_app_i.h new file mode 100644 index 0000000000..d1f739bebb --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app_i.h @@ -0,0 +1,53 @@ +#pragma once + +#include "bad_ble_app.h" +#include "scenes/bad_ble_scene.h" +#include "helpers/ducky_script.h" +#include "helpers/bad_ble_hid.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/bad_ble_view.h" + +#define BAD_BLE_APP_BASE_FOLDER EXT_PATH("badusb") +#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/assets/layouts" +#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt" +#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl" + +typedef enum { + BadBleAppErrorNoFiles, + BadBleAppErrorCloseRpc, +} BadBleAppError; + +struct BadBleApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + Popup* popup; + VariableItemList* var_item_list; + + BadBleAppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadBle* bad_ble_view; + BadBleScript* bad_ble_script; + + BadBleHidInterface interface; +}; + +typedef enum { + BadBleAppViewWidget, + BadBleAppViewPopup, + BadBleAppViewWork, + BadBleAppViewConfig, +} BadBleAppView; diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.c b/applications/system/bad_ble/helpers/bad_ble_hid.c new file mode 100644 index 0000000000..c34b3c6461 --- /dev/null +++ b/applications/system/bad_ble/helpers/bad_ble_hid.c @@ -0,0 +1,157 @@ +#include "bad_ble_hid.h" +#include +#include +#include + +#define TAG "BadBLE HID" + +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" + +typedef struct { + Bt* bt; + FuriHalBleProfileBase* profile; + HidStateCallback state_callback; + void* callback_context; + bool is_connected; +} BleHidInstance; + +static const BleProfileHidParams ble_hid_params = { + .device_name_prefix = "BadBLE", + .mac_xor = 0x0002, +}; + +static void hid_ble_connection_status_callback(BtStatus status, void* context) { + furi_assert(context); + BleHidInstance* ble_hid = context; + ble_hid->is_connected = (status == BtStatusConnected); + if(ble_hid->state_callback) { + ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); + } +} + +void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { + UNUSED(hid_cfg); + BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); + ble_hid->bt = furi_record_open(RECORD_BT); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); + furi_check(ble_hid->profile); + + furi_hal_bt_start_advertising(); + + bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); + + return ble_hid; +} + +void hid_ble_deinit(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + + bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(ble_hid->bt); + + furi_check(bt_profile_restore_default(ble_hid->bt)); + furi_record_close(RECORD_BT); + free(ble_hid); +} + +void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + ble_hid->state_callback = cb; + ble_hid->callback_context = context; +} + +bool hid_ble_is_connected(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_hid->is_connected; +} + +bool hid_ble_kb_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_press(ble_hid->profile, button); +} + +bool hid_ble_kb_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_release(ble_hid->profile, button); +} + +bool hid_ble_consumer_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_press(ble_hid->profile, button); +} + +bool hid_ble_consumer_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_release(ble_hid->profile, button); +} + +bool hid_ble_release_all(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + bool state = ble_profile_hid_kb_release_all(ble_hid->profile); + state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); + return state; +} + +uint8_t hid_ble_get_led_state(void* inst) { + UNUSED(inst); + FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); + return 0; +} + +static const BadBleHidApi hid_api_ble = { + .init = hid_ble_init, + .deinit = hid_ble_deinit, + .set_state_callback = hid_ble_set_state_callback, + .is_connected = hid_ble_is_connected, + + .kb_press = hid_ble_kb_press, + .kb_release = hid_ble_kb_release, + .consumer_press = hid_ble_consumer_press, + .consumer_release = hid_ble_consumer_release, + .release_all = hid_ble_release_all, + .get_led_state = hid_ble_get_led_state, +}; + +const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface) { + UNUSED(interface); + return &hid_api_ble; +} + +void bad_ble_hid_ble_remove_pairing(void) { + Bt* bt = furi_record_open(RECORD_BT); + bt_disconnect(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + bt_forget_bonded_devices(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(bt); + + furi_check(bt_profile_restore_default(bt)); + furi_record_close(RECORD_BT); +} diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.h b/applications/system/bad_ble/helpers/bad_ble_hid.h new file mode 100644 index 0000000000..b06385d6dd --- /dev/null +++ b/applications/system/bad_ble/helpers/bad_ble_hid.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef enum { + BadBleHidInterfaceBle, +} BadBleHidInterface; + +typedef struct { + void* (*init)(FuriHalUsbHidConfig* hid_cfg); + void (*deinit)(void* inst); + void (*set_state_callback)(void* inst, HidStateCallback cb, void* context); + bool (*is_connected)(void* inst); + + bool (*kb_press)(void* inst, uint16_t button); + bool (*kb_release)(void* inst, uint16_t button); + bool (*consumer_press)(void* inst, uint16_t button); + bool (*consumer_release)(void* inst, uint16_t button); + bool (*release_all)(void* inst); + uint8_t (*get_led_state)(void* inst); +} BadBleHidApi; + +const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface); + +void bad_ble_hid_ble_remove_pairing(void); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script.c b/applications/system/bad_ble/helpers/ducky_script.c new file mode 100644 index 0000000000..a903fbdc4d --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script.c @@ -0,0 +1,716 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" +#include + +#define TAG "BadBle" + +#define WORKER_TAG TAG "Worker" + +#define BADUSB_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + +typedef enum { + WorkerEvtStartStop = (1 << 0), + WorkerEvtPauseResume = (1 << 1), + WorkerEvtEnd = (1 << 2), + WorkerEvtConnect = (1 << 3), + WorkerEvtDisconnect = (1 << 4), +} WorkerEvtFlags; + +static const char ducky_cmd_id[] = {"ID"}; + +static const uint8_t numpad_keys[10] = { + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, +}; + +uint32_t ducky_get_command_len(const char* line) { + uint32_t len = strlen(line); + for(uint32_t i = 0; i < len; i++) { + if(line[i] == ' ') return i; + } + return 0; +} + +bool ducky_is_line_end(const char chr) { + return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'); +} + +uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) { + uint16_t keycode = ducky_get_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; + } + + if((accept_chars) && (strlen(param) > 0)) { + return BADUSB_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF; + } + return 0; +} + +bool ducky_get_number(const char* param, uint32_t* val) { + uint32_t value = 0; + if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) { + *val = value; + return true; + } + return false; +} + +void ducky_numlock_on(BadBleScript* bad_ble) { + if((bad_ble->hid->get_led_state(bad_ble->hid_inst) & HID_KB_LED_NUM) == 0) { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); + } +} + +bool ducky_numpad_press(BadBleScript* bad_ble, const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + bad_ble->hid->kb_press(bad_ble->hid_inst, key); + bad_ble->hid->kb_release(bad_ble->hid_inst, key); + + return true; +} + +bool ducky_altchar(BadBleScript* bad_ble, const char* charcode) { + uint8_t i = 0; + bool state = false; + + bad_ble->hid->kb_press(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(bad_ble, charcode[i]); + if(state == false) break; + i++; + } + + bad_ble->hid->kb_release(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); + return state; +} + +bool ducky_altstring(BadBleScript* bad_ble, const char* param) { + uint32_t i = 0; + bool state = false; + + while(param[i] != '\0') { + if((param[i] < ' ') || (param[i] > '~')) { + i++; + continue; // Skip non-printable chars + } + + char temp_str[4]; + snprintf(temp_str, 4, "%u", param[i]); + + state = ducky_altchar(bad_ble, temp_str); + if(state == false) break; + i++; + } + return state; +} + +int32_t ducky_error(BadBleScript* bad_ble, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(bad_ble->st.error, sizeof(bad_ble->st.error), text, args); + + va_end(args); + return SCRIPT_STATE_ERROR; +} + +bool ducky_string(BadBleScript* bad_ble, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + if(param[i] != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); + bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); + } + } else { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + } + i++; + } + bad_ble->stringdelay = 0; + return true; +} + +static bool ducky_string_next(BadBleScript* bad_ble) { + if(bad_ble->string_print_pos >= furi_string_size(bad_ble->string_print)) { + return true; + } + + char print_char = furi_string_get_char(bad_ble->string_print, bad_ble->string_print_pos); + + if(print_char != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, print_char); + if(keycode != HID_KEYBOARD_NONE) { + bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); + bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); + } + } else { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + } + + bad_ble->string_print_pos++; + + return false; +} + +static int32_t ducky_parse_line(BadBleScript* bad_ble, FuriString* line) { + uint32_t line_len = furi_string_size(line); + const char* line_tmp = furi_string_get_cstr(line); + + if(line_len == 0) { + return SCRIPT_STATE_NEXT_LINE; // Skip empty lines + } + FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + + // Ducky Lang Functions + int32_t cmd_result = ducky_execute_cmd(bad_ble, line_tmp); + if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { + return cmd_result; + } + + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_ble, "No keycode defined for %s", line_tmp); + } + if((key & 0xFF00) != 0) { + // It's a modifier key + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + key |= ducky_get_keycode(bad_ble, line_tmp, true); + } + bad_ble->hid->kb_press(bad_ble->hid_inst, key); + bad_ble->hid->kb_release(bad_ble->hid_inst, key); + return 0; +} + +static bool ducky_set_usb_id(BadBleScript* bad_ble, const char* line) { + if(sscanf(line, "%lX:%lX", &bad_ble->hid_cfg.vid, &bad_ble->hid_cfg.pid) == 2) { + bad_ble->hid_cfg.manuf[0] = '\0'; + bad_ble->hid_cfg.product[0] = '\0'; + + uint8_t id_len = ducky_get_command_len(line); + if(!ducky_is_line_end(line[id_len + 1])) { + sscanf( + &line[id_len + 1], + "%31[^\r\n:]:%31[^\r\n]", + bad_ble->hid_cfg.manuf, + bad_ble->hid_cfg.product); + } + FURI_LOG_D( + WORKER_TAG, + "set id: %04lX:%04lX mfr:%s product:%s", + bad_ble->hid_cfg.vid, + bad_ble->hid_cfg.pid, + bad_ble->hid_cfg.manuf, + bad_ble->hid_cfg.product); + return true; + } + return false; +} + +static void bad_ble_hid_state_callback(bool state, void* context) { + furi_assert(context); + BadBleScript* bad_ble = context; + + if(state == true) { + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect); + } else { + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect); + } +} + +static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) { + uint8_t ret = 0; + uint32_t line_len = 0; + + furi_string_reset(bad_ble->line); + + do { + ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + for(uint16_t i = 0; i < ret; i++) { + if(bad_ble->file_buf[i] == '\n' && line_len > 0) { + bad_ble->st.line_nb++; + line_len = 0; + } else { + if(bad_ble->st.line_nb == 0) { // Save first line + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + line_len++; + } + } + if(storage_file_eof(script_file)) { + if(line_len > 0) { + bad_ble->st.line_nb++; + break; + } + } + } while(ret > 0); + + const char* line_tmp = furi_string_get_cstr(bad_ble->line); + bool id_set = false; // Looking for ID command at first line + if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { + id_set = ducky_set_usb_id(bad_ble, &line_tmp[strlen(ducky_cmd_id) + 1]); + } + + if(id_set) { + bad_ble->hid_inst = bad_ble->hid->init(&bad_ble->hid_cfg); + } else { + bad_ble->hid_inst = bad_ble->hid->init(NULL); + } + bad_ble->hid->set_state_callback(bad_ble->hid_inst, bad_ble_hid_state_callback, bad_ble); + + storage_file_seek(script_file, 0, true); + furi_string_reset(bad_ble->line); + + return true; +} + +static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) { + int32_t delay_val = 0; + + if(bad_ble->repeat_cnt > 0) { + bad_ble->repeat_cnt--; + delay_val = ducky_parse_line(bad_ble, bad_ble->line_prev); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; + } else if(delay_val < 0) { // Script error + bad_ble->st.error_line = bad_ble->st.line_cur - 1; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur - 1U); + return SCRIPT_STATE_ERROR; + } else { + return delay_val + bad_ble->defdelay; + } + } + + furi_string_set(bad_ble->line_prev, bad_ble->line); + furi_string_reset(bad_ble->line); + + while(1) { + if(bad_ble->buf_len == 0) { + bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + if(storage_file_eof(script_file)) { + if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) { + bad_ble->file_buf[bad_ble->buf_len] = '\n'; + bad_ble->buf_len++; + bad_ble->file_end = true; + } + } + + bad_ble->buf_start = 0; + if(bad_ble->buf_len == 0) return SCRIPT_STATE_END; + } + for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) { + if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) { + bad_ble->st.line_cur++; + bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1); + bad_ble->buf_start = i + 1; + furi_string_trim(bad_ble->line); + delay_val = ducky_parse_line(bad_ble, bad_ble->line); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; + } else if(delay_val < 0) { + bad_ble->st.error_line = bad_ble->st.line_cur; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur); + return SCRIPT_STATE_ERROR; + } else { + return delay_val + bad_ble->defdelay; + } + } else { + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + } + bad_ble->buf_len = 0; + if(bad_ble->file_end) return SCRIPT_STATE_END; + } + + return 0; +} + +static uint32_t bad_ble_flags_get(uint32_t flags_mask, uint32_t timeout) { + uint32_t flags = furi_thread_flags_get(); + furi_check((flags & FuriFlagError) == 0); + if(flags == 0) { + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout)); + } else { + uint32_t state = furi_thread_flags_clear(flags); + furi_check((state & FuriFlagError) == 0); + } + return flags; +} + +static int32_t bad_ble_worker(void* context) { + BadBleScript* bad_ble = context; + + BadBleWorkerState worker_state = BadBleStateInit; + BadBleWorkerState pause_state = BadBleStateRunning; + int32_t delay_val = 0; + + FURI_LOG_I(WORKER_TAG, "Init"); + File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bad_ble->line = furi_string_alloc(); + bad_ble->line_prev = furi_string_alloc(); + bad_ble->string_print = furi_string_alloc(); + + while(1) { + if(worker_state == BadBleStateInit) { // State: initialization + if(storage_file_open( + script_file, + furi_string_get_cstr(bad_ble->file_path), + FSAM_READ, + FSOM_OPEN_EXISTING)) { + if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) { + if(bad_ble->hid->is_connected(bad_ble->hid_inst)) { + worker_state = BadBleStateIdle; // Ready to run + } else { + worker_state = BadBleStateNotConnected; // USB not connected + } + } else { + worker_state = BadBleStateScriptError; // Script preload error + } + } else { + FURI_LOG_E(WORKER_TAG, "File open error"); + worker_state = BadBleStateFileError; // File open error + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateNotConnected) { // State: USB not connected + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { + worker_state = BadBleStateIdle; // Ready to run + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateWillRun; // Will run when USB is connected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateIdle) { // State: ready to start + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { // Start executing script + dolphin_deed(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->stringdelay = 0; + bad_ble->defstringdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->key_hold_nb = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateWillRun) { // State: start on connection + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + dolphin_deed(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->stringdelay = 0; + bad_ble->defstringdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + // extra time for PC to recognize Flipper as keyboard + flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriFlagWaitAny | FuriFlagNoClear, + 1500); + if(flags == (unsigned)FuriFlagErrorTimeout) { + // If nothing happened - start script execution + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; + furi_thread_flags_clear(WorkerEvtStartStop); + } + } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution + worker_state = BadBleStateNotConnected; + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateRunning) { // State: running + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriFlagWaitAny, + delay_cur); + + delay_val -= delay_cur; + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadBleStateRunning; + worker_state = BadBleStatePaused; // Pause + } + bad_ble->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + if(delay_val > 0) { + bad_ble->st.delay_remain--; + continue; + } + bad_ble->st.state = BadBleStateRunning; + delay_val = ducky_script_execute_next(bad_ble, script_file); + if(delay_val == SCRIPT_STATE_ERROR) { // Script error + delay_val = 0; + worker_state = BadBleStateScriptError; + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(delay_val == SCRIPT_STATE_END) { // End of script + delay_val = 0; + worker_state = BadBleStateIdle; + bad_ble->st.state = BadBleStateDone; + bad_ble->hid->release_all(bad_ble->hid_inst); + continue; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays + delay_val = bad_ble->defdelay; + bad_ble->string_print_pos = 0; + worker_state = BadBleStateStringDelay; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input + worker_state = BadBleStateWaitForBtn; + bad_ble->st.state = BadBleStateWaitForBtn; // Show long delays + } else if(delay_val > 1000) { + bad_ble->st.state = BadBleStateDelay; // Show long delays + bad_ble->st.delay_remain = delay_val / 1000; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if(worker_state == BadBleStateWaitForBtn) { // State: Wait for button Press + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriWaitForever); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + delay_val = 0; + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } + bad_ble->st.state = worker_state; + continue; + } + } else if(worker_state == BadBleStatePaused) { // State: Paused + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriWaitForever); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + if(pause_state == BadBleStateRunning) { + if(delay_val > 0) { + bad_ble->st.state = BadBleStateDelay; + bad_ble->st.delay_remain = delay_val / 1000; + } else { + bad_ble->st.state = BadBleStateRunning; + delay_val = 0; + } + worker_state = BadBleStateRunning; // Resume + } else if(pause_state == BadBleStateStringDelay) { + bad_ble->st.state = BadBleStateRunning; + worker_state = BadBleStateStringDelay; // Resume + } + } + continue; + } + } else if(worker_state == BadBleStateStringDelay) { // State: print string with delays + uint32_t delay = (bad_ble->stringdelay == 0) ? bad_ble->defstringdelay : + bad_ble->stringdelay; + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + delay); + + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadBleStateStringDelay; + worker_state = BadBleStatePaused; // Pause + } + bad_ble->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + bool string_end = ducky_string_next(bad_ble); + if(string_end) { + bad_ble->stringdelay = 0; + worker_state = BadBleStateRunning; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if( + (worker_state == BadBleStateFileError) || + (worker_state == BadBleStateScriptError)) { // State: error + uint32_t flags = + bad_ble_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command + + if(flags & WorkerEvtEnd) { + break; + } + } + } + + bad_ble->hid->set_state_callback(bad_ble->hid_inst, NULL, NULL); + bad_ble->hid->deinit(bad_ble->hid_inst); + + storage_file_close(script_file); + storage_file_free(script_file); + furi_string_free(bad_ble->line); + furi_string_free(bad_ble->line_prev); + furi_string_free(bad_ble->string_print); + + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) { + furi_assert(bad_ble); + memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout)); + memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout))); +} + +BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface) { + furi_assert(file_path); + + BadBleScript* bad_ble = malloc(sizeof(BadBleScript)); + bad_ble->file_path = furi_string_alloc(); + furi_string_set(bad_ble->file_path, file_path); + bad_ble_script_set_default_keyboard_layout(bad_ble); + + bad_ble->st.state = BadBleStateInit; + bad_ble->st.error[0] = '\0'; + bad_ble->hid = bad_ble_hid_get_interface(interface); + + bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble); + furi_thread_start(bad_ble->thread); + return bad_ble; +} //-V773 + +void bad_ble_script_close(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd); + furi_thread_join(bad_ble->thread); + furi_thread_free(bad_ble->thread); + furi_string_free(bad_ble->file_path); + free(bad_ble); +} + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) { + furi_assert(bad_ble); + + if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) { + // do not update keyboard layout while a script is running + return; + } + + File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(!furi_string_empty(layout_path)) { //-V1051 + if(storage_file_open( + layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + uint16_t layout[128]; + if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { + memcpy(bad_ble->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_ble_script_set_default_keyboard_layout(bad_ble); + } + storage_file_free(layout_file); +} + +void bad_ble_script_start_stop(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtStartStop); +} + +void bad_ble_script_pause_resume(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtPauseResume); +} + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) { + furi_assert(bad_ble); + return &(bad_ble->st); +} diff --git a/applications/system/bad_ble/helpers/ducky_script.h b/applications/system/bad_ble/helpers/ducky_script.h new file mode 100644 index 0000000000..044cae8256 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "bad_ble_hid.h" + +typedef enum { + BadBleStateInit, + BadBleStateNotConnected, + BadBleStateIdle, + BadBleStateWillRun, + BadBleStateRunning, + BadBleStateDelay, + BadBleStateStringDelay, + BadBleStateWaitForBtn, + BadBleStatePaused, + BadBleStateDone, + BadBleStateScriptError, + BadBleStateFileError, +} BadBleWorkerState; + +typedef struct { + BadBleWorkerState state; + size_t line_cur; + size_t line_nb; + uint32_t delay_remain; + size_t error_line; + char error[64]; +} BadBleState; + +typedef struct BadBleScript BadBleScript; + +BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface); + +void bad_ble_script_close(BadBleScript* bad_ble); + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path); + +void bad_ble_script_start(BadBleScript* bad_ble); + +void bad_ble_script_stop(BadBleScript* bad_ble); + +void bad_ble_script_start_stop(BadBleScript* bad_ble); + +void bad_ble_script_pause_resume(BadBleScript* bad_ble); + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_commands.c b/applications/system/bad_ble/helpers/ducky_script_commands.c new file mode 100644 index 0000000000..f70c5eba40 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_commands.c @@ -0,0 +1,241 @@ +#include +#include "ducky_script.h" +#include "ducky_script_i.h" + +typedef int32_t (*DuckyCmdCallback)(BadBleScript* bad_usb, const char* line, int32_t param); + +typedef struct { + char* name; + DuckyCmdCallback callback; + int32_t param; +} DuckyCmd; + +static int32_t ducky_fnc_delay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint32_t delay_val = 0; + bool state = ducky_get_number(line, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + + return ducky_error(bad_usb, "Invalid number %s", line); +} + +static int32_t ducky_fnc_defdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_strdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->stringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_defstrdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defstringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_string(BadBleScript* bad_usb, const char* line, int32_t param) { + line = &line[ducky_get_command_len(line) + 1]; + furi_string_set_str(bad_usb->string_print, line); + if(param == 1) { + furi_string_cat(bad_usb->string_print, "\n"); + } + + if(bad_usb->stringdelay == 0 && + bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately + bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); + if(!state) { + return ducky_error(bad_usb, "Invalid string %s", line); + } + } else { // stringdelay is set - run command in thread to keep handling external events + return SCRIPT_STATE_STRING_START; + } + + return 0; +} + +static int32_t ducky_fnc_repeat(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->repeat_cnt); + if((!state) || (bad_usb->repeat_cnt == 0)) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_sysrq(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + bad_usb->hid->release_all(bad_usb->hid_inst); + return 0; +} + +static int32_t ducky_fnc_altchar(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_usb); + bool state = ducky_altchar(bad_usb, line); + if(!state) { + return ducky_error(bad_usb, "Invalid altchar %s", line); + } + return 0; +} + +static int32_t ducky_fnc_altstring(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_usb); + bool state = ducky_altstring(bad_usb, line); + if(!state) { + return ducky_error(bad_usb, "Invalid altstring %s", line); + } + return 0; +} + +static int32_t ducky_fnc_hold(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + bad_usb->key_hold_nb++; + if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { + return ducky_error(bad_usb, "Too many keys are hold"); + } + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_release(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + if(bad_usb->key_hold_nb == 0) { + return ducky_error(bad_usb, "No keys are hold"); + } + bad_usb->key_hold_nb--; + bad_usb->hid->kb_release(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_media(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_media_keycode_by_name(line); + if(key == HID_CONSUMER_UNASSIGNED) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + bad_usb->hid->consumer_press(bad_usb->hid_inst, key); + bad_usb->hid->consumer_release(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_globe(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + + bad_usb->hid->consumer_press(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + bad_usb->hid->kb_release(bad_usb->hid_inst, key); + bad_usb->hid->consumer_release(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); + return 0; +} + +static int32_t ducky_fnc_waitforbutton(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + UNUSED(bad_usb); + UNUSED(line); + + return SCRIPT_STATE_WAIT_FOR_BTN; +} + +static const DuckyCmd ducky_commands[] = { + {"REM", NULL, -1}, + {"ID", NULL, -1}, + {"DELAY", ducky_fnc_delay, -1}, + {"STRING", ducky_fnc_string, 0}, + {"STRINGLN", ducky_fnc_string, 1}, + {"DEFAULT_DELAY", ducky_fnc_defdelay, -1}, + {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, + {"STRINGDELAY", ducky_fnc_strdelay, -1}, + {"STRING_DELAY", ducky_fnc_strdelay, -1}, + {"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1}, + {"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -1}, + {"REPEAT", ducky_fnc_repeat, -1}, + {"SYSRQ", ducky_fnc_sysrq, -1}, + {"ALTCHAR", ducky_fnc_altchar, -1}, + {"ALTSTRING", ducky_fnc_altstring, -1}, + {"ALTCODE", ducky_fnc_altstring, -1}, + {"HOLD", ducky_fnc_hold, -1}, + {"RELEASE", ducky_fnc_release, -1}, + {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, + {"MEDIA", ducky_fnc_media, -1}, + {"GLOBE", ducky_fnc_globe, -1}, +}; + +#define TAG "BadBle" + +#define WORKER_TAG TAG "Worker" + +int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line) { + size_t cmd_word_len = strcspn(line, " "); + for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { + size_t cmd_compare_len = strlen(ducky_commands[i].name); + + if(cmd_compare_len != cmd_word_len) { + continue; + } + + if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) { + if(ducky_commands[i].callback == NULL) { + return 0; + } else { + return (ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param); + } + } + } + + return SCRIPT_STATE_CMD_UNKNOWN; +} diff --git a/applications/system/bad_ble/helpers/ducky_script_i.h b/applications/system/bad_ble/helpers/ducky_script_i.h new file mode 100644 index 0000000000..a5581d2065 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_i.h @@ -0,0 +1,76 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ducky_script.h" +#include "bad_ble_hid.h" + +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) +#define SCRIPT_STATE_CMD_UNKNOWN (-4) +#define SCRIPT_STATE_STRING_START (-5) +#define SCRIPT_STATE_WAIT_FOR_BTN (-6) + +#define FILE_BUFFER_LEN 16 + +struct BadBleScript { + FuriHalUsbHidConfig hid_cfg; + const BadBleHidApi* hid; + void* hid_inst; + FuriThread* thread; + BadBleState st; + + FuriString* file_path; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + + uint32_t defdelay; + uint32_t stringdelay; + uint32_t defstringdelay; + uint16_t layout[128]; + + FuriString* line; + FuriString* line_prev; + uint32_t repeat_cnt; + uint8_t key_hold_nb; + + FuriString* string_print; + size_t string_print_pos; +}; + +uint16_t ducky_get_keycode(BadBleScript* bad_usb, const char* param, bool accept_chars); + +uint32_t ducky_get_command_len(const char* line); + +bool ducky_is_line_end(const char chr); + +uint16_t ducky_get_keycode_by_name(const char* param); + +uint16_t ducky_get_media_keycode_by_name(const char* param); + +bool ducky_get_number(const char* param, uint32_t* val); + +void ducky_numlock_on(BadBleScript* bad_usb); + +bool ducky_numpad_press(BadBleScript* bad_usb, const char num); + +bool ducky_altchar(BadBleScript* bad_usb, const char* charcode); + +bool ducky_altstring(BadBleScript* bad_usb, const char* param); + +bool ducky_string(BadBleScript* bad_usb, const char* param); + +int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line); + +int32_t ducky_error(BadBleScript* bad_usb, const char* text, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_keycodes.c b/applications/system/bad_ble/helpers/ducky_script_keycodes.c new file mode 100644 index 0000000000..290618c131 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_keycodes.c @@ -0,0 +1,133 @@ +#include +#include "ducky_script_i.h" + +typedef struct { + char* name; + uint16_t keycode; +} DuckyKey; + +static const DuckyKey ducky_keys[] = { + {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, + {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, + {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, + {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, + {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, + {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, + + {"CTRL", KEY_MOD_LEFT_CTRL}, + {"CONTROL", KEY_MOD_LEFT_CTRL}, + {"SHIFT", KEY_MOD_LEFT_SHIFT}, + {"ALT", KEY_MOD_LEFT_ALT}, + {"GUI", KEY_MOD_LEFT_GUI}, + {"WINDOWS", KEY_MOD_LEFT_GUI}, + + {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UPARROW", HID_KEYBOARD_UP_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, + + {"ENTER", HID_KEYBOARD_RETURN}, + {"BREAK", HID_KEYBOARD_PAUSE}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, + {"BACKSPACE", HID_KEYBOARD_DELETE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"ESCAPE", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + {"APP", HID_KEYBOARD_APPLICATION}, + + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, + {"F13", HID_KEYBOARD_F13}, + {"F14", HID_KEYBOARD_F14}, + {"F15", HID_KEYBOARD_F15}, + {"F16", HID_KEYBOARD_F16}, + {"F17", HID_KEYBOARD_F17}, + {"F18", HID_KEYBOARD_F18}, + {"F19", HID_KEYBOARD_F19}, + {"F20", HID_KEYBOARD_F20}, + {"F21", HID_KEYBOARD_F21}, + {"F22", HID_KEYBOARD_F22}, + {"F23", HID_KEYBOARD_F23}, + {"F24", HID_KEYBOARD_F24}, +}; + +static const DuckyKey ducky_media_keys[] = { + {"POWER", HID_CONSUMER_POWER}, + {"REBOOT", HID_CONSUMER_RESET}, + {"SLEEP", HID_CONSUMER_SLEEP}, + {"LOGOFF", HID_CONSUMER_AL_LOGOFF}, + + {"EXIT", HID_CONSUMER_AC_EXIT}, + {"HOME", HID_CONSUMER_AC_HOME}, + {"BACK", HID_CONSUMER_AC_BACK}, + {"FORWARD", HID_CONSUMER_AC_FORWARD}, + {"REFRESH", HID_CONSUMER_AC_REFRESH}, + + {"SNAPSHOT", HID_CONSUMER_SNAPSHOT}, + + {"PLAY", HID_CONSUMER_PLAY}, + {"PAUSE", HID_CONSUMER_PAUSE}, + {"PLAY_PAUSE", HID_CONSUMER_PLAY_PAUSE}, + {"NEXT_TRACK", HID_CONSUMER_SCAN_NEXT_TRACK}, + {"PREV_TRACK", HID_CONSUMER_SCAN_PREVIOUS_TRACK}, + {"STOP", HID_CONSUMER_STOP}, + {"EJECT", HID_CONSUMER_EJECT}, + + {"MUTE", HID_CONSUMER_MUTE}, + {"VOLUME_UP", HID_CONSUMER_VOLUME_INCREMENT}, + {"VOLUME_DOWN", HID_CONSUMER_VOLUME_DECREMENT}, + + {"FN", HID_CONSUMER_FN_GLOBE}, + {"BRIGHT_UP", HID_CONSUMER_BRIGHTNESS_INCREMENT}, + {"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT}, +}; + +uint16_t ducky_get_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { + size_t key_cmd_len = strlen(ducky_keys[i].name); + if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_keys[i].keycode; + } + } + + return HID_KEYBOARD_NONE; +} + +uint16_t ducky_get_media_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_media_keys); i++) { + size_t key_cmd_len = strlen(ducky_media_keys[i].name); + if((strncmp(param, ducky_media_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_media_keys[i].keycode; + } + } + + return HID_CONSUMER_UNASSIGNED; +} diff --git a/applications/system/bad_ble/icon.png b/applications/system/bad_ble/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..27355f8dbab9f62f03c3114bd345117b72703df2 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsse8IOhE&W+{!>5Ur5*F)|L$Vj tulr2n@!{d|IW8GdR-3Ztdxz&lMuxeII5)+8P-F*b^>p=fS?83{1OR(_90~vc literal 0 HcmV?d00001 diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.c b/applications/system/bad_ble/scenes/bad_ble_scene.c new file mode 100644 index 0000000000..351bb1e794 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene.c @@ -0,0 +1,30 @@ +#include "bad_ble_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_ble_scene_on_enter_handlers[])(void*) = { +#include "bad_ble_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 bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_ble_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 bad_ble_scene_on_exit_handlers[])(void* context) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_ble_scene_handlers = { + .on_enter_handlers = bad_ble_scene_on_enter_handlers, + .on_event_handlers = bad_ble_scene_on_event_handlers, + .on_exit_handlers = bad_ble_scene_on_exit_handlers, + .scene_num = BadBleSceneNum, +}; diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.h b/applications/system/bad_ble/scenes/bad_ble_scene.h new file mode 100644 index 0000000000..25b19fc4b5 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadBleScene##id, +typedef enum { +#include "bad_ble_scene_config.h" + BadBleSceneNum, +} BadBleScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_ble_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_ble_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 "bad_ble_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 "bad_ble_scene_config.h" +#undef ADD_SCENE diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.c b/applications/system/bad_ble/scenes/bad_ble_scene_config.c new file mode 100644 index 0000000000..1f64f19039 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config.c @@ -0,0 +1,59 @@ +#include "../bad_ble_app_i.h" + +enum SubmenuIndex { + ConfigIndexKeyboardLayout, + ConfigIndexBleUnpair, +}; + +void bad_ble_scene_config_select_callback(void* context, uint32_t index) { + BadBleApp* bad_ble = context; + + view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index); +} + +static void draw_menu(BadBleApp* bad_ble) { + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_reset(var_item_list); + + variable_item_list_add(var_item_list, "Keyboard Layout (Global)", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Unpair Device", 0, NULL, NULL); +} + +void bad_ble_scene_config_on_enter(void* context) { + BadBleApp* bad_ble = context; + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_set_enter_callback( + var_item_list, bad_ble_scene_config_select_callback, bad_ble); + draw_menu(bad_ble); + variable_item_list_set_selected_item(var_item_list, 0); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig); +} + +bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == ConfigIndexKeyboardLayout) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout); + } else if(event.event == ConfigIndexBleUnpair) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfirmUnpair); + } else { + furi_crash("Unknown key type"); + } + } + + return consumed; +} + +void bad_ble_scene_config_on_exit(void* context) { + BadBleApp* bad_ble = context; + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.h b/applications/system/bad_ble/scenes/bad_ble_scene_config.h new file mode 100644 index 0000000000..5675fca59b --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config.h @@ -0,0 +1,7 @@ +ADD_SCENE(bad_ble, file_select, FileSelect) +ADD_SCENE(bad_ble, work, Work) +ADD_SCENE(bad_ble, error, Error) +ADD_SCENE(bad_ble, config, Config) +ADD_SCENE(bad_ble, config_layout, ConfigLayout) +ADD_SCENE(bad_ble, confirm_unpair, ConfirmUnpair) +ADD_SCENE(bad_ble, unpair_done, UnpairDone) diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c new file mode 100644 index 0000000000..594525dd7b --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c @@ -0,0 +1,49 @@ +#include "../bad_ble_app_i.h" +#include + +static bool bad_ble_layout_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_ble->keyboard_layout)) { + furi_string_set(predefined_path, bad_ble->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + browser_options.base_path = BAD_BLE_APP_PATH_LAYOUT_FOLDER; + browser_options.skip_assets = false; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_ble_scene_config_layout_on_enter(void* context) { + BadBleApp* bad_ble = context; + + if(bad_ble_layout_select(bad_ble)) { + scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneWork); + } else { + scene_manager_previous_scene(bad_ble->scene_manager); + } +} + +bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_config_layout_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c b/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c new file mode 100644 index 0000000000..63f1e92cf2 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c @@ -0,0 +1,53 @@ +#include "../bad_ble_app_i.h" + +void bad_ble_scene_confirm_unpair_widget_callback( + GuiButtonType type, + InputType input_type, + void* context) { + UNUSED(input_type); + SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type}; + bad_ble_scene_confirm_unpair_on_event(context, event); +} + +void bad_ble_scene_confirm_unpair_on_enter(void* context) { + BadBleApp* bad_ble = context; + Widget* widget = bad_ble->widget; + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Unpair", + bad_ble_scene_confirm_unpair_widget_callback, + context); + + widget_add_text_box_element( + widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget); +} + +bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + SceneManager* scene_manager = bad_ble->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone); + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(scene_manager); + } + } + + return consumed; +} + +void bad_ble_scene_confirm_unpair_on_exit(void* context) { + BadBleApp* bad_ble = context; + Widget* widget = bad_ble->widget; + + widget_reset(widget); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_error.c b/applications/system/bad_ble/scenes/bad_ble_scene_error.c new file mode 100644 index 0000000000..c9c2b12da2 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_error.c @@ -0,0 +1,65 @@ +#include "../bad_ble_app_i.h" + +typedef enum { + BadBleCustomEventErrorBack, +} BadBleCustomEvent; + +static void + bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + BadBleApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack); + } +} + +void bad_ble_scene_error_on_enter(void* context) { + BadBleApp* app = context; + + if(app->error == BadBleAppErrorNoFiles) { + widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); + widget_add_string_multiline_element( + app->widget, + 81, + 4, + AlignCenter, + AlignTop, + FontSecondary, + "No SD card or\napp data found.\nThis app will not\nwork without\nrequired files."); + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app); + } else if(app->error == BadBleAppErrorCloseRpc) { + widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); + widget_add_string_multiline_element( + app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nIs Active!"); + widget_add_string_multiline_element( + app->widget, + 3, + 30, + AlignLeft, + AlignTop, + FontSecondary, + "Disconnect from\nPC or phone to\nuse this function."); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWidget); +} + +bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBleCustomEventErrorBack) { + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } + } + return consumed; +} + +void bad_ble_scene_error_on_exit(void* context) { + BadBleApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c new file mode 100644 index 0000000000..2a182a874d --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c @@ -0,0 +1,46 @@ +#include "../bad_ble_app_i.h" +#include +#include + +static bool bad_ble_file_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_BLE_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options); + + return res; +} + +void bad_ble_scene_file_select_on_enter(void* context) { + BadBleApp* bad_ble = context; + + if(bad_ble->bad_ble_script) { + bad_ble_script_close(bad_ble->bad_ble_script); + bad_ble->bad_ble_script = NULL; + } + + if(bad_ble_file_select(bad_ble)) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork); + } else { + view_dispatcher_stop(bad_ble->view_dispatcher); + } +} + +bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_file_select_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c new file mode 100644 index 0000000000..4c1fe3366b --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c @@ -0,0 +1,37 @@ +#include "../bad_ble_app_i.h" + +static void bad_ble_scene_unpair_done_popup_callback(void* context) { + BadBleApp* bad_ble = context; + scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneConfig); +} + +void bad_ble_scene_unpair_done_on_enter(void* context) { + BadBleApp* bad_ble = context; + Popup* popup = bad_ble->popup; + + popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58); + popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom); + popup_set_callback(popup, bad_ble_scene_unpair_done_popup_callback); + popup_set_context(popup, bad_ble); + popup_set_timeout(popup, 1000); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewPopup); +} + +bool bad_ble_scene_unpair_done_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + UNUSED(bad_ble); + UNUSED(event); + + bool consumed = false; + + return consumed; +} + +void bad_ble_scene_unpair_done_on_exit(void* context) { + BadBleApp* bad_ble = context; + Popup* popup = bad_ble->popup; + + popup_reset(popup); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_work.c b/applications/system/bad_ble/scenes/bad_ble_scene_work.c new file mode 100644 index 0000000000..ff71edc3c2 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_work.c @@ -0,0 +1,65 @@ +#include "../helpers/ducky_script.h" +#include "../bad_ble_app_i.h" +#include "../views/bad_ble_view.h" +#include +#include "toolbox/path.h" + +void bad_ble_scene_work_button_callback(InputKey key, void* context) { + furi_assert(context); + BadBleApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, key); +} + +bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InputKeyLeft) { + if(bad_ble_view_is_idle_state(app->bad_ble_view)) { + bad_ble_script_close(app->bad_ble_script); + app->bad_ble_script = NULL; + + scene_manager_next_scene(app->scene_manager, BadBleSceneConfig); + } + consumed = true; + } else if(event.event == InputKeyOk) { + bad_ble_script_start_stop(app->bad_ble_script); + consumed = true; + } else if(event.event == InputKeyRight) { + bad_ble_script_pause_resume(app->bad_ble_script); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + } + return consumed; +} + +void bad_ble_scene_work_on_enter(void* context) { + BadBleApp* app = context; + + app->bad_ble_script = bad_ble_script_open(app->file_path, app->interface); + bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout); + + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + bad_ble_view_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name)); + furi_string_free(file_name); + + FuriString* layout; + layout = furi_string_alloc(); + path_extract_filename(app->keyboard_layout, layout, true); + bad_ble_view_set_layout(app->bad_ble_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + + bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + + bad_ble_view_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork); +} + +void bad_ble_scene_work_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/system/bad_ble/views/bad_ble_view.c b/applications/system/bad_ble/views/bad_ble_view.c new file mode 100644 index 0000000000..28f935733e --- /dev/null +++ b/applications/system/bad_ble/views/bad_ble_view.c @@ -0,0 +1,284 @@ +#include "bad_ble_view.h" +#include "../helpers/ducky_script.h" +#include +#include +#include +#include "bad_ble_icons.h" + +#define MAX_NAME_LEN 64 + +struct BadBle { + View* view; + BadBleButtonCallback callback; + void* context; +}; + +typedef struct { + char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; + BadBleState state; + bool pause_wait; + uint8_t anim_frame; +} BadBleModel; + +static void bad_ble_draw_callback(Canvas* canvas, void* _model) { + BadBleModel* model = _model; + + FuriString* disp_str; + disp_str = furi_string_alloc_set(model->file_name); + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); + + if(strlen(model->layout) == 0) { + furi_string_set(disp_str, "(default)"); + } else { + furi_string_printf(disp_str, "(%s)", model->layout); + } + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_draw_str( + canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); + + furi_string_reset(disp_str); + + canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22); + + BadBleWorkerState state = model->state.state; + + if((state == BadBleStateIdle) || (state == BadBleStateDone) || + (state == BadBleStateNotConnected)) { + elements_button_center(canvas, "Run"); + elements_button_left(canvas, "Config"); + } else if((state == BadBleStateRunning) || (state == BadBleStateDelay)) { + elements_button_center(canvas, "Stop"); + if(!model->pause_wait) { + elements_button_right(canvas, "Pause"); + } + } else if(state == BadBleStatePaused) { + elements_button_center(canvas, "End"); + elements_button_right(canvas, "Resume"); + } else if(state == BadBleStateWaitForBtn) { + elements_button_center(canvas, "Press to continue"); + } else if(state == BadBleStateWillRun) { + elements_button_center(canvas, "Cancel"); + } + + if(state == BadBleStateNotConnected) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device"); + } else if(state == BadBleStateWillRun) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); + } else if(state == BadBleStateFileError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); + } else if(state == BadBleStateScriptError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); + canvas_set_font(canvas, FontSecondary); + furi_string_printf(disp_str, "line %zu", model->state.error_line); + canvas_draw_str_aligned( + canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + + furi_string_set_str(disp_str, model->state.error); + elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); + canvas_draw_str_aligned( + canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else if(state == BadBleStateIdle) { + canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateRunning) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateDone) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateDelay) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); + canvas_draw_str_aligned( + canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else if((state == BadBleStatePaused) || (state == BadBleStateWaitForBtn)) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); + furi_string_reset(disp_str); + } else { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + } + + furi_string_free(disp_str); +} + +static bool bad_ble_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BadBle* bad_ble = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } else if(event->key == InputKeyOk) { + with_view_model( + bad_ble->view, BadBleModel * model, { model->pause_wait = false; }, true); + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } else if(event->key == InputKeyRight) { + with_view_model( + bad_ble->view, + BadBleModel * model, + { + if((model->state.state == BadBleStateRunning) || + (model->state.state == BadBleStateDelay)) { + model->pause_wait = true; + } + }, + true); + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } + } + + return consumed; +} + +BadBle* bad_ble_view_alloc(void) { + BadBle* bad_ble = malloc(sizeof(BadBle)); + + bad_ble->view = view_alloc(); + view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel)); + view_set_context(bad_ble->view, bad_ble); + view_set_draw_callback(bad_ble->view, bad_ble_draw_callback); + view_set_input_callback(bad_ble->view, bad_ble_input_callback); + + return bad_ble; +} + +void bad_ble_view_free(BadBle* bad_ble) { + furi_assert(bad_ble); + view_free(bad_ble->view); + free(bad_ble); +} + +View* bad_ble_view_get_view(BadBle* bad_ble) { + furi_assert(bad_ble); + return bad_ble->view; +} + +void bad_ble_view_set_button_callback( + BadBle* bad_ble, + BadBleButtonCallback callback, + void* context) { + furi_assert(bad_ble); + furi_assert(callback); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + UNUSED(model); + bad_ble->callback = callback; + bad_ble->context = context; + }, + true); +} + +void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name) { + furi_assert(name); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->file_name, name, MAX_NAME_LEN); }, + true); +} + +void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout) { + furi_assert(layout); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->layout, layout, MAX_NAME_LEN); }, + true); +} + +void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st) { + furi_assert(st); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + memcpy(&(model->state), st, sizeof(BadBleState)); + model->anim_frame ^= 1; + if(model->state.state == BadBleStatePaused) { + model->pause_wait = false; + } + }, + true); +} + +bool bad_ble_view_is_idle_state(BadBle* bad_ble) { + bool is_idle = false; + with_view_model( + bad_ble->view, + BadBleModel * model, + { + if((model->state.state == BadBleStateIdle) || + (model->state.state == BadBleStateDone) || + (model->state.state == BadBleStateNotConnected)) { + is_idle = true; + } + }, + false); + return is_idle; +} diff --git a/applications/system/bad_ble/views/bad_ble_view.h b/applications/system/bad_ble/views/bad_ble_view.h new file mode 100644 index 0000000000..e26488818e --- /dev/null +++ b/applications/system/bad_ble/views/bad_ble_view.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "../helpers/ducky_script.h" + +typedef struct BadBle BadBle; +typedef void (*BadBleButtonCallback)(InputKey key, void* context); + +BadBle* bad_ble_view_alloc(void); + +void bad_ble_view_free(BadBle* bad_ble); + +View* bad_ble_view_get_view(BadBle* bad_ble); + +void bad_ble_view_set_button_callback( + BadBle* bad_ble, + BadBleButtonCallback callback, + void* context); + +void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name); + +void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout); + +void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st); + +bool bad_ble_view_is_idle_state(BadBle* bad_ble); From 0aefb1c0f29db458f112361e0f488c825d172f93 Mon Sep 17 00:00:00 2001 From: Astra Date: Fri, 4 Oct 2024 22:12:32 +0900 Subject: [PATCH 100/236] Format images to 1-bit B/W --- .../system/bad_ble/assets/Bad_BLE_48x22.png | Bin 1097 -> 145 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/applications/system/bad_ble/assets/Bad_BLE_48x22.png b/applications/system/bad_ble/assets/Bad_BLE_48x22.png index 0421d33c7d33bc7a1fef807906805a624682c885..5f6fa6f4694972b23d9d0a219f404f16c18f6403 100644 GIT binary patch delta 128 zcmX@fF_CeCL?t5w5UlG_0?`qkE{-7;b9~Play1wTxLnLn625Vm;}+M>+2S8On$|8T zH%w4X-q^LWjh$IObGitFg2TtcsUg}&|72!%K0f(S%lG^oiF@jkJSU%K`abJrjO5(t eyPH?*CG3}bAT71M<*9c8$SzM;KbLh*2~7aukTTZ* literal 1097 zcmbVL&1=*^6pz#vDXxl#f(2zrMJ$+PGD&tfq1%sbx^|&k*X>%1c<5|0+lFmEnoQla z2f;!Qg(A|E7lneLAQbed2dg*FiU*JW13gF&UaXULw|Xc&7)aim$@{(E`&=(i-b{7( zc5@t;Dvp~KCLw!!&UCQvoBrMBOkAR4Gjz(Y(LP3qpIbM$6egA<6j-BK0Y9&|-M#;Q0z7mqD zmC35L(y(-!9~uM$vB4a;gkli83tnWzIsU+}!Sv)>;=zFlZRGgFpk_*CFzSZ{=%OrG z5F(J#MV!WIMNen(4S*n0C8$U+Ey%K=Ap>fl_3(@^wChI2EVSs@DaX%K8W@t)Y&ONF zBKqN+BT*D&3W@xs&|)pA_qD;Sd?ynnhAbN6R_L(4 z;04pSmR#b|qM75_AJKMfL$NcuW?@Z`HLM6~Izxo4g@~X-l~^(&&?0u*&vE#?BEtgA zs+xk3ERPlPrk>9ipsBH*Xy!<|jV*c+#hyjl+z#VzVU-iH#%M?|^~0*~FSG}tJnvIK zn)d^Mkf?#{C2Tofa?_u5=`d-Ngw6wE7ee0!2W2*#V?xLvOzP>nCfFLW1XYEqP_ye$ zsMQoCE3%SN)hyq_+y7fWi3KGkv+~dEw6EAeChE~Lu*13#Umhl+XpU6l@3q+-Ze!!@i&IE_@@nmL>cSe>92c%OhNmZ1FK1QwV-NNG zT)(Doy0+X4b~xuZ_j>E=-gid8 M6~`vc?fmTOAF2pg_W%F@ From 4f722a00c06c7eb4816840fbe4e10a857e8db3dc Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 4 Oct 2024 18:41:29 -0400 Subject: [PATCH 101/236] Avoid storage errors, clean up temporary files --- .../scenes/nfc_scene_mf_classic_dict_attack.c | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 4c0459e744..2691999b08 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -171,20 +171,25 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { do { instance->nfc_dict_context.enhanced_dict = true; - // TODO: Check for errors - storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); - storage_common_copy( - instance->storage, - NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, - NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) { + storage_common_remove( + instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH)) { + storage_common_copy( + instance->storage, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, + NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { state = DictAttackStateSystemDictInProgress; break; } - // TODO: Check for errors - storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + } storage_common_copy( instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_PATH, @@ -376,6 +381,14 @@ void nfc_scene_mf_classic_dict_attack_on_exit(void* context) { instance->nfc_dict_context.msb_count = 0; instance->nfc_dict_context.enhanced_dict = false; + // Clean up temporary files used for nested dictionary attack + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_USER_NESTED_PATH); + } + if(keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH)) { + storage_common_remove(instance->storage, NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH); + } + nfc_blink_stop(instance); notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); } From 83b0e46ccda9b8e3eeebfd269a41dc3dea808b7f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Oct 2024 04:46:43 +0300 Subject: [PATCH 102/236] merge ofw PR 3933 furi_hal_random: Wait for ready state and no errors before sampling by n1kolasM https://github.com/flipperdevices/flipperzero-firmware/pull/3933/files --- targets/f7/furi_hal/furi_hal_random.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f7/furi_hal/furi_hal_random.c b/targets/f7/furi_hal/furi_hal_random.c index 8b75a05c5b..6269a90e1b 100644 --- a/targets/f7/furi_hal/furi_hal_random.c +++ b/targets/f7/furi_hal/furi_hal_random.c @@ -11,7 +11,7 @@ #define TAG "FuriHalRandom" static uint32_t furi_hal_random_read_rng(void) { - while(LL_RNG_IsActiveFlag_CECS(RNG) && LL_RNG_IsActiveFlag_SECS(RNG) && + while(LL_RNG_IsActiveFlag_CECS(RNG) || LL_RNG_IsActiveFlag_SECS(RNG) || !LL_RNG_IsActiveFlag_DRDY(RNG)) { /* Error handling as described in RM0434, pg. 582-583 */ if(LL_RNG_IsActiveFlag_CECS(RNG)) { From 54ad331c4c1a2c96259cb4e079ba9dbab0ef1cc5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Oct 2024 07:04:21 +0300 Subject: [PATCH 103/236] rename extract mf keys to extract MFC keys because its used only for mifare classic, not for plus etc.. --- applications/main/nfc/scenes/nfc_scene_start.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index b981b719a9..b6e8f82985 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -30,7 +30,7 @@ void nfc_scene_start_on_enter(void* context) { submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc); submenu_add_item( submenu, - "Extract MF Keys", + "Extract MFC Keys", SubmenuIndexDetectReader, nfc_scene_start_submenu_callback, nfc); From 1ceb1eb256985e95a0c61d740f9c8677ea175e71 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Oct 2024 08:43:42 +0300 Subject: [PATCH 104/236] Allow to disable control of GPIO pin on rx/tx states in subghz [ci skip] in radio settings with debug ON, settings is saved on microsd, please don't use unless you know what you are doing --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 53 +++++++++++-------- .../scenes/subghz_scene_radio_settings.c | 36 +++++++++++-- applications/main/subghz/subghz.c | 4 ++ .../main/subghz/subghz_dangerous_freq.c | 3 ++ .../main/subghz/subghz_last_settings.c | 13 +++++ .../main/subghz/subghz_last_settings.h | 1 + lib/subghz/devices/devices.c | 2 +- lib/subghz/devices/types.h | 2 +- targets/f7/api_symbols.csv | 2 + targets/f7/furi_hal/furi_hal_subghz.c | 10 ++++ targets/f7/furi_hal/furi_hal_subghz.h | 5 +- 11 files changed, 101 insertions(+), 30 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index ac72ab5c1a..868f606167 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -92,7 +92,7 @@ typedef struct { const GpioPin* g0_pin; SubGhzDeviceCC1101ExtAsyncTx async_tx; SubGhzDeviceCC1101ExtAsyncRx async_rx; - bool power_amp; + bool amp_and_leds; bool extended_range; } SubGhzDeviceCC1101Ext; @@ -219,11 +219,11 @@ bool subghz_device_cc1101_ext_alloc(SubGhzDeviceConf* conf) { subghz_device_cc1101_ext->async_mirror_pin = NULL; subghz_device_cc1101_ext->spi_bus_handle = &furi_hal_spi_bus_handle_external; subghz_device_cc1101_ext->g0_pin = SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO; - subghz_device_cc1101_ext->power_amp = false; + subghz_device_cc1101_ext->amp_and_leds = false; subghz_device_cc1101_ext->extended_range = false; if(conf) { if(conf->ver == SUBGHZ_DEVICE_CC1101_CONFIG_VER) { - subghz_device_cc1101_ext->power_amp = conf->power_amp; + subghz_device_cc1101_ext->amp_and_leds = conf->amp_and_leds; subghz_device_cc1101_ext->extended_range = conf->extended_range; } else { FURI_LOG_E(TAG, "Config version mismatch"); @@ -233,7 +233,7 @@ bool subghz_device_cc1101_ext_alloc(SubGhzDeviceConf* conf) { subghz_device_cc1101_ext->async_rx.capture_delta_duration = 0; furi_hal_spi_bus_handle_init(subghz_device_cc1101_ext->spi_bus_handle); - if(subghz_device_cc1101_ext->power_amp) { + if(subghz_device_cc1101_ext->amp_and_leds) { furi_hal_gpio_init_simple(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, GpioModeOutputPushPull); } @@ -244,7 +244,7 @@ void subghz_device_cc1101_ext_free(void) { furi_assert(subghz_device_cc1101_ext != NULL); furi_hal_spi_bus_handle_deinit(subghz_device_cc1101_ext->spi_bus_handle); - if(subghz_device_cc1101_ext->power_amp) { + if(subghz_device_cc1101_ext->amp_and_leds) { furi_hal_gpio_init_simple(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, GpioModeAnalog); } @@ -418,9 +418,11 @@ void subghz_device_cc1101_ext_reset(void) { // Warning: push pull cc1101 clock output on GD0 cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); - // Reset GDO2 (!TX/RX) to floating state - cc1101_write_reg( - subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); + if(subghz_device_cc1101_ext->amp_and_leds) { + // Reset GDO2 (!TX/RX) to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); + } furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -430,13 +432,15 @@ void subghz_device_cc1101_ext_idle(void) { //waiting for the chip to switch to IDLE mode furi_check(cc1101_wait_status_state( subghz_device_cc1101_ext->spi_bus_handle, CC1101StateIDLE, 10000)); - // Reset GDO2 (!TX/RX) to floating state - cc1101_write_reg( - subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); - furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); - if(subghz_device_cc1101_ext->power_amp) { + + if(subghz_device_cc1101_ext->amp_and_leds) { furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 0); + // Reset GDO2 (!TX/RX) to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); } + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } void subghz_device_cc1101_ext_rx(void) { @@ -445,14 +449,17 @@ void subghz_device_cc1101_ext_rx(void) { //waiting for the chip to switch to Rx mode furi_check( cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateRX, 10000)); - // Go GDO2 (!TX/RX) to high (RX state) - cc1101_write_reg( - subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); - furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); - if(subghz_device_cc1101_ext->power_amp) { + if(subghz_device_cc1101_ext->amp_and_leds) { furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 0); + // Go GDO2 (!TX/RX) to high (RX state) + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, + CC1101_IOCFG2, + CC1101IocfgHW | CC1101_IOCFG_INV); } + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } bool subghz_device_cc1101_ext_tx(void) { @@ -462,12 +469,14 @@ bool subghz_device_cc1101_ext_tx(void) { //waiting for the chip to switch to Tx mode furi_check( cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateTX, 10000)); - // Go GDO2 (!TX/RX) to low (TX state) - cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); - furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); - if(subghz_device_cc1101_ext->power_amp) { + + if(subghz_device_cc1101_ext->amp_and_leds) { furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 1); + // Go GDO2 (!TX/RX) to low (TX state) + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); } + + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); return true; } diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index ac1675d01b..974a2f5649 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -14,8 +14,8 @@ const uint32_t radio_device_value[RADIO_DEVICE_COUNT] = { SubGhzRadioDeviceTypeExternalCC1101, }; -#define TIMESTAMP_NAMES_COUNT 2 -const char* const timestamp_names_text[TIMESTAMP_NAMES_COUNT] = { +#define ON_OFF_COUNT 2 +const char* const on_off_text[ON_OFF_COUNT] = { "OFF", "ON", }; @@ -81,6 +81,22 @@ static void subghz_scene_receiver_config_set_debug_pin(VariableItem* item) { subghz_txrx_set_debug_pin_state(subghz->txrx, index == 1); } +static void subghz_scene_reciever_config_set_ext_amp_leds_control(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, on_off_text[index]); + subghz->last_settings->leds_and_amp = index == 1; + // Set globally in furi hal + furi_hal_subghz_set_ext_leds_and_amp(subghz->last_settings->leds_and_amp); + subghz_last_settings_save(subghz->last_settings); + // reinit external device + const SubGhzRadioDeviceType current = subghz_txrx_radio_device_get(subghz->txrx); + if(current != SubGhzRadioDeviceTypeInternal) { + subghz_txrx_radio_device_set(subghz->txrx, SubGhzRadioDeviceTypeInternal); + subghz_txrx_radio_device_set(subghz->txrx, current); + } +} + static void subghz_scene_receiver_config_set_debug_counter(VariableItem* item) { uint8_t index = variable_item_get_current_value_index(item); @@ -92,7 +108,7 @@ static void subghz_scene_receiver_config_set_timestamp_file_names(VariableItem* SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text(item, timestamp_names_text[index]); + variable_item_set_current_value_text(item, on_off_text[index]); subghz->last_settings->protocol_file_names = (index == 1); subghz_last_settings_save(subghz->last_settings); @@ -123,12 +139,12 @@ void subghz_scene_radio_settings_on_enter(void* context) { item = variable_item_list_add( variable_item_list, "Protocol Names", - TIMESTAMP_NAMES_COUNT, + ON_OFF_COUNT, subghz_scene_receiver_config_set_timestamp_file_names, subghz); value_index = subghz->last_settings->protocol_file_names; variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, timestamp_names_text[value_index]); + variable_item_set_current_value_text(item, on_off_text[value_index]); item = variable_item_list_add( variable_item_list, @@ -146,6 +162,16 @@ void subghz_scene_radio_settings_on_enter(void* context) { variable_item_set_current_value_text(item, debug_counter_text[value_index]); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + item = variable_item_list_add( + variable_item_list, + "Ext Amp & LEDs", + ON_OFF_COUNT, + subghz_scene_reciever_config_set_ext_amp_leds_control, + subghz); + value_index = subghz->last_settings->leds_and_amp ? 1 : 0; + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, on_off_text[value_index]); + item = variable_item_list_add( variable_item_list, "Debug Pin", diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 30480054ec..75b3180053 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -197,6 +197,10 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { subghz->last_settings = subghz_last_settings_alloc(); size_t preset_count = subghz_setting_get_preset_count(setting); subghz_last_settings_load(subghz->last_settings, preset_count); + + // Set LED and Amp GPIO control state + furi_hal_subghz_set_ext_leds_and_amp(subghz->last_settings->leds_and_amp); + if(!alloc_for_tx_only) { subghz_txrx_set_preset_internal( subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index); diff --git a/applications/main/subghz/subghz_dangerous_freq.c b/applications/main/subghz/subghz_dangerous_freq.c index 699c42754b..b76a6ec05e 100644 --- a/applications/main/subghz/subghz_dangerous_freq.c +++ b/applications/main/subghz/subghz_dangerous_freq.c @@ -27,6 +27,9 @@ void subghz_dangerous_freq() { SubGhzLastSettings* last_settings = subghz_last_settings_alloc(); subghz_last_settings_load(last_settings, 0); + // Set LED and Amp GPIO control state + furi_hal_subghz_set_ext_leds_and_amp(last_settings->leds_and_amp); + subghz_last_settings_free(last_settings); furi_record_close(RECORD_STORAGE); diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 12e871a2f9..174a15a5bd 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -18,6 +18,7 @@ #define SUBGHZ_LAST_SETTING_FIELD_RSSI_THRESHOLD "RSSI" #define SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD "DelOldSignals" #define SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD "HoppingThreshold" +#define SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP "LedAndPowerAmp" SubGhzLastSettings* subghz_last_settings_alloc(void) { SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings)); @@ -42,6 +43,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count instance->filter = SubGhzProtocolFlag_Decodable; instance->rssi = SUBGHZ_RAW_THRESHOLD_MIN; instance->hopping_threshold = -90.0f; + instance->leds_and_amp = true; Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); @@ -123,6 +125,13 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count 1)) { flipper_format_rewind(fff_data_file); } + if(!flipper_format_read_bool( + fff_data_file, + SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP, + &instance->leds_and_amp, + 1)) { + flipper_format_rewind(fff_data_file); + } } while(0); } else { @@ -219,6 +228,10 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { 1)) { break; } + if(!flipper_format_write_bool( + file, SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP, &instance->leds_and_amp, 1)) { + break; + } saved = true; } while(0); diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index 30c5884e55..c0559e4ec8 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -25,6 +25,7 @@ typedef struct { float rssi; bool delete_old_signals; float hopping_threshold; + bool leds_and_amp; } SubGhzLastSettings; SubGhzLastSettings* subghz_last_settings_alloc(void); diff --git a/lib/subghz/devices/devices.c b/lib/subghz/devices/devices.c index 9ae2520172..0999531363 100644 --- a/lib/subghz/devices/devices.c +++ b/lib/subghz/devices/devices.c @@ -33,7 +33,7 @@ bool subghz_devices_begin(const SubGhzDevice* device) { SubGhzDeviceConf conf = { .ver = 1, .extended_range = false, // TODO - .power_amp = true, + .amp_and_leds = furi_hal_subghz_get_ext_leds_and_amp(), }; ret = device->interconnect->begin(&conf); diff --git a/lib/subghz/devices/types.h b/lib/subghz/devices/types.h index 6de2d9bd35..14852a1603 100644 --- a/lib/subghz/devices/types.h +++ b/lib/subghz/devices/types.h @@ -94,5 +94,5 @@ struct SubGhzDevice { struct SubGhzDeviceConf { uint8_t ver; bool extended_range; - bool power_amp; + bool amp_and_leds; }; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index cf8e69d265..781da2bb3b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1690,6 +1690,7 @@ Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, Function,+,furi_hal_subghz_get_data_gpio,const GpioPin*, +Function,+,furi_hal_subghz_get_ext_leds_and_amp,_Bool, Function,+,furi_hal_subghz_get_lqi,uint8_t, Function,+,furi_hal_subghz_get_rolling_counter_mult,int8_t, Function,+,furi_hal_subghz_get_rssi,float, @@ -1707,6 +1708,7 @@ Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* +Function,+,furi_hal_subghz_set_ext_leds_and_amp,void,_Bool Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index c2efe1fd38..19aa0f7668 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -52,6 +52,7 @@ typedef struct { const GpioPin* async_mirror_pin; int8_t rolling_counter_mult; + bool ext_leds_and_amp : 1; bool dangerous_frequency_i : 1; } FuriHalSubGhz; @@ -60,6 +61,7 @@ volatile FuriHalSubGhz furi_hal_subghz = { .regulation = SubGhzRegulationTxRx, .async_mirror_pin = NULL, .rolling_counter_mult = 1, + .ext_leds_and_amp = true, .dangerous_frequency_i = false, }; @@ -79,6 +81,14 @@ void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { furi_hal_subghz.async_mirror_pin = pin; } +void furi_hal_subghz_set_ext_leds_and_amp(bool enabled) { + furi_hal_subghz.ext_leds_and_amp = enabled; +} + +bool furi_hal_subghz_get_ext_leds_and_amp(void) { + return furi_hal_subghz.ext_leds_and_amp; +} + const GpioPin* furi_hal_subghz_get_data_gpio(void) { return &gpio_cc1101_g0; } diff --git a/targets/f7/furi_hal/furi_hal_subghz.h b/targets/f7/furi_hal/furi_hal_subghz.h index e8e95d9f3b..20843520a5 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.h +++ b/targets/f7/furi_hal/furi_hal_subghz.h @@ -238,7 +238,10 @@ bool furi_hal_subghz_is_async_tx_complete(void); */ void furi_hal_subghz_stop_async_tx(void); -// External CC1101 Ebytes power amplifier control is now enabled by default +// External CC1101 Amplifier and LEDs (if present) control +void furi_hal_subghz_set_ext_leds_and_amp(bool enabled); +// Get state (enabled by default, can be disabled in radio setting with debug ON) +bool furi_hal_subghz_get_ext_leds_and_amp(void); #ifdef __cplusplus } From ae14e8d3f0d4618e734262710681a57176387906 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:32:24 +0300 Subject: [PATCH 105/236] upd changelog --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5073cc14a..88fb7680e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,23 @@ - Princeton support for second button encoding type (8bit) - GangQi fix serial check - Hollarm add more button codes (thanks to @mishamyte for captures) + - Misc: + - Add extra settings to disable GPIO pins control used for external modules amplifiers and/or LEDs (in radio settings menu with debug ON) +- NFC: + - OFW PR 3822: MIFARE Classic Key Recovery Improvements (by @noproto) + - OFW PR 3918: NFC Some api adjustments (by @RebornedBrain) + - OFW PR 3930: NFC Emulation freeze (by @RebornedBrain) + - OFW PR 3885: Add API to enforce ISO15693 mode (by @aaronjamt) +* OFW: New layout for BadUSB (es-LA) * Infrared: Update universal remote assets (by @amec0e | PR #813) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* OFW PR 3885: Add API to enforce ISO15693 mode (by @aaronjamt) +* OFW PR 3931: Split BadUSB into BadUSB and BadBLE (by @Astrrra) +* OFW PR 3933: furi_hal_random: Wait for ready state and no errors before sampling (by @n1kolasM) +* OFW: Improve bit_buffer.h docs +* OFW: Prevent idle priority threads from potentially starving the FreeRTOS idle task +* OFW: IR universal remote additions +* OFW: Fix EM4100 T5577 writing block order (was already done in UL) * OFW: kerel typo * OFW: Folder rename fails * OFW: Put errno into TCB From 09d6e4ee300b54c3ced4d1f2a44dc99ea02f3aad Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Oct 2024 12:16:37 +0300 Subject: [PATCH 106/236] fix idle states --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 868f606167..27af3e5572 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -418,11 +418,10 @@ void subghz_device_cc1101_ext_reset(void) { // Warning: push pull cc1101 clock output on GD0 cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); - if(subghz_device_cc1101_ext->amp_and_leds) { - // Reset GDO2 (!TX/RX) to floating state - cc1101_write_reg( - subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); - } + // Reset GDO2 (!TX/RX) to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -433,12 +432,10 @@ void subghz_device_cc1101_ext_idle(void) { furi_check(cc1101_wait_status_state( subghz_device_cc1101_ext->spi_bus_handle, CC1101StateIDLE, 10000)); - if(subghz_device_cc1101_ext->amp_and_leds) { - furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 0); - // Reset GDO2 (!TX/RX) to floating state - cc1101_write_reg( - subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); - } + furi_hal_gpio_write(SUBGHZ_DEVICE_CC1101_EXT_E07_AMP_GPIO, 0); + // Reset GDO2 (!TX/RX) to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } From 2e241f56eb734a2300e49226ff207680009d7c82 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:55:09 +0300 Subject: [PATCH 107/236] rename everywhere --- .../main/nfc/helpers/protocol_support/mf_classic/mf_classic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 4fece16be5..5f3592c4b6 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -117,7 +117,7 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, - "Extract MF Keys", + "Extract MFC Keys", SubmenuIndexDetectReader, nfc_protocol_support_common_submenu_callback, instance); @@ -155,7 +155,7 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, - "Extract MF Keys", + "Extract MFC Keys", SubmenuIndexDetectReader, nfc_protocol_support_common_submenu_callback, instance); From 5c99477c1e103ba5a52bbae160355ff4bbe18c3c Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:25:31 +0100 Subject: [PATCH 108/236] Update tv.ir New additions --- .../infrared/resources/infrared/assets/tv.ir | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index a26bf4b416..46fe152927 100755 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 27th Sept, 2024 -# Last Checked 27th Sept, 2024 +# Last Updated 5th Oct, 2024 +# Last Checked 5th Oct, 2024 # name: Power type: parsed @@ -3677,3 +3677,21 @@ type: parsed protocol: NECext address: 00 7F 00 00 command: 5B A4 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 18 00 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 19 E6 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 33 CC 00 00 From b4c230f955701ec6239eba22e513d9e056712d0c Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:25:58 +0100 Subject: [PATCH 109/236] Update projectors.ir New addition --- .../infrared/resources/infrared/assets/projectors.ir | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/projectors.ir b/applications/main/infrared/resources/infrared/assets/projectors.ir index a1814e1a7f..e1cc36b8ec 100644 --- a/applications/main/infrared/resources/infrared/assets/projectors.ir +++ b/applications/main/infrared/resources/infrared/assets/projectors.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 27th Sept, 2024 -# Last Checked 27th Sept, 2024 +# Last Updated 5th Oct, 2024 +# Last Checked 5th Oct, 2024 # # TEMP FIX FOR POWER # @@ -1813,3 +1813,9 @@ type: parsed protocol: NECext address: 86 6B 00 00 command: 09 F6 00 00 +# +name: Vol_down +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 0B F4 00 00 From 74c8a2b7b68cde00e7b210babf6d8783a9bdb79e Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:26:23 +0100 Subject: [PATCH 110/236] Update fans.ir New addition --- .../main/infrared/resources/infrared/assets/fans.ir | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/fans.ir b/applications/main/infrared/resources/infrared/assets/fans.ir index 1dfe43496a..28e5fee1b8 100644 --- a/applications/main/infrared/resources/infrared/assets/fans.ir +++ b/applications/main/infrared/resources/infrared/assets/fans.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -#Last Updated 27th Sept, 2024 -#Last Checked 27th Sept, 2024 +# Last Updated 5th Oct, 2024 +# Last Checked 5th Oct, 2024 # name: Power type: raw @@ -2523,3 +2523,9 @@ type: parsed protocol: NEC address: 80 00 00 00 command: 11 00 00 00 +# +name: Rotate +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4608 4604 546 1509 540 1516 544 1512 548 1509 540 1517 543 1513 547 2555 545 1511 539 2564 546 1510 540 2562 538 1518 542 2559 541 1516 544 1512 548 1509 541 1514 4605 4605 545 1510 540 1517 543 1514 546 1510 540 1517 543 1514 546 2556 544 1512 548 2555 545 1510 540 2563 547 1508 542 2587 513 1517 543 1513 547 1510 540 13755 9237 2298 545 From 58bac2571b8ce888aa526c520e4516f9b3341903 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:26:46 +0100 Subject: [PATCH 111/236] Update audio.ir New addition --- .../resources/infrared/assets/audio.ir | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index 65c33ea867..76a3b80c78 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 -# Last Updated 27th Sept, 2024 -# Last Checked 27th Sept, 2024 +# Last Updated 5th Oct, 2024 +# Last Checked 5th Oct, 2024 # name: Power type: parsed @@ -4172,37 +4172,37 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4637 4376 612 419 584 420 584 420 583 421 582 1427 531 1477 531 472 532 472 557 1452 556 1451 557 1451 557 1452 556 447 557 448 556 449 555 449 555 4453 554 450 554 450 554 451 553 450 554 451 553 451 553 451 553 450 554 1455 553 1454 554 1454 554 451 553 1454 554 1454 554 1455 553 1454 554 451 553 450 554 450 554 1455 553 55439 4554 4458 555 449 555 449 555 450 554 450 554 1455 553 1454 554 451 553 450 554 1454 554 1454 554 1454 554 1455 553 450 554 451 553 451 553 451 553 4453 554 451 553 451 552 451 553 451 553 451 553 451 553 451 553 451 553 1455 553 1455 553 1455 553 451 553 1455 553 1455 553 1455 553 1455 553 451 553 451 552 451 553 1455 553 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4636 4380 612 392 612 394 610 394 610 419 583 1399 557 1452 556 473 556 448 556 1428 581 1453 555 1453 555 1453 555 449 555 450 554 451 553 452 552 4457 551 452 552 452 552 452 552 452 552 452 552 1457 551 452 552 1457 551 452 552 452 552 453 551 1457 552 1457 551 452 552 1457 551 452 552 1457 551 1457 551 1457 552 452 552 55450 4551 4461 553 451 553 452 552 452 552 452 552 1456 552 1456 552 452 552 452 552 1456 552 1456 552 1456 552 1456 552 452 552 452 552 453 551 453 551 4456 551 453 551 453 551 453 551 453 551 453 551 1457 551 453 551 1457 552 453 551 454 550 454 550 1457 552 1457 551 454 550 1457 551 454 550 1458 551 1457 551 1458 550 454 550 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4640 4405 583 420 583 421 582 421 583 422 581 1427 531 1478 530 473 531 472 557 1452 557 1452 556 1452 556 1452 556 448 556 448 556 449 555 450 554 4454 554 451 553 451 553 451 553 451 553 1455 554 1455 553 1455 553 451 553 1455 553 1456 553 1456 553 451 553 451 553 451 553 451 554 1455 554 451 553 452 553 451 553 1456 553 55447 4556 4458 555 449 555 450 554 450 554 450 554 1455 553 1455 553 451 553 451 553 1455 553 1455 553 1455 553 1455 553 451 553 451 553 451 553 451 553 4454 553 451 553 450 554 451 553 450 554 1455 553 1455 553 1455 553 451 553 1455 553 1455 553 1455 553 451 553 451 553 451 553 450 554 1455 553 451 553 451 553 451 553 1455 553 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4636 4378 613 393 611 392 612 393 611 393 557 1451 558 1450 610 420 583 421 557 1427 581 1452 556 1452 556 1452 556 448 555 449 555 450 554 450 554 4455 553 451 553 451 553 451 553 451 553 451 553 451 553 452 552 1456 553 1456 552 1456 553 1456 552 451 553 1456 553 1456 552 1456 553 451 553 451 553 452 552 451 553 1456 552 55452 4553 4461 553 450 554 451 553 451 553 451 553 1456 553 1456 552 451 553 451 553 1456 552 1455 553 1455 553 1455 553 451 553 451 553 451 553 451 553 4456 552 451 553 451 553 451 553 451 553 451 553 451 553 451 553 1456 552 1455 553 1455 553 1455 553 451 553 1455 553 1456 552 1456 552 451 553 451 553 451 553 451 553 1456 552 -# +# name: Prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 255 113623 4638 4378 613 391 612 392 559 446 558 446 558 1477 531 1477 532 472 532 472 532 1476 532 1476 532 1476 532 1477 531 473 555 449 555 449 555 450 554 4455 554 450 554 450 554 450 554 450 554 1455 554 1455 554 450 554 1455 554 450 555 450 554 450 554 1455 554 451 553 451 553 1455 554 450 554 1455 554 1456 553 1455 554 450 554 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 4557 4430 611 392 610 394 559 445 559 446 558 1451 558 1477 531 448 556 472 532 1476 532 1477 532 1477 531 1477 531 473 556 449 555 449 555 450 554 4454 554 450 554 450 554 450 554 450 555 450 554 450 554 1455 554 1455 554 450 554 450 554 450 554 1455 554 1455 554 1455 553 451 553 450 554 1455 554 1455 554 1455 554 450 554 55458 4555 4459 554 450 554 450 554 450 554 450 554 1455 553 1455 553 450 554 450 554 1455 553 1455 553 1455 553 1455 553 450 554 450 554 450 554 450 554 4454 554 450 554 450 554 450 554 451 553 450 554 450 554 1455 553 1455 553 451 553 450 554 450 554 1455 553 1455 553 1455 553 450 554 451 553 1455 554 1455 553 1455 553 450 554 -# +# name: Mute type: raw frequency: 38000 @@ -5102,3 +5102,9 @@ type: parsed protocol: NECext address: 78 0E 00 00 command: 02 FD 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 From 6814fe8fb3ae47a60141852312fbe7b1c025ef97 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:38:33 +0100 Subject: [PATCH 112/236] Update tv.ir Removed additional whitespaces --- .../infrared/resources/infrared/assets/tv.ir | 596 +++++++++--------- 1 file changed, 298 insertions(+), 298 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 46fe152927..ea378b78b8 100755 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -38,31 +38,31 @@ type: parsed protocol: SIRC address: 01 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 40 00 00 00 command: 0B 00 00 00 -# +# name: Ch_next type: parsed protocol: NEC address: 40 00 00 00 command: 11 00 00 00 -# +# name: Ch_prev type: parsed protocol: NEC address: 40 00 00 00 command: 10 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 40 00 00 00 command: 13 00 00 00 -# +# name: Mute type: parsed protocol: NEC @@ -80,7 +80,7 @@ type: parsed protocol: Kaseikyo address: 80 02 20 00 command: 00 02 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo @@ -92,13 +92,13 @@ type: parsed protocol: Kaseikyo address: 80 02 20 00 command: 20 03 00 00 -# +# name: Ch_next type: parsed protocol: Kaseikyo address: 80 02 20 00 command: 40 03 00 00 -# +# name: Ch_prev type: parsed protocol: Kaseikyo @@ -110,43 +110,43 @@ type: parsed protocol: NEC address: 04 00 00 00 command: 40 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 04 00 00 00 command: 12 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 7F 00 00 command: 15 EA 00 00 -# +# name: Mute type: parsed protocol: NECext address: 00 7F 00 00 command: 16 E9 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 00 7F 00 00 command: 1B E4 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 00 7F 00 00 command: 1A E5 00 00 -# +# name: Ch_next type: parsed protocol: NECext address: 00 7F 00 00 command: 19 E6 00 00 -# +# name: Ch_prev type: parsed protocol: NECext @@ -447,7 +447,7 @@ type: parsed protocol: Samsung32 address: 0E 00 00 00 command: 15 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC @@ -1143,17 +1143,17 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 0D 00 00 00 -# +# # Samsung Standby -# +# name: Power type: parsed protocol: Samsung32 address: 07 00 00 00 command: E0 00 00 00 -# +# # Samsung Power Off -# +# name: Power type: parsed protocol: Samsung32 @@ -1789,31 +1789,31 @@ type: parsed protocol: NEC address: 00 00 00 00 command: 33 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 28 00 00 00 command: 0B 00 00 00 -# +# name: Ch_next type: parsed protocol: NEC address: 28 00 00 00 command: 0C 00 00 00 -# +# name: Ch_prev type: parsed protocol: NEC address: 28 00 00 00 command: 0D 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 28 00 00 00 command: 0E 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC @@ -1825,79 +1825,79 @@ type: parsed protocol: NEC address: 28 00 00 00 command: 10 00 00 00 -# +# name: Ch_next type: parsed protocol: RC5 address: 01 00 00 00 command: 14 00 00 00 -# +# name: Ch_next type: parsed protocol: SIRC20 address: 5A 0E 00 00 command: 10 00 00 00 -# +# name: Ch_prev type: parsed protocol: SIRC20 address: 5A 0E 00 00 command: 11 00 00 00 -# +# name: Vol_up type: parsed protocol: RC5 address: 00 00 00 00 command: 15 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC5 address: 00 00 00 00 command: 14 00 00 00 -# +# name: Ch_next type: parsed protocol: RC5 address: 00 00 00 00 command: 18 00 00 00 -# +# name: Ch_prev type: parsed protocol: RC5 address: 00 00 00 00 command: 17 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 80 00 00 00 command: 12 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 80 00 00 00 command: 1A 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 80 00 00 00 command: 1E 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 80 00 00 00 command: 10 00 00 00 -# +# name: Ch_next type: parsed protocol: SIRC20 address: 10 01 00 00 command: 34 00 00 00 -# +# name: Ch_prev type: parsed protocol: SIRC20 @@ -1905,37 +1905,37 @@ address: 10 01 00 00 command: 33 00 00 00 # # Sharp TV -# +# name: Power type: parsed protocol: NECext address: 00 BD 00 00 command: 01 FE 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 00 BD 00 00 command: 0C F3 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 00 BD 00 00 command: 10 EF 00 00 -# +# name: Ch_next type: parsed protocol: NECext address: 00 BD 00 00 command: 18 E7 00 00 -# +# name: Ch_prev type: parsed protocol: NECext address: 00 BD 00 00 command: 1C E3 00 00 -# +# name: Mute type: parsed protocol: NECext @@ -1947,105 +1947,105 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 278 1811 277 788 246 794 250 764 280 786 248 792 252 1813 275 1815 273 791 253 1812 276 789 255 785 249 791 253 1812 276 789 255 45322 280 1809 279 786 248 766 278 788 246 794 250 1815 273 792 252 788 246 1819 280 785 249 1817 271 1819 280 1810 278 787 247 1818 281 43217 274 1818 270 794 250 764 280 786 248 792 252 788 256 1809 279 1811 277 788 246 1819 280 785 249 766 278 762 272 1819 280 785 248 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 278 1812 276 762 282 758 276 765 279 761 273 1818 281 1809 279 1811 277 762 282 1809 279 760 274 766 278 762 282 1809 279 760 274 44279 276 1813 275 763 281 759 275 766 278 762 272 768 276 764 280 760 274 1817 271 768 276 1815 273 1817 271 1819 280 759 275 1816 272 44276 279 1812 276 763 281 758 276 765 279 761 273 1818 281 1810 278 1811 277 762 272 1819 279 760 274 766 278 762 282 1809 279 760 274 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 272 1817 271 794 250 790 254 786 248 792 252 762 272 794 250 1815 273 792 252 1813 275 790 254 785 249 766 278 1813 275 789 255 46372 273 1817 271 794 250 763 281 785 248 792 252 1813 275 1814 274 791 253 1812 276 789 255 1810 278 1812 276 1813 275 790 254 1811 277 42170 277 1814 274 791 253 787 247 793 251 763 281 759 275 791 253 1812 276 789 255 1810 278 787 247 793 251 789 255 1810 278 787 247 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 275 1814 274 791 253 787 247 793 251 789 255 1810 278 787 247 1818 281 785 249 1816 272 793 251 789 255 785 249 1816 272 766 278 45325 274 1815 273 792 252 762 272 794 250 790 254 786 247 1818 270 794 250 1815 273 792 252 1813 275 1815 273 1816 272 793 251 1814 274 43224 277 1814 274 764 280 786 248 792 252 788 246 1820 279 786 247 1817 271 768 276 1815 273 792 252 761 273 794 250 1815 273 791 253 -# +# name: Ch_next type: raw frequency: 38000 duty_cycle: 0.330000 data: 272 1817 271 794 250 790 254 786 248 792 252 1813 275 790 254 759 275 792 252 1813 275 789 255 785 248 792 252 1813 275 789 255 46372 273 1817 271 793 251 763 281 759 275 791 253 787 247 1818 281 1810 278 1812 276 789 255 1809 279 1811 277 1813 275 790 254 1811 277 42169 277 1815 273 792 252 787 247 794 250 789 255 1810 278 787 247 794 250 789 255 1810 278 761 273 793 251 789 255 1810 278 787 247 -# +# name: Ch_prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 273 1816 272 767 277 789 255 785 249 791 253 787 246 1818 281 785 248 765 279 1812 276 789 255 759 275 791 253 1812 276 789 255 46372 281 1808 280 785 249 791 253 787 247 793 251 1814 274 791 253 1812 276 1814 274 791 253 1812 276 1814 274 1815 273 792 252 1813 275 42172 272 1819 280 785 249 765 279 761 273 768 276 764 280 1811 277 788 246 768 276 1815 273 792 252 788 246 794 250 1815 273 791 253 -# +# # Brandt TV -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 9346 4516 657 502 659 502 688 473 687 475 634 527 635 527 634 1662 633 528 662 1634 662 1658 635 1660 608 1687 609 1687 633 1662 634 527 634 1662 634 528 633 1663 632 529 632 530 631 1665 630 531 630 531 631 531 630 1666 630 531 630 1666 630 1666 630 532 630 1666 630 1666 630 1666 630 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 9348 4542 631 529 632 503 659 502 688 474 634 527 634 527 634 1661 635 528 661 1636 660 1659 634 1637 632 1687 608 1687 609 1687 609 552 632 1664 633 528 633 1663 633 529 632 1664 631 1665 631 531 630 531 631 531 631 1665 631 531 631 1666 630 531 631 531 630 1666 630 1666 630 1666 631 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 9376 4512 660 475 684 475 635 527 634 528 633 527 634 527 634 1662 662 525 636 1658 637 1658 637 1658 636 1659 636 1660 635 1660 635 526 635 1661 634 527 634 1662 633 1663 632 1664 631 1665 630 531 631 531 630 531 630 1666 630 531 630 531 631 531 630 531 631 1666 630 1666 630 1667 630 -# +# name: Ch_next type: raw frequency: 38000 duty_cycle: 0.330000 data: 9315 4513 657 501 660 501 659 500 661 502 688 499 661 474 634 1660 634 527 634 1660 634 1686 608 1687 607 1686 608 1686 608 1686 608 552 608 1686 608 1687 607 1687 632 530 631 1664 630 1665 630 531 630 531 630 531 630 531 630 531 630 1665 630 531 630 531 630 1665 630 1665 630 1665 630 -# +# name: Ch_prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 9345 4540 631 503 658 529 661 475 683 476 635 526 635 527 634 1661 634 527 662 1658 636 1659 634 1660 608 1686 609 1686 634 1661 634 527 634 1661 634 1662 633 1662 633 1663 632 1664 630 1665 630 531 630 531 630 531 630 531 631 531 630 531 630 531 630 532 629 1666 630 1666 629 1666 629 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 9348 4516 657 501 660 502 659 502 659 502 688 474 634 527 634 1662 634 528 633 1687 634 1660 608 1664 631 1687 608 1687 609 1686 609 552 609 1687 608 553 632 529 633 529 632 530 631 1665 630 531 630 531 631 531 630 1666 630 1666 630 1666 630 1666 630 531 631 1666 630 1666 630 1666 630 -# +# name: Power type: parsed protocol: Samsung32 address: 3E 00 00 00 command: 0C 00 00 00 -# +# name: Vol_up type: parsed protocol: Samsung32 address: 3E 00 00 00 command: 14 00 00 00 -# +# name: Vol_dn type: parsed protocol: Samsung32 address: 3E 00 00 00 command: 15 00 00 00 -# +# name: Ch_next type: parsed protocol: Samsung32 address: 3E 00 00 00 command: 12 00 00 00 -# +# name: Ch_prev type: parsed protocol: Samsung32 address: 3E 00 00 00 command: 13 00 00 00 -# +# name: Mute type: parsed protocol: Samsung32 @@ -2053,37 +2053,37 @@ address: 3E 00 00 00 command: 0D 00 00 00 # # TCL LED49D2930 / Thomson Remote RC3000E02 -# +# name: Power type: parsed protocol: RCA address: 0F 00 00 00 command: 54 00 00 00 -# +# name: Mute type: parsed protocol: RCA address: 0F 00 00 00 command: FC 00 00 00 -# +# name: Ch_next type: parsed protocol: RCA address: 0F 00 00 00 command: B4 00 00 00 -# +# name: Ch_prev type: parsed protocol: RCA address: 0F 00 00 00 command: 34 00 00 00 -# +# name: Vol_up type: parsed protocol: RCA address: 0F 00 00 00 command: F4 00 00 00 -# +# name: Vol_dn type: parsed protocol: RCA @@ -2095,251 +2095,251 @@ type: parsed protocol: NEC address: 01 00 00 00 command: 18 00 00 00 -# +# name: Ch_prev type: parsed protocol: NEC address: 08 00 00 00 command: DD 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 04 F4 00 00 command: 08 F7 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 04 F4 00 00 command: 02 FD 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 04 F4 00 00 command: 03 FC 00 00 -# +# name: Mute type: parsed protocol: NECext address: 04 F4 00 00 command: 09 F6 00 00 -# +# name: Ch_prev type: parsed protocol: Samsung32 address: 07 00 00 00 command: 13 00 00 00 -# +# name: Vol_up type: parsed protocol: SIRC address: 10 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: SIRC address: 10 00 00 00 command: 13 00 00 00 -# +# name: Mute type: parsed protocol: SIRC address: 10 00 00 00 command: 14 00 00 00 -# +# name: Ch_next type: parsed protocol: SIRC address: 0D 00 00 00 command: 10 00 00 00 -# +# name: Ch_prev type: parsed protocol: SIRC address: 0D 00 00 00 command: 11 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 01 00 00 00 command: 11 00 00 00 -# +# name: Ch_prev type: parsed protocol: RC5 address: 01 00 00 00 command: 22 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 40 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 01 00 00 00 command: 10 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 7F 00 00 command: 1E E1 00 00 -# +# name: Mute type: parsed protocol: NECext address: 00 7F 00 00 command: 5C A3 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 20 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 20 00 00 00 command: 09 00 00 00 -# +# name: Ch_next type: parsed protocol: NEC address: 20 00 00 00 command: 03 00 00 00 -# +# name: Ch_prev type: parsed protocol: NEC address: 20 00 00 00 command: 41 00 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 02 7D 00 00 command: 0F F0 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 02 7D 00 00 command: 5A A5 00 00 -# +# name: Ch_next type: parsed protocol: NECext address: 02 7D 00 00 command: 0C F3 00 00 -# +# name: Ch_prev type: parsed protocol: NECext address: 02 7D 00 00 command: 19 E6 00 00 -# +# name: Mute type: parsed protocol: NEC address: 40 00 00 00 command: 10 00 00 00 -# +# name: Ch_next type: parsed protocol: Samsung32 address: 07 00 00 00 command: 10 00 00 00 -# +# name: Ch_prev type: parsed protocol: Samsung32 address: 07 00 00 00 command: 12 00 00 00 -# +# name: Power type: parsed protocol: NEC address: A0 00 00 00 command: 1C 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: A0 00 00 00 command: 5F 00 00 00 -# +# # Xiaomi TV -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1126 497 708 498 706 1433 708 1433 707 497 707 1458 707 497 707 1434 681 523 681 1459 681 1460 680 9808 1100 525 677 527 677 1464 676 1465 675 529 675 1466 674 530 675 1466 675 530 674 1466 674 1466 675 9814 1095 529 675 529 675 1466 675 1466 674 530 674 1466 675 530 674 1466 674 530 674 1466 675 1466 674 9814 1095 530 674 530 674 1466 674 1466 675 530 674 1467 674 530 674 1467 674 530 674 1467 674 1467 674 9814 1095 530 674 530 674 1467 674 1467 673 530 674 1467 673 531 674 1467 673 530 675 1467 673 1467 674 9814 1095 530 674 530 674 1467 674 1467 673 531 673 1467 674 531 673 1467 673 531 673 1467 673 1467 674 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1126 496 707 1122 705 499 704 811 704 1124 704 502 701 502 702 1461 678 1150 677 527 677 527 676 12307 1096 529 675 1154 674 530 674 842 674 1155 673 530 674 530 674 1466 674 1154 674 531 673 530 674 12309 1094 530 674 1155 673 531 673 843 673 1155 673 531 673 530 674 1467 673 1155 673 530 674 531 673 12309 1094 530 674 1155 673 531 673 843 673 1155 673 531 673 531 673 1467 673 1155 673 531 673 531 673 12309 1094 531 673 1155 673 531 673 843 673 1155 673 531 673 531 673 1467 673 1155 673 531 673 531 673 12310 1093 531 673 1156 672 532 672 844 672 1156 672 532 672 532 672 1468 672 1157 671 532 672 532 672 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 1096 528 676 1152 676 529 675 842 673 1155 674 530 674 530 674 1466 674 1467 673 531 674 843 673 11688 1094 530 674 1156 673 532 673 844 672 1156 673 531 673 531 674 1467 673 1468 673 531 673 843 675 11686 1095 530 674 1155 671 534 673 843 673 1156 672 532 673 531 674 1468 672 1468 673 530 674 843 673 11689 1092 532 672 1157 673 531 673 844 673 1155 673 531 674 530 673 1468 673 1467 674 531 673 844 671 11690 1093 532 673 1155 673 533 672 844 672 1156 672 531 673 532 672 1468 673 1468 673 531 673 844 671 11688 1095 531 672 1182 647 532 672 845 672 1156 672 532 671 534 672 1469 670 1469 671 534 672 845 671 -# +# name: Ch_next type: raw frequency: 38000 duty_cycle: 0.330000 data: 1097 505 699 1154 673 531 673 842 675 1156 672 529 675 532 672 842 674 843 674 1156 672 1466 674 11688 1094 531 673 1157 671 531 673 844 671 1156 648 557 672 533 674 842 673 844 672 1156 673 1467 674 11687 1094 531 674 1157 670 534 671 843 674 1156 673 530 672 534 673 844 672 845 671 1158 671 1467 670 11690 1094 531 671 1158 671 531 673 844 674 1155 673 530 673 532 673 842 672 845 646 1184 699 1440 673 11687 1094 532 671 1157 672 531 673 844 672 1155 674 531 673 531 672 845 672 844 672 1157 670 1470 645 11715 1093 531 674 1155 673 531 673 870 647 1156 673 531 674 557 647 843 672 845 672 1156 673 1467 673 -# +# name: Ch_prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 1119 528 675 1154 647 558 674 841 675 1153 675 531 673 532 673 843 673 1155 675 1153 674 531 673 12312 1120 505 672 1156 673 532 673 844 673 1155 673 532 671 534 672 842 675 1155 670 1158 673 530 674 12312 1093 532 671 1156 675 530 672 845 672 1156 673 533 671 533 670 846 672 1156 674 1154 674 529 675 12312 1093 557 646 1182 646 532 674 843 673 1181 646 534 672 532 674 842 673 1158 670 1156 674 530 673 12315 1091 530 674 1155 673 530 673 844 673 1157 644 559 673 532 672 844 670 1157 672 1158 672 532 672 12313 1093 533 671 1157 672 532 673 845 671 1157 670 558 647 532 646 871 672 1157 671 1181 646 534 671 -# +# # Daewoo TV -# +# name: Power type: parsed protocol: NEC address: 80 00 00 00 command: 82 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 80 00 00 00 command: 9F 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 80 00 00 00 command: 8B 00 00 00 -# +# name: Ch_next type: parsed protocol: NEC address: 80 00 00 00 command: 9B 00 00 00 -# +# name: Ch_prev type: parsed protocol: NEC address: 80 00 00 00 command: 8F 00 00 00 -# +# name: Mute type: parsed protocol: NEC @@ -2369,25 +2369,25 @@ type: parsed protocol: NECext address: 00 7F 00 00 command: 06 F9 00 00 -# +# name: Power type: parsed protocol: NECext address: 69 69 00 00 command: 01 FE 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 69 69 00 00 command: 0A F5 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 69 69 00 00 command: 0B F4 00 00 -# +# name: Mute type: parsed protocol: NECext @@ -2465,1099 +2465,1099 @@ type: parsed protocol: NEC address: 38 00 00 00 command: 04 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 634 2571 505 519 479 519 479 518 480 518 480 518 480 518 480 517 481 547 481 517 481 20040 590 2555 501 1007 999 997 510 548 480 486 512 486 512 486 542 485 513 516 482 116758 593 2552 504 1004 992 1004 514 514 514 483 515 513 485 483 545 482 516 482 516 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 525 1955 449 1999 476 4545 446 4544 478 2032 443 2006 469 2011 444 4577 445 4545 447 4574 448 2002 473 4547 444 34913 447 2032 443 2007 478 4542 449 4541 471 2039 446 2004 471 2008 447 4574 448 4543 448 4572 450 2030 445 4545 446 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 389 1737 280 796 253 744 295 754 274 775 274 776 273 1827 271 1828 270 805 254 1820 278 796 253 797 252 745 294 1806 302 773 245 48942 305 1821 277 798 251 746 303 747 271 778 271 1829 279 796 253 796 253 1821 277 798 251 1823 275 1824 274 1825 273 802 247 1827 271 42824 381 1745 272 804 245 752 297 753 275 773 276 774 275 1825 273 1826 272 803 246 1828 270 805 254 795 244 753 296 1804 294 781 247 48939 379 1746 271 804 245 779 270 753 275 774 275 1825 273 802 247 802 247 1827 271 804 245 1829 279 1820 278 1821 277 798 251 1823 275 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 562 1721 561 594 557 597 564 617 513 615 536 618 543 1715 566 1716 566 1692 559 594 567 588 563 618 543 611 540 615 536 618 543 1715 556 623 538 617 534 621 530 624 516 638 513 642 509 1722 560 620 541 1717 565 1692 559 1724 568 1715 556 1701 560 1723 559 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8436 4189 538 1563 566 1559 539 510 559 543 516 507 542 560 509 540 509 567 512 1586 512 1562 567 1559 539 536 533 1566 542 507 562 513 536 540 509 22102 647 1478 559 1568 540 508 541 535 534 515 534 568 511 538 511 539 540 1585 513 1560 559 1567 541 534 535 1564 534 515 534 568 511 538 511 22125 644 1482 565 1561 537 511 538 564 515 508 541 535 534 541 508 516 563 1588 510 1563 556 1570 538 510 559 1567 541 534 515 535 534 541 508 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 448 2031 444 2005 480 4540 452 4539 473 2037 438 2011 474 2006 449 4571 451 4539 453 4568 444 2036 449 4541 451 34906 527 1953 451 1998 477 4543 449 4542 480 2030 445 2004 471 2009 446 4575 447 4543 449 4572 450 1999 476 4545 446 -# +# name: Power type: parsed protocol: NEC42 address: 7B 00 00 00 command: 00 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8041 3979 513 536 482 515 513 1559 509 515 514 1560 508 515 514 509 509 514 484 4000 533 1566 512 537 481 1566 512 537 481 1566 512 537 481 516 513 537 481 24150 8044 3977 505 518 510 539 479 1567 511 512 506 1568 510 539 479 543 485 538 480 3977 536 1564 514 534 484 1563 515 508 510 1563 515 508 510 540 489 534 484 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 383 2027 295 2112 271 2138 266 890 271 887 294 926 235 2114 300 887 274 915 236 2145 269 887 274 884 297 923 238 920 241 917 264 895 266 26573 384 2026 296 2111 273 2136 268 889 272 886 295 924 237 2113 301 886 275 914 237 2144 270 886 275 914 267 921 240 919 242 916 265 924 237 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 177 8474 175 5510 174 8476 173 8477 177 8504 171 5515 175 8476 178 8472 177 8473 176 5541 174 8476 173 45583 171 8481 178 5507 177 8473 176 8474 175 8506 173 5512 172 8478 176 8475 174 8476 178 5538 177 8474 175 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8044 3976 506 517 511 1563 505 517 511 538 480 517 511 538 460 563 455 568 460 3993 530 545 483 1564 514 1559 509 1564 514 509 509 540 488 535 483 513 505 24150 8043 3978 514 509 509 1564 514 509 509 540 478 519 509 540 458 565 464 559 459 3994 529 546 482 1565 513 1560 508 1565 513 510 508 541 487 536 482 541 477 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 558 2942 450 10021 482 2989 484 10015 447 3024 449 10021 472 3100 485 2986 477 2994 500 2999 475 2996 477 2994 479 2992 482 3018 476 2995 479 6464 484 36270 477 3023 450 10020 473 2999 485 10014 448 3022 452 10019 474 3098 478 2994 480 2991 503 2996 477 2994 480 2992 482 2990 484 3015 479 2992 481 6462 485 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 587 2407 476 1062 965 547 482 547 451 577 452 546 452 22108 590 2404 479 1060 967 1028 510 519 509 548 480 487 511 120791 645 2411 472 1066 961 1065 483 515 514 514 504 493 515 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 172 7439 171 7441 169 7443 177 7434 176 7462 178 4887 176 4916 177 4914 169 7469 171 4920 174 4918 175 55174 176 7436 174 7437 173 7439 171 7440 175 7463 172 4894 174 4917 171 4921 172 7465 175 4916 178 4914 169 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 589 2556 500 524 474 554 454 544 454 543 455 543 455 543 455 543 445 583 446 552 446 20046 584 2561 505 1033 485 514 963 518 480 1032 516 512 476 522 506 491 507 522 476 116758 586 2560 506 1033 484 513 964 548 450 1031 507 522 476 521 507 490 508 521 477 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 586 2407 476 1063 964 578 450 548 450 547 481 517 481 577 451 546 452 546 472 556 452 545 453 575 453 514 484 544 484 543 455 14954 510 2483 481 1058 480 548 959 552 446 1067 481 516 512 546 482 515 992 1003 535 493 515 543 486 513 475 522 506 552 446 111671 589 2405 478 1061 477 551 967 514 484 1059 479 549 479 548 480 517 990 1036 512 516 482 546 483 515 503 525 483 544 454 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8444 4180 537 1564 565 1560 538 1561 557 1568 540 1559 539 536 533 542 517 559 510 1563 535 1564 565 1561 537 512 567 1558 540 535 534 1566 542 1557 562 23122 564 1562 557 1569 539 1560 538 1587 542 1558 540 534 535 515 534 541 538 1587 511 1563 566 1560 538 536 533 1567 541 534 515 1585 533 1566 542 23166 561 1565 564 1561 537 1563 535 1590 539 1561 537 538 541 534 515 535 534 1564 534 1566 563 1563 535 540 539 1560 538 511 538 1588 541 1559 539 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 527 1923 481 1998 477 4543 449 4542 470 2040 445 2004 471 2009 446 4575 447 4543 449 4572 450 1999 476 2034 441 34899 524 1956 448 2001 474 4546 446 4545 477 2033 442 2007 478 2002 443 4578 444 4546 445 4575 447 2003 472 2037 448 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 533 1356 437 3474 427 3483 429 3455 436 1454 430 1459 405 28168 510 1379 434 3477 434 3476 425 3459 432 1457 427 1462 402 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 179 7433 177 4915 178 7434 175 7436 174 7464 176 7435 175 4916 177 4915 173 4918 170 4922 171 4920 173 55174 175 7437 173 4919 174 7437 173 7439 171 7467 173 7438 172 4920 173 4919 174 4917 176 4915 178 4914 169 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 169 6731 176 6748 169 6730 177 6748 169 6755 172 4427 177 4447 178 6721 175 6749 178 4446 168 4456 169 54704 176 6723 174 6750 177 6723 173 6750 177 6747 170 4429 175 4449 176 6723 174 6751 176 4448 177 4447 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3506 3494 876 830 840 2576 847 2568 844 862 819 2570 842 864 816 863 818 2570 842 836 844 2572 840 866 815 865 815 2573 839 867 813 866 814 2573 850 857 813 2575 847 2568 844 834 847 2569 843 835 845 2571 872 2571 842 32654 3512 3488 872 834 847 2570 842 2573 839 867 814 2574 849 858 822 857 813 2575 848 859 821 2566 846 832 848 860 821 2567 845 833 848 860 820 2568 844 834 847 2569 843 2572 840 838 843 2574 849 829 841 2575 868 2575 837 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 560 2939 453 10018 475 2996 477 10022 450 3021 452 10018 475 3097 478 6464 483 6460 477 6466 502 6469 479 2993 480 2990 484 36274 564 2936 456 10015 478 2993 481 10020 534 2936 456 10014 479 3093 482 6461 476 6466 482 6461 476 6495 473 2999 485 2986 477 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 10726 41047 10727 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1617 4604 1559 1537 1560 1537 1560 4661 1533 33422 1613 4607 1566 1530 1556 1540 1536 4685 1539 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 4972 177 4910 173 4944 170 6988 174 6984 177 6951 175 6983 174 14269 177 4969 175 4912 176 4941 178 6979 172 6986 175 6953 178 6980 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 7067 176 10544 1055 719 1053 739 1054 738 953 822 1052 2566 169 3435 171 3431 175 3446 170 3432 174 3446 170 3432 174 3428 178 3442 174 3429 177 3425 171 39320 2323 4900 178 10543 1056 719 1053 739 1054 738 953 821 1053 2566 169 3435 171 3431 175 3445 171 3432 174 3446 170 3432 174 3428 178 3442 174 3428 178 3425 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3506 3492 868 839 841 2575 848 858 822 2566 846 832 848 859 821 858 812 2576 847 860 820 2567 845 833 847 860 821 2568 844 862 818 2570 842 864 817 2571 841 2574 849 2567 845 861 820 2568 845 834 847 2570 873 2570 842 34395 3503 3496 874 833 847 2568 845 834 847 2570 842 864 816 835 846 862 819 2569 843 835 846 2571 841 865 816 864 816 2571 841 837 844 2573 850 857 813 2575 848 2567 845 2570 842 864 816 2572 840 838 842 2574 869 2574 838 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 8479 170 5516 178 8472 177 8474 175 8506 173 5513 171 8479 175 8476 178 8472 177 5540 175 8475 174 45584 177 8473 176 5509 175 8476 173 8477 176 8504 170 5516 178 8472 177 8474 175 8476 173 5543 172 8479 170 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 4969 170 6958 173 4944 175 6983 173 6956 174 6984 177 6980 171 16308 180 4966 173 6955 176 4941 172 6985 176 6982 169 6960 176 6982 174 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 585 2409 474 550 478 550 448 1033 994 1063 485 512 506 79916 585 2410 473 551 477 550 448 1034 993 1033 515 543 475 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1192 1012 6649 26844 1192 1013 6648 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3134 6105 6263 82963 3134 6105 6263 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 588 1511 567 1533 565 589 531 597 513 589 541 587 533 1565 533 569 541 1533 565 562 568 1531 537 1537 561 593 537 591 539 1507 561 1539 569 22152 586 1513 565 1535 563 590 540 562 538 564 566 588 542 1557 531 597 513 1534 564 590 540 1533 535 1539 559 568 562 592 538 1509 569 1531 567 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 20 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 689 1461 566 1534 564 589 531 597 513 1534 564 564 566 1533 535 620 510 1537 561 592 538 1535 543 1531 567 587 533 595 535 1511 567 1533 565 22161 588 1512 566 1534 564 564 556 598 512 1535 563 565 565 1534 534 594 536 1538 560 593 537 1536 542 1532 566 587 543 585 535 1511 567 1534 564 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 588 2558 498 526 482 515 483 546 452 545 453 545 453 545 453 545 453 544 474 554 444 20047 583 2562 504 519 479 519 479 1003 515 483 1024 548 450 1001 995 1001 506 116771 593 2552 504 520 478 551 447 1004 513 514 993 549 449 1002 994 1002 516 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 587 2559 507 517 481 517 481 547 451 516 482 546 452 546 452 546 452 545 473 525 483 20038 592 2553 503 521 477 552 446 521 477 1004 514 515 992 1034 483 514 484 513 485 116769 593 2552 504 520 478 550 448 520 478 1003 515 514 993 1032 486 513 475 522 476 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4558 4576 558 596 565 97881 4554 4579 565 589 562 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1039 7202 958 4713 981 4713 961 7255 956 4739 955 16077 1038 7204 956 4714 980 4715 959 7256 954 4741 954 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 506 2638 510 516 482 516 482 546 452 546 452 545 453 545 453 545 453 575 443 554 444 20048 592 2554 502 1036 481 517 481 517 511 516 482 516 961 1035 513 515 483 514 484 116757 594 2552 504 1034 484 514 484 514 504 524 484 513 964 1032 506 522 476 492 506 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 7441 169 4924 174 7437 193 7444 171 7441 174 4918 170 7441 174 4917 171 7440 195 7443 172 7439 171 55187 174 7437 173 4919 175 7437 173 7438 172 7466 175 4891 172 7439 171 4921 172 7439 171 7441 174 7464 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 179 4967 172 4915 178 6980 171 4945 169 4948 176 4912 176 4941 178 16307 176 4969 175 4912 176 6982 174 4942 171 4945 169 4919 174 4943 176 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1042 793 898 883 869 858 924 884 878 877 875 879 873 855 928 1717 901 854 1794 878 894 887 875 1716 902 89114 985 798 903 878 874 880 902 852 900 855 897 857 905 876 896 1722 906 849 1789 883 899 855 897 1721 897 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 8480 169 8481 178 8472 177 8474 175 8476 173 5513 176 8474 175 8476 173 8507 178 5509 175 8505 174 45558 175 8476 173 8476 173 8477 172 8478 171 8480 169 5517 177 8473 176 8474 175 8506 174 5512 172 8509 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 628 2577 499 528 480 545 453 544 454 544 454 544 454 544 454 543 455 543 475 553 445 20047 593 2553 503 521 477 551 447 1004 514 515 992 519 479 1003 515 513 485 543 455 116768 585 2561 505 519 479 519 479 1002 516 513 994 548 450 1001 506 522 476 521 477 -# +# name: Power type: parsed protocol: NECext address: 80 63 00 00 command: 0F 15 00 00 -# +# name: Power type: parsed protocol: NECext address: 80 64 00 00 command: 49 08 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 298 1828 270 804 245 1829 279 1820 278 797 252 771 278 1822 276 1824 274 800 249 1825 273 802 247 776 252 771 278 1822 276 799 250 48931 388 1737 280 796 253 1821 277 1822 276 798 251 1823 275 800 249 774 275 1825 273 776 273 1801 297 1828 270 1829 279 796 253 1821 277 42813 301 1825 273 801 248 1826 272 1827 271 804 245 805 244 1830 278 1821 277 798 251 1823 275 799 250 774 244 779 280 1820 278 796 253 48926 382 1744 354 696 271 1828 270 1829 279 796 253 1821 277 797 252 746 303 1823 275 774 275 1799 299 1826 272 1827 271 804 245 1829 279 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 177 5508 176 5539 176 8475 174 5542 173 8478 171 5545 170 8481 168 8482 177 8473 176 5541 174 8476 173 45573 169 5517 177 5538 177 8473 176 5541 174 8476 173 5544 171 8479 170 8481 178 8472 177 5540 175 8475 174 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 8474 175 8475 174 8477 172 8478 171 8480 169 8481 178 5538 177 5510 174 5542 173 5543 172 5544 171 45575 177 8472 177 8474 175 8476 173 8477 172 8478 171 8481 178 5507 177 5539 176 5540 175 5542 173 5543 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8050 3971 511 1562 516 1558 510 539 490 1557 511 1563 515 533 485 538 490 533 485 3983 530 1569 509 1564 514 1559 509 1565 513 1560 508 515 513 536 482 541 488 24152 8042 3979 514 1560 508 1565 513 510 508 1565 513 1560 508 515 513 536 482 541 488 3980 533 1567 511 1562 516 1557 511 1563 515 1558 510 539 489 534 484 539 490 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 8475 174 5512 177 8473 176 8475 174 8506 179 5508 176 8474 175 8475 174 8477 172 5544 171 8480 169 45587 176 8475 174 5511 173 8477 177 8473 176 8504 170 5516 178 8472 177 8473 176 8475 174 5542 173 8478 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7436 174 7438 172 7439 171 7441 174 7463 172 7440 170 4921 172 4919 174 4917 176 4916 177 4914 174 55176 175 7437 173 7439 191 7446 174 7438 177 7434 176 7435 175 4917 176 4915 179 4914 174 4917 171 4946 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7435 175 4917 177 7435 175 7436 189 7449 176 7435 175 7437 173 4918 175 7436 174 4918 175 4916 178 55171 175 7436 174 4918 170 7441 174 7438 177 7460 170 7441 174 7438 177 4914 174 7437 178 4914 169 4922 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8049 3973 509 1564 514 509 509 1564 514 535 483 1564 514 535 483 540 489 535 483 3980 533 1566 512 537 481 1566 512 511 507 1566 512 537 481 542 486 511 507 24149 8045 3976 516 1558 510 512 516 1557 511 512 516 1558 510 512 516 507 511 512 506 3984 539 1560 508 515 513 1560 508 541 488 1560 508 541 488 536 482 514 504 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 366 202 867 527 170 130516 343 227 863 529 168 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 5992 176 1372 176 1320 177 1345 172 1349 179 1370 178 1317 175 4444 176 1346 171 1351 177 1345 172 1349 179 1344 174 5963 175 65574 172 5995 178 1344 174 1348 175 1347 175 1347 170 1378 170 1325 172 4447 178 1344 173 1349 169 1353 175 1347 170 1351 177 5961 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 497 1478 488 511 467 1482 494 531 447 1477 499 501 466 533 445 530 468 507 471 529 438 12857 488 1485 491 509 469 1481 495 529 448 1476 490 510 468 532 445 529 469 480 498 502 465 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 982 6313 961 2662 903 2718 929 2719 907 6363 900 2722 904 6365 909 6361 903 6368 906 2742 905 46364 989 6307 906 2716 911 2712 925 2723 903 6367 907 2715 901 6369 905 6365 909 6361 903 2745 902 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 599 257 975 317 177 130649 308 228 974 319 175 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 4969 170 6958 173 6985 177 6981 171 6958 178 6980 177 6981 170 16308 180 4966 173 6955 176 6982 169 6988 174 6956 175 6983 173 6984 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 5990 173 1349 174 1348 175 1348 174 4444 176 1346 171 1351 177 4416 178 1344 173 1348 169 1353 175 1347 170 1351 177 9048 177 65573 173 5995 173 1348 175 1348 174 1347 176 4444 171 1351 177 1345 173 4421 173 1348 169 1352 176 1347 170 1351 177 1345 172 9053 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 4972 177 4910 173 6985 177 4940 174 6984 178 6951 175 6983 174 14269 177 4968 176 4912 176 6981 175 4941 173 6985 177 6953 178 6979 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1318 225 177 130305 1609 231 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 585 2410 473 1065 962 581 447 519 479 580 448 549 449 579 449 518 480 548 480 517 481 517 481 577 451 516 482 576 452 545 453 14955 510 2483 481 1058 480 549 969 543 445 1037 511 547 481 546 482 516 482 545 484 545 483 514 484 544 963 1063 485 543 445 111612 626 2427 557 954 513 512 995 547 451 1031 507 521 508 551 477 520 478 550 478 549 479 488 510 548 969 1057 481 517 481 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 203 272 1215 302 171 130229 1423 275 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7436 174 7438 192 7446 174 7437 178 7434 176 7435 175 4917 176 4915 178 4913 175 4917 197 7440 175 55130 286 7377 177 7435 175 7437 173 7438 172 7466 174 7411 178 4913 170 4921 172 4919 174 4918 175 7436 174 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 7437 173 4918 170 7441 174 7437 178 7460 170 7441 179 7433 177 4915 178 4913 170 4922 171 4920 173 55124 291 7372 172 4921 172 7439 171 7441 174 7463 172 7440 170 7442 178 4913 170 4922 171 4920 173 4918 175 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 172 8477 172 8478 171 8480 169 5516 179 8502 178 5509 175 8475 174 8476 173 8479 170 5545 170 8480 169 45588 176 8473 176 8474 175 8476 173 5513 177 8504 171 5515 174 8476 178 8472 177 8473 176 5541 174 8476 173 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 177 7436 174 4918 175 7436 174 4918 175 7462 178 4887 176 4916 177 4914 174 4917 171 4921 172 4919 175 55184 175 7435 175 4918 175 7436 174 4918 175 7462 178 4914 174 4917 171 4920 173 4919 174 4917 176 4915 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 508 2484 480 1060 967 544 485 544 454 574 454 543 455 573 445 553 445 522 506 552 446 552 446 551 477 551 447 581 447 520 478 550 478 15908 626 2427 476 1062 476 522 995 547 451 1061 477 520 508 550 478 519 479 519 999 1058 959 1037 990 582 446 1035 483 111666 590 2404 479 1059 479 549 968 543 455 1027 511 548 480 517 511 486 512 546 961 1065 962 1034 993 579 449 1032 486 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 8475 174 8475 174 8477 172 8478 171 8480 169 5517 178 8473 175 8475 179 8501 173 5513 176 8505 169 45562 179 8472 177 8473 176 8474 175 8476 173 8478 171 5515 174 8476 178 8473 176 8505 174 5512 172 8508 171 120769 178 93377 175 7437 173 4919 174 7437 173 7439 171 7467 173 7439 171 7441 174 4918 170 4921 172 7439 171 7467 173 55167 171 7440 170 4922 171 7440 195 7443 172 7439 171 7441 174 7438 177 4915 173 4918 195 7442 173 7439 170 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 295 1805 273 776 242 1808 270 754 244 1806 272 777 241 758 270 754 264 760 268 756 272 14149 297 1802 266 784 244 1805 263 762 246 1803 265 785 244 780 248 751 267 758 271 753 265 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 535 1723 569 585 566 615 536 619 542 586 565 616 535 1722 539 615 536 1721 561 620 541 587 564 617 534 621 540 588 563 618 543 1714 537 617 534 621 540 615 536 618 543 612 539 615 536 1722 560 594 567 1717 534 1723 569 1714 557 1700 643 1639 643 1641 559 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 502 2521 504 521 997 545 453 575 453 545 453 575 454 22097 591 2433 511 513 994 1062 476 552 476 522 476 552 476 120839 628 2455 509 515 992 1064 484 513 505 493 515 543 475 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 591 2404 479 1060 967 1029 509 519 509 549 479 518 480 79926 585 2408 556 956 989 1033 515 544 484 543 475 522 476 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 586 2560 506 518 480 548 450 548 450 517 481 517 481 517 481 547 451 546 482 516 482 20040 590 2556 500 1038 480 518 969 543 455 543 475 1006 511 517 481 517 511 486 481 116778 584 2561 505 1033 485 513 964 548 450 548 480 1001 506 522 476 522 506 521 446 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 917 206 175 186 170 21561 170 2280 175 2274 502 1929 174 2276 169 5178 170 2261 173 3724 498 1932 171 2279 176 2273 172 3709 172 2277 178 3720 171 17223 177 7619 174 2275 170 2279 176 2256 168 2280 175 5172 176 2256 168 3729 173 2276 179 2253 171 2278 177 3703 178 2271 174 3724 177 17251 170 7627 177 2272 173 2276 169 2263 171 2277 178 5169 169 2262 172 3726 175 2256 168 2280 175 2274 171 3710 171 2278 177 3720 171 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 565 233 653 313 170 130328 752 235 233 107 229 398 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 586 2439 505 549 968 1027 511 517 511 517 481 547 481 21583 584 2439 505 520 997 1059 479 549 479 518 480 548 480 120894 593 2432 501 522 995 1061 477 521 507 520 478 550 478 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 558 8032 474 8115 503 8116 482 8108 480 8141 477 5239 476 8114 504 8115 483 8107 481 5236 509 8111 477 45290 554 8036 481 8108 510 8110 478 8112 476 8145 473 5243 482 8107 511 8109 479 8111 477 5240 505 8115 473 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 173 7438 172 4920 173 7438 172 4920 173 7465 175 4890 173 7439 171 4920 173 4919 174 4917 176 4915 178 55179 170 7441 169 4924 174 7437 178 4913 175 7463 172 4893 170 7441 174 4918 170 4922 171 4920 173 4918 175 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 225 745 222 774 193 778 200 797 175 769 193 777 201 771 196 774 224 747 220 776 202 769 198 248 220 252 196 250 218 253 225 746 221 250 198 248 220 252 226 246 222 223 225 248 220 252 216 229 219 253 225 247 221 277 176 243 220 279 189 230 228 244 224 248 220 252 196 250 218 253 215 257 201 770 197 799 189 257 201 271 197 37716 222 749 218 778 200 771 196 801 172 772 200 771 196 774 193 777 221 750 217 780 197 773 194 251 217 255 193 279 199 273 195 749 218 254 194 252 226 245 223 275 178 242 221 277 201 245 193 253 225 247 221 250 218 254 194 252 226 272 196 223 225 248 220 251 217 255 193 253 225 247 221 251 197 773 194 803 174 297 176 244 219 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3503 2655 197 642 876 2568 844 834 846 833 848 860 821 831 839 2576 847 2569 843 2572 841 2574 849 858 823 857 813 866 815 865 815 2572 841 2575 848 2568 844 2570 842 836 844 863 818 834 847 861 820 2568 845 2571 872 2571 842 32651 3505 3495 875 2567 845 861 820 832 849 859 811 840 840 2576 847 2568 844 2571 842 2574 849 830 840 867 814 838 842 865 815 2572 840 2575 848 2568 845 2571 841 865 815 864 817 834 846 861 819 2569 843 2572 871 2572 840 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 347 677 219 252 196 276 202 742 225 798 174 770 192 778 200 771 196 775 223 748 219 777 200 770 197 249 219 253 195 251 227 271 197 747 220 252 216 229 219 253 225 273 195 277 176 244 219 253 215 230 228 244 224 248 220 252 196 250 218 280 198 247 201 245 223 249 219 253 195 251 227 245 223 248 200 797 175 795 198 248 200 246 222 37666 344 678 228 245 223 222 226 771 196 800 198 747 220 750 217 753 224 773 194 776 202 769 198 799 173 246 227 245 223 249 199 247 221 749 218 280 198 247 201 245 223 249 219 253 195 251 227 244 224 248 200 272 196 250 218 254 194 252 226 245 223 249 199 274 194 251 227 245 193 253 225 273 195 251 217 753 225 746 221 251 197 275 193 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 7437 173 7440 174 7437 178 7433 177 7461 169 7442 178 4914 174 4917 171 4921 172 4919 174 7463 177 55163 177 7435 174 7437 173 7438 172 7440 175 7463 172 7413 176 4915 178 4913 175 4917 171 4920 174 7438 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8042 3979 513 510 508 541 487 1559 509 515 513 1560 508 515 513 510 508 541 477 3981 532 1568 510 1563 515 1558 510 1564 514 1559 509 514 514 535 483 540 488 24151 8042 3979 513 536 482 541 487 1560 508 541 488 1560 508 541 487 536 482 541 477 3980 533 1566 512 1561 507 1566 512 1562 516 1557 511 538 491 533 485 538 490 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8988 4504 640 487 562 592 538 590 510 592 538 590 540 588 532 596 514 614 516 1689 562 1668 563 1695 536 1695 566 1664 567 1690 612 1618 643 1430 801 1613 648 481 558 570 540 589 541 587 533 595 535 592 508 594 536 592 538 1667 564 1693 558 1672 569 1688 563 1668 644 1586 563 1694 567 40630 8994 2265 557 96833 8987 2273 538 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 173 7439 171 4922 172 7439 171 4921 172 7466 174 4917 176 4915 173 4918 170 7441 174 7438 192 7445 175 55181 174 7437 173 4918 175 7437 173 4919 175 7463 177 4888 175 4917 176 4915 178 7460 170 7440 175 7437 178 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1636 4610 1563 1533 1584 7760 1561 28769 1641 4606 1557 1539 1588 7757 1564 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 590 2404 479 1059 968 575 454 544 454 574 454 543 455 543 475 522 476 552 476 552 446 551 447 581 448 520 478 581 447 519 479 549 479 15967 587 2407 476 1062 476 522 995 547 451 1030 508 520 509 550 478 519 479 519 998 1028 999 1057 481 547 960 1066 482 111641 587 2407 476 1063 485 513 994 547 451 1031 507 551 477 551 477 520 478 550 968 1059 968 1027 511 548 959 1036 512 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 588 2406 477 1061 966 577 451 516 482 545 483 545 453 575 443 524 484 514 504 554 454 543 455 543 475 553 445 583 445 521 477 582 446 15969 585 2409 474 1065 483 514 993 549 449 1033 515 543 475 553 475 522 476 552 965 1030 997 576 452 1029 998 1028 479 111668 587 2407 475 1063 485 543 964 517 481 1031 507 552 476 551 477 520 478 550 968 1028 999 574 454 1027 990 1036 481 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 7439 196 7441 174 7437 173 7439 171 7441 174 7438 177 7460 170 7442 178 7433 177 4915 173 4918 170 55186 174 7438 172 7440 170 7441 174 7438 177 7461 169 7416 173 7464 176 7435 175 7437 173 4918 175 4917 176 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 173 7438 172 7441 174 7438 177 7434 176 7462 168 7443 177 7435 175 4917 176 4915 173 7438 177 4941 173 55166 174 7438 197 7441 174 7437 173 7439 171 7441 174 7438 177 7460 170 4922 171 4893 195 7443 172 4920 173 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 175 7436 179 4913 175 4917 171 4920 173 4945 169 4896 177 7435 175 7436 174 7464 176 4916 177 4914 169 55180 170 7441 169 4924 169 4922 171 4920 173 4919 174 4917 176 7435 175 7463 177 7434 176 4916 177 4914 174 -# +# name: Power type: parsed protocol: RC5 address: 02 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 5991 177 1372 176 1320 177 1344 173 1349 174 1374 169 1327 170 4449 176 1346 171 1351 177 1345 172 1349 168 1353 175 5963 175 65573 173 5995 178 1344 174 1348 175 1348 174 1347 170 1378 170 1325 172 4447 178 1344 174 1349 169 1353 175 1347 170 1352 176 5961 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 7761 176 11308 546 957 540 1958 538 970 537 1955 541 962 545 1953 543 964 543 962 545 957 540 970 548 960 547 1945 541 1950 546 965 542 1953 543 962 545 1945 540 970 537 1958 538 7881 172 7744 172 11318 536 971 536 1956 540 963 534 1964 542 966 541 1951 535 968 539 971 536 971 536 969 538 964 533 1965 541 1954 542 963 534 1956 540 971 536 1959 537 968 539 1951 535 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 278 1845 274 808 271 806 273 812 278 805 275 805 274 1840 279 1844 275 809 281 1836 272 806 274 812 278 805 274 1842 277 802 277 44956 279 1842 277 804 275 802 277 808 271 811 279 1838 281 798 271 814 276 1844 275 806 273 1841 278 1845 274 1846 273 808 271 1843 276 44959 275 1845 274 807 272 805 275 811 279 804 275 805 274 1839 280 1844 275 808 271 1845 274 805 274 811 279 804 275 1841 278 801 278 44955 280 1841 278 802 277 801 278 807 272 810 280 1837 271 807 272 813 277 1843 276 805 274 1839 280 1843 276 1845 274 807 272 1842 277 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 509 1717 504 629 512 631 510 627 504 633 508 633 508 1713 508 1719 512 1713 508 625 505 637 504 633 508 629 512 629 512 623 508 1719 512 626 505 628 513 631 510 627 514 623 507 632 509 1713 508 632 509 1716 505 1715 506 1724 507 1716 505 1719 512 1715 506 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 506 493 505 4059 505 5051 501 506 502 4065 499 511 497 4063 501 5058 504 504 504 4060 504 5052 500 507 501 4066 498 5056 506 5042 500 515 503 119614 504 505 503 4065 499 5051 501 511 497 4069 505 499 499 4072 502 5050 502 506 502 4066 498 5053 499 512 506 4060 504 5044 498 5061 501 508 500 -# +# name: Power type: parsed protocol: NECext address: 87 22 00 00 command: E0 1F 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8313 4161 515 1574 514 1571 507 569 510 562 507 563 506 562 507 568 511 562 507 1580 508 1577 511 1582 506 567 513 1575 513 554 505 571 509 564 505 22604 513 1573 505 1589 509 563 506 564 505 562 507 569 511 562 507 563 506 1579 509 1584 514 1576 512 558 511 1574 514 562 507 565 515 556 513 22593 514 1581 507 1583 505 564 505 563 506 570 510 563 506 564 505 562 507 1586 512 1578 510 1577 511 557 512 1581 507 566 514 556 513 555 514 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8735 4383 558 573 557 550 560 568 562 544 566 1722 560 540 560 1732 560 544 566 1719 563 1701 560 1723 559 1704 557 574 556 1699 562 574 556 1703 559 1727 565 1698 563 1721 561 546 564 1723 559 541 559 577 564 541 559 571 560 548 562 565 566 1697 565 567 564 1693 558 1733 559 1701 560 39926 8754 2247 565 92341 8758 2243 589 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3298 3336 821 2506 825 881 820 2505 826 2529 823 856 825 2524 817 866 825 2528 813 2513 818 888 813 862 819 887 814 865 826 2522 820 864 827 2526 815 861 820 887 814 2510 821 885 816 863 818 2531 821 2512 819 2559 793 32401 3298 3349 818 2507 824 882 819 2509 822 2527 814 868 823 2530 821 855 826 2531 821 2504 827 879 822 857 824 875 816 867 824 2529 823 854 827 2530 821 853 817 889 822 2506 825 873 818 866 825 2527 814 2513 818 2564 788 -# +# name: Power type: parsed protocol: NEC42 address: 1C 01 00 00 command: 12 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 289 2112 261 2109 295 2101 262 918 294 912 259 915 297 2098 265 916 296 909 262 2107 297 905 266 913 289 918 263 910 292 909 262 918 294 24789 263 2106 298 2098 265 2110 294 913 258 916 296 905 266 2108 296 911 260 914 288 2107 266 914 298 908 263 911 291 910 261 919 293 913 258 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3215 1637 410 430 405 439 406 1242 408 435 410 1242 408 428 407 440 405 435 410 1240 410 1243 407 431 404 440 405 437 408 1238 402 1254 406 434 401 439 406 438 407 431 404 440 405 437 408 428 407 439 406 434 401 439 406 438 407 1241 409 434 401 441 404 432 403 444 401 1249 401 439 406 438 407 1241 409 434 401 441 404 433 402 444 401 1249 401 439 406 1248 402 436 409 434 401 441 404 433 402 444 401 439 406 45471 3239 1614 403 435 400 444 401 1250 400 437 408 1248 402 438 407 433 402 442 403 1245 405 1248 423 420 405 431 404 443 402 1248 402 1248 422 421 404 435 400 443 402 440 405 431 404 443 402 438 407 433 402 442 403 435 400 443 402 1250 400 436 399 447 408 432 403 438 407 1246 404 434 401 443 402 1250 400 436 399 447 408 432 403 437 408 1246 404 434 401 1253 407 434 401 436 399 447 408 432 403 437 408 436 399 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 7928 3948 504 515 503 518 500 1583 505 516 502 1584 504 510 508 516 502 516 502 3952 510 1579 509 507 501 1587 501 519 510 1571 507 518 500 517 501 517 501 23073 7931 3943 509 514 504 515 503 1578 500 524 505 1580 508 510 508 514 504 511 507 3951 511 1576 502 513 505 1586 502 515 503 1582 506 515 503 513 505 516 502 -# +# name: Power type: parsed protocol: NECext address: 18 18 00 00 command: C0 3F 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 492 4975 496 4993 498 498 500 4056 498 504 494 4055 499 4963 497 532 496 4031 492 4996 495 502 496 4060 494 4972 499 4990 491 506 492 4063 491 511 497 4052 492 4970 490 539 500 4027 496 103358 500 4961 500 4995 496 505 493 4056 498 499 499 4057 497 4969 491 533 496 4026 497 4997 494 508 490 4059 495 4967 494 5001 490 511 497 4053 491 505 493 4063 491 4975 496 528 490 4032 491 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3425 3482 848 2607 846 2639 845 2608 846 2639 845 2612 852 2624 850 2613 851 911 851 885 847 919 843 891 851 915 847 890 852 907 845 897 845 917 845 891 851 915 847 887 845 2639 845 2612 852 2625 849 2613 851 2630 844 34282 3455 3478 852 2601 852 2633 851 2605 848 2629 845 2617 847 2633 851 2604 849 917 845 890 852 913 849 889 843 915 847 896 846 916 846 890 852 914 848 885 847 919 843 895 847 2630 844 2617 847 2634 850 2605 848 2636 848 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 500 500 498 4066 498 5058 504 502 506 4061 503 508 500 4060 504 5055 497 5055 497 511 497 4071 503 5047 505 507 501 4065 499 5049 503 512 496 124314 501 508 500 4067 507 5043 499 513 505 4060 504 501 497 4073 501 5052 500 5052 500 512 496 4066 498 5058 504 506 502 4058 506 5053 499 509 499 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7763 174 8817 169 10842 544 1955 541 967 540 1952 544 959 538 972 546 962 545 1947 538 1952 544 967 540 1955 541 964 543 1947 539 972 546 1949 547 7873 170 7746 170 10350 175 11811 543 960 537 1961 535 972 535 970 537 965 542 1956 540 1956 540 965 542 1947 539 973 534 1960 536 969 538 1952 534 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 2570 2682 1189 1208 1186 2665 1186 1217 1187 2668 1183 1215 1179 2671 1190 2695 1186 1187 1187 2692 1179 2671 1190 1213 1181 1193 1191 1207 1187 2663 1188 1215 1179 2676 1185 46941 2563 2685 1186 1191 1183 2698 1184 1188 1186 2691 1180 1197 1187 2694 1187 2666 1185 1210 1184 2674 1187 2695 1186 1185 1189 1206 1188 1189 1185 2696 1186 1186 1187 2689 1182 -# +# name: Power type: parsed protocol: RC5 address: 00 00 00 00 command: 26 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3325 1560 406 444 401 453 402 1226 404 449 406 1226 404 443 402 454 401 449 406 1224 406 1228 402 446 409 444 401 451 404 1223 407 1230 410 440 405 445 410 443 402 446 409 444 401 451 404 442 403 453 402 448 407 443 402 451 404 1224 406 448 407 444 401 445 410 446 409 1221 409 442 403 1231 409 439 406 1227 403 450 405 441 404 452 403 1227 403 448 407 446 409 439 406 447 408 444 401 445 400 456 410 440 405 52348 3320 1553 403 445 400 454 401 1230 400 447 408 1228 402 449 406 444 401 452 403 1225 405 1229 431 421 404 442 403 454 401 1229 401 1229 431 423 402 446 399 455 400 451 404 442 403 453 402 448 407 443 402 451 404 444 401 452 403 1229 401 445 400 457 398 451 404 446 399 1235 405 443 402 1232 408 444 401 1225 405 452 403 447 398 452 403 1231 399 449 406 447 408 443 402 444 401 456 399 450 405 445 400 453 402 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 990 915 980 909 986 924 981 2829 981 934 981 2823 987 923 982 2828 982 933 982 906 989 33895 989 907 988 927 988 900 985 2841 979 915 980 2851 980 909 986 2840 980 914 981 934 981 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 821 5754 848 2490 841 2492 819 2524 817 5726 845 2492 839 5727 844 5757 845 5727 854 2483 848 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 5092 1621 406 2599 406 2598 407 2605 1653 1616 411 2619 1609 1606 1654 1618 409 2623 382 2600 405 -# +# name: Power type: parsed protocol: NEC address: 15 00 00 00 command: 12 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1144 1010 6795 26754 1151 997 6798 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1144 1009 1120 1006 1143 1991 1116 26758 1146 1006 1123 1003 1146 1988 1119 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 46238 169 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8906 4165 572 1672 569 1678 563 640 572 637 565 643 569 633 569 643 569 1674 567 639 563 1684 567 637 565 1681 570 1675 566 1673 568 1681 570 636 566 640 572 637 565 639 563 1684 567 640 572 630 572 640 572 634 568 1675 566 1681 570 1670 571 638 564 1681 570 1669 572 1678 563 1680 571 40485 8898 2252 570 85621 8955 2194 567 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 446 1191 449 1194 446 1195 445 1204 446 1200 1316 459 447 1193 447 1202 448 1197 443 1201 449 1191 449 34491 443 1204 446 1197 443 1198 442 1207 443 1202 1314 436 440 1201 449 1199 441 1205 445 1198 442 1199 441 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4268 4327 522 1593 516 1603 516 1597 522 519 520 520 519 515 514 531 518 520 519 519 520 521 518 519 520 1597 522 1595 514 1597 522 1599 520 1595 514 524 515 1604 515 521 518 523 516 524 515 519 520 525 514 524 515 1599 520 522 517 1596 513 1605 514 1602 517 1595 514 1607 522 1592 516 40481 8748 2187 523 93986 8721 2189 521 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 247 3006 244 153 178 1007 251 117 637 597 381 678 218 156 175 529 382 679 217 157 174 24963 247 3038 252 117 219 994 249 119 217 483 250 117 173 531 278 779 173 167 169 569 383 679 217 156 175 126538 246 3039 251 118 218 995 247 120 195 504 249 118 172 532 277 780 172 168 178 560 382 680 216 157 174 -# +# name: Power type: parsed protocol: NEC address: 10 00 00 00 command: EF 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4278 4318 521 517 522 520 519 517 522 520 519 521 518 516 513 531 518 520 519 1595 514 1605 514 1599 520 1598 521 1595 513 1598 521 1600 519 1596 513 1602 517 1601 518 1595 514 1604 515 525 514 520 519 526 513 525 514 524 515 527 522 513 516 526 513 1603 516 1595 514 1607 522 1593 516 40481 8749 2186 524 93985 8722 2189 521 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 2746 8423 2744 19607 2746 19601 2742 8431 2745 8424 2742 19608 2745 8419 2747 19608 2745 19607 2746 8422 2744 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 170 6683 174 4889 175 10382 372 849 821 2498 176 10378 264 2479 842 2492 839 2475 846 2483 838 2481 840 2495 836 2477 844 845 846 37009 172 6681 176 4888 175 10381 373 848 924 2395 844 2490 174 10383 248 2492 172 10386 245 2495 169 -# +# name: Power type: parsed protocol: NEC address: C4 00 00 00 command: 18 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 37 00 00 00 command: 12 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 179 2644 178 8174 170 2646 176 8181 173 2648 174 8177 177 2640 172 5419 174 5413 170 2649 173 2644 178 2646 176 2646 176 5409 174 28831 174 2651 171 8183 171 2648 174 8174 170 2655 177 8177 177 2642 170 5412 171 5420 173 2649 173 2646 176 2640 172 2653 169 5419 174 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 174 2648 174 8178 176 2640 171 8185 169 2653 169 8182 172 2645 177 2647 557 2264 558 5027 174 5410 173 5418 175 5413 170 28837 168 2649 173 8184 170 2652 170 8181 173 2643 169 8188 176 2646 176 2643 168 2648 174 5417 176 5411 172 5413 170 5413 170 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 706 266 139 268 137 173 173 198 234 178 126768 175 299 169 86 275 130 267 138 172 230 177 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 554 1922 584 3809 582 3782 578 3821 580 1920 555 1941 544 1922 574 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 476 1470 465 3479 474 3469 474 3477 475 1467 468 1471 474 27473 472 1474 472 3475 467 3478 475 3468 474 1471 475 1468 467 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 301 2188 236 2169 235 2205 230 1009 234 1070 183 1064 179 2198 206 1046 207 1069 173 2207 207 1042 211 1062 201 1043 210 1032 231 1005 237 1039 234 1006 236 -# +# name: Power type: parsed protocol: NEC address: 6D 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 20 00 00 00 command: 11 00 00 00 -# +# name: Power type: parsed protocol: NEC address: A0 00 00 00 command: 0B 00 00 00 -# +# name: Power type: parsed protocol: NEC address: FF 00 00 00 command: 3F 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 176 7764 173 11361 544 1951 545 1948 176 8815 171 2319 177 2322 174 2321 175 2318 178 2313 173 18281 170 7746 170 11369 536 1954 542 1957 539 969 538 1954 542 1949 536 1962 534 1960 174 2320 176 2315 170 2328 178 2317 168 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 172 23035 176 2267 178 2285 170 2270 175 2288 177 11602 222 2218 216 2246 173 183 173 935 221 218 174 864 221 1250 171 1310 223 2219 216 2247 218 1244 223 216 176 869 170 1296 217 2239 216 1254 223 216 176 866 219 9127 169 7642 173 2284 171 2273 172 2290 175 2263 171 10143 178 10623 222 1245 222 217 175 1843 220 2226 219 1264 223 1237 174 183 178 952 219 2222 223 216 176 866 219 1249 172 9190 173 7637 178 2266 169 2286 169 2280 175 2284 171 10125 175 10622 224 215 177 868 216 2228 217 2238 171 1299 224 215 177 865 174 183 173 934 222 216 176 1848 215 1247 220 1265 222 762 170 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 7410 1482 382 2742 375 2747 380 2751 1630 1535 400 2724 1657 1529 1629 1538 407 2720 407 2716 401 2722 4125 1549 376 2751 376 2748 379 2744 1626 1541 405 2723 1658 1530 1628 1531 404 2726 401 2726 401 2723 4124 1542 383 2747 380 2747 380 2745 1626 1534 401 2729 1652 1539 1629 1532 403 2719 408 2722 405 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4277 4319 520 518 521 521 518 518 521 1597 522 1594 515 1597 522 1599 520 1594 515 1600 519 1600 519 1594 515 526 513 527 522 512 517 528 521 517 522 516 513 1605 514 522 517 525 514 525 514 521 518 526 513 525 514 1600 519 523 516 1597 522 1596 513 1603 516 1595 514 1607 522 1593 516 40481 8749 2186 524 93986 8721 2188 522 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1325 431 445 1199 1317 455 441 1207 443 1202 1294 457 449 1190 440 1209 441 1205 445 1198 1318 454 442 93237 1320 434 442 1201 1325 448 448 1200 440 1205 1291 460 446 1193 447 1202 448 1198 442 1201 1325 447 449 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 0B 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 300 1791 297 744 295 743 296 751 298 745 294 747 302 736 293 1837 271 745 294 747 302 1793 295 752 297 1802 296 1801 297 742 297 1806 302 31592 296 1801 297 742 297 749 300 744 295 745 294 745 294 752 297 1829 269 746 293 745 294 1809 300 744 295 1802 296 1799 299 748 301 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 2003 177 2899 177 1996 174 2908 168 2910 177 2000 170 2004 176 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8898 4173 564 642 570 1677 564 1677 564 645 567 640 572 631 571 641 571 635 567 639 563 1683 568 1673 568 641 571 636 566 637 565 647 565 641 571 634 568 642 570 1671 570 639 563 1682 569 632 570 643 569 636 566 1677 564 1683 568 636 566 1680 571 636 566 1674 567 1682 569 1674 567 40489 8904 2246 566 85626 8902 2248 564 -# +# name: Power type: parsed protocol: NEC address: AA 00 00 00 command: 80 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 51 00 00 00 command: 08 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3892 3856 525 978 529 977 530 969 528 977 530 974 523 975 532 1924 531 971 526 1924 531 975 532 1916 529 976 531 1921 524 1947 508 1925 530 1920 525 1926 529 1925 530 970 527 1927 528 976 531 1915 530 978 529 1921 1033 9201 3871 3867 524 977 530 975 522 981 526 972 525 983 524 978 529 1921 524 982 525 1923 532 973 524 1928 527 971 526 1931 524 1950 505 1922 523 1931 524 1924 531 1923 532 972 525 1922 523 985 533 1918 527 975 532 1922 1032 -# +# name: Power type: parsed protocol: NEC address: 6E 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: RC5 address: 07 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4758 1543 403 2731 407 2726 402 2739 1601 1514 401 2760 1580 1530 1577 1540 406 2758 400 2708 1602 1535 400 2740 408 2729 409 2726 401 2731 1599 1520 405 2758 1572 1540 1578 1532 403 2737 431 2706 1604 1535 411 2722 405 2734 403 2734 404 2731 1599 1512 403 2764 1576 1539 1578 1533 402 2730 428 2712 1608 1534 402 -# +# name: Power type: parsed protocol: NEC address: AA 00 00 00 command: A7 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 171 313 176 798 337 133 600 232 170 126777 176 65 169 496 176 200 609 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 500 527 491 4033 500 4986 495 510 498 4054 500 499 499 4048 496 4974 497 530 499 4026 497 4989 492 513 495 4057 497 4967 493 4992 499 506 492 4060 494 505 493 4054 500 98563 497 529 500 4025 498 4988 493 512 496 4056 498 501 497 4050 493 4976 495 532 497 4028 495 4991 500 505 493 4059 495 4969 491 4994 497 508 490 4062 492 507 491 4056 498 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 879 901 871 1796 1770 903 869 917 876 916 877 913 880 906 877 918 875 914 879 1788 1768 1784 875 87826 871 921 872 1797 1769 897 875 919 874 915 878 910 873 920 873 914 879 913 870 1800 1776 1767 871 -# +# name: Power type: parsed protocol: Kaseikyo address: 90 02 20 00 command: D0 03 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4411 4332 558 1660 561 596 565 612 559 597 564 617 565 585 566 620 561 591 560 620 561 595 566 611 560 597 564 1653 558 592 559 627 565 589 562 617 564 1630 560 617 564 592 559 22591 4439 4322 558 1639 561 618 563 590 561 622 560 592 559 624 557 597 564 612 559 600 561 618 563 590 561 622 559 1628 562 621 561 594 567 609 562 597 564 1652 559 595 566 617 564 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3444 1767 413 487 419 1274 417 481 415 1277 414 488 418 1267 414 492 414 1276 415 484 412 1282 419 478 418 1275 416 1276 415 480 416 1280 421 479 417 1272 419 1275 416 1272 419 1274 417 484 412 484 412 493 413 1277 414 485 421 1273 418 479 417 486 420 1271 420 476 420 486 420 479 417 482 414 1280 411 1276 415 488 418 1273 418 478 418 488 418 481 415 1275 416 487 419 478 418 485 411 1280 421 475 421 1275 416 1273 418 69071 3439 1759 441 456 440 1253 438 463 443 1243 438 468 438 1251 440 460 446 1247 444 454 442 1251 440 461 445 1241 440 1256 445 455 441 1248 443 461 445 1242 439 1254 447 1245 446 1240 441 465 441 458 438 462 444 1249 442 456 440 1252 439 463 443 452 444 1252 439 461 445 454 442 461 445 452 444 1249 442 1250 441 454 442 1254 437 463 443 456 440 463 443 1245 446 456 440 462 444 451 445 1251 440 460 436 1253 438 1256 445 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8705 4317 583 682 581 688 585 678 585 1721 581 1722 580 681 582 690 583 682 581 1721 581 1725 587 1713 579 689 584 683 580 1718 584 1724 588 1714 588 677 586 683 580 683 580 1726 586 680 583 678 585 687 586 679 584 1718 584 1721 581 1719 583 685 588 1716 586 1713 579 1729 583 1719 583 41145 8706 2217 585 94686 8705 2217 584 -# +# name: Power type: parsed protocol: NECext address: 10 2D 00 00 command: 1F E0 00 00 -# +# name: Power type: parsed protocol: RC5 address: 05 00 00 00 command: 0C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 178 7753 174 2308 178 2284 171 2316 170 2298 177 10228 174 10724 223 1256 221 217 175 1868 169 2293 172 1327 216 1263 178 1316 217 2245 220 218 174 887 218 1261 170 10707 174 7752 175 2296 169 2314 171 2293 172 2307 179 10216 176 6265 174 10729 219 1258 219 1272 215 1268 173 2310 221 1255 222 217 175 877 172 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 532 1692 559 561 529 574 567 559 531 566 564 555 535 1694 557 568 532 1691 560 560 530 573 557 568 532 565 565 555 535 567 563 1688 533 564 567 554 536 567 563 562 538 558 562 558 532 1697 565 561 539 1683 558 1689 532 1697 564 1687 534 1689 562 1684 537 -# +# name: Power type: parsed protocol: NECext address: 12 FF 00 00 command: 0E F1 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 446 1694 455 1706 453 1705 424 628 452 597 452 622 458 1673 456 625 455 594 455 1706 454 590 459 621 459 591 458 616 453 591 458 622 539 22750 459 1675 454 1733 427 1712 427 622 458 588 451 622 458 1681 458 618 451 595 454 1705 455 598 451 625 454 592 457 616 453 598 451 626 535 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3429 3445 875 2555 868 875 867 871 871 2560 873 868 874 2551 872 874 868 871 871 869 873 870 872 865 867 2565 868 873 869 2556 867 2567 866 874 868 2560 873 870 872 2555 868 2564 869 2586 847 2577 846 2589 844 870 872 32983 3470 3430 869 2559 874 868 874 867 875 2550 873 873 869 2559 874 865 867 877 875 862 870 873 869 872 870 2555 868 877 875 2554 869 2559 874 869 873 2554 869 873 869 2562 871 2553 870 2590 843 2586 847 2581 842 876 866 -# +# name: Power type: parsed protocol: NEC address: 83 00 00 00 command: FF 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4275 4320 519 520 519 523 516 520 519 1599 520 520 519 515 524 521 518 520 519 1595 524 1595 524 1588 520 521 518 1599 520 1591 517 1603 516 1599 520 1595 524 1594 525 1588 521 1597 522 518 521 513 526 519 520 518 521 517 522 520 519 517 522 519 520 1597 522 1589 519 1601 518 1597 522 40475 8724 2186 514 93995 8752 2183 516 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 10380 4892 599 620 592 605 597 620 592 604 598 623 589 2081 598 627 595 2080 599 2101 598 2105 574 2099 590 2088 591 2111 599 591 590 2116 594 599 593 626 596 2082 597 619 593 2086 593 626 596 594 598 627 595 598 594 2106 593 604 598 2100 589 607 595 2107 593 2079 590 2116 594 2082 750 41149 8722 2109 590 94682 8727 2104 596 -# +# name: Power type: parsed protocol: NEC address: 83 00 00 00 command: 08 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4271 4324 515 1600 519 1600 519 1594 515 1603 516 1601 518 1593 516 1605 514 1601 518 520 519 522 517 520 519 522 517 523 516 518 521 523 516 522 517 1598 521 1597 522 1591 518 1600 519 521 518 516 523 522 517 521 518 520 519 523 516 520 519 522 517 1599 520 1591 518 1603 516 1599 520 40477 8753 2183 516 93993 8724 2185 515 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 7847 3931 470 1448 467 495 472 1443 472 490 467 1451 464 491 466 499 468 491 466 4413 467 1455 470 486 471 1449 466 495 472 1441 464 501 466 492 465 494 463 22093 7851 3934 467 1454 471 490 467 1446 469 496 471 1446 469 489 468 494 473 484 473 4410 470 1449 466 489 468 1455 470 489 468 1449 466 496 471 486 471 490 467 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3462 1592 490 332 513 1200 489 331 514 1201 489 355 490 1201 489 356 512 1178 489 356 512 1178 512 334 487 1202 488 1202 488 357 512 1178 512 334 486 1203 487 1202 488 1203 487 1204 486 383 461 1228 488 357 488 357 487 357 487 1203 486 1204 486 359 485 1205 485 360 485 361 484 360 485 360 485 361 484 361 484 361 484 1206 484 360 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 1206 484 361 484 71543 3434 1620 486 359 485 1205 485 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1206 484 360 485 1205 485 1206 484 360 485 1206 484 360 485 1206 484 1206 484 1206 484 1206 484 361 484 1206 484 360 485 360 485 361 484 1206 484 1206 484 360 484 1206 484 360 485 361 484 361 484 360 485 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 361 484 1207 483 361 484 1206 484 361 484 71543 3435 1619 486 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 484 1205 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 1205 485 1206 484 360 485 1205 485 360 485 360 485 360 485 1205 485 1206 484 360 485 1206 484 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1206 484 360 485 360 485 360 485 1205 485 360 485 1206 484 360 485 71542 3436 1619 486 358 487 1204 486 359 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 1206 484 1206 484 1206 484 1206 484 360 485 1206 484 360 485 360 485 361 484 1206 484 1206 484 361 484 1206 484 361 484 361 484 361 484 361 484 361 484 361 484 360 485 1206 484 361 484 361 484 361 484 1206 484 361 484 361 484 360 485 1206 484 361 484 1206 484 361 484 71542 3437 1618 487 358 486 1204 486 359 486 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 360 485 1205 485 1206 484 360 485 1205 485 360 485 1206 484 1205 485 1206 484 1205 485 360 485 1205 485 360 485 360 485 360 485 1205 485 1205 485 360 485 1205 485 360 485 360 485 360 485 360 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 360 485 360 485 1205 485 360 485 1205 485 360 485 -# +# name: Mute type: parsed protocol: NEC From f858949372f3f55d5b3aebbebe98b4cc397101a2 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:39:39 +0100 Subject: [PATCH 113/236] Update projectors.ir Removed additional whitespaces --- .../resources/infrared/assets/projectors.ir | 176 +++++++++--------- 1 file changed, 87 insertions(+), 89 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/projectors.ir b/applications/main/infrared/resources/infrared/assets/projectors.ir index e1cc36b8ec..0279f73e9c 100644 --- a/applications/main/infrared/resources/infrared/assets/projectors.ir +++ b/applications/main/infrared/resources/infrared/assets/projectors.ir @@ -2,7 +2,7 @@ Filetype: IR library file Version: 1 # Last Updated 5th Oct, 2024 # Last Checked 5th Oct, 2024 -# +# # TEMP FIX FOR POWER # # ON @@ -71,7 +71,7 @@ type: parsed protocol: NECext address: 00 30 00 00 command: 83 7C 00 00 -# +# name: Vol_up type: parsed protocol: NECext @@ -107,13 +107,13 @@ type: parsed protocol: NECext address: 87 4E 00 00 command: 29 D6 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 87 4E 00 00 command: 08 F7 00 00 -# +# name: Vol_dn type: parsed protocol: NECext @@ -149,7 +149,6 @@ type: parsed protocol: NEC address: 02 00 00 00 command: 1D 00 00 00 -# # ON name: Power type: raw @@ -162,7 +161,6 @@ type: parsed protocol: NEC address: 02 00 00 00 command: 1D 00 00 00 -# # ON name: Power type: raw @@ -835,25 +833,25 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 219 27658 217 27663 216 27658 216 27634 216 27642 215 27646 217 27662 217 27637 216 27649 216 27649 218 27656 217 27658 215 27640 214 27636 217 27649 216 27644 218 27635 217 27630 215 27645 216 27631 215 27632 216 27650 216 27628 217 27630 214 27627 217 27623 215 27632 215 27641 216 27634 214 27633 215 27648 215 27648 217 27651 215 27635 216 27629 216 27630 216 2021 9254 4461 618 542 618 542 618 542 618 1675 619 1676 618 541 619 541 619 542 618 1677 617 543 617 543 617 1678 616 568 592 1702 592 1702 618 1676 618 542 618 542 618 543 617 1677 617 543 617 544 616 1678 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1678 616 544 616 1678 616 40239 9200 2247 617 99930 110 27739 119 27738 123 27750 126 27738 175 27617 214 27716 203 27604 213 27639 217 27631 214 27722 136 27753 119 27736 175 27618 246 27683 177 27619 245 27685 171 55486 244 27693 158 27635 241 27695 170 27693 129 27717 340 27530 113 27757 106 27751 124 27728 172 27707 126 27666 215 27708 123 27733 123 -# +# name: Vol_dn type: parsed protocol: NECext address: 18 E9 00 00 command: 49 B6 00 00 -# +# name: Power type: parsed protocol: NEC address: 02 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: NEC @@ -865,13 +863,13 @@ type: parsed protocol: NEC address: 02 00 00 00 command: 48 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 02 00 00 00 command: 40 00 00 00 -# +# name: Mute type: parsed protocol: NEC @@ -901,7 +899,7 @@ type: parsed protocol: NECext address: B8 57 00 00 command: 1E E1 00 00 -# +# name: Vol_up type: parsed protocol: NECext @@ -925,13 +923,13 @@ type: parsed protocol: NEC address: 32 00 00 00 command: 8F 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 32 00 00 00 command: 8C 00 00 00 -# +# name: Mute type: raw frequency: 38000 @@ -949,19 +947,19 @@ type: parsed protocol: NEC address: 00 00 00 00 command: A8 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 88 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 9C 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC @@ -979,19 +977,19 @@ type: parsed protocol: NECext address: 87 45 00 00 command: 17 E8 00 00 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 9064 4354 666 1559 666 1562 662 1586 638 475 636 477 635 477 635 478 635 1590 635 1591 634 478 635 1591 634 478 634 478 635 478 634 1591 635 478 634 1591 634 478 635 478 634 478 635 1591 634 478 634 1591 635 478 634 478 634 1591 634 1591 635 1591 634 478 635 1591 634 478 634 1591 635 40957 9035 2144 634 95483 9047 2155 632 95484 9048 2153 633 -# +# name: Vol_dn type: parsed protocol: NECext address: 87 45 00 00 command: 50 AF 00 00 -# +# name: Mute type: raw frequency: 38000 @@ -1009,13 +1007,13 @@ type: parsed protocol: NECext address: FF FF 00 00 command: E8 17 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: FF FF 00 00 command: BD 42 00 00 -# +# name: Vol_dn type: parsed protocol: NECext @@ -1033,13 +1031,13 @@ type: parsed protocol: Kaseikyo address: 41 54 32 00 command: 05 00 00 00 -# +# name: Vol_up type: parsed protocol: Kaseikyo address: 41 54 32 00 command: 70 01 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo @@ -1099,61 +1097,61 @@ type: parsed protocol: NECext address: 4F 50 00 00 command: 02 FD 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 4F 50 00 00 command: 08 F7 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 4F 50 00 00 command: 0B F4 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 81 03 00 00 command: F0 0F 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 87 45 00 00 command: 51 AE 00 00 -# +# name: Mute type: parsed protocol: NECext address: 87 45 00 00 command: 52 AD 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500 -# +# name: Vol_up type: parsed protocol: NEC address: 01 00 00 00 command: 06 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 01 00 00 00 command: 09 00 00 00 -# +# name: Mute type: parsed protocol: NEC @@ -1171,253 +1169,253 @@ type: parsed protocol: NEC address: 01 00 00 00 command: 00 00 00 00 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 9035 4437 563 548 563 548 563 522 594 1645 591 1639 592 518 593 548 563 552 563 1640 592 548 563 553 562 1668 564 524 592 1642 594 1674 562 1673 563 1639 593 548 563 552 564 1669 562 548 563 520 615 529 586 1645 587 529 587 1650 586 1646 586 529 586 1650 586 1649 587 1646 586 524 587 524 587 524 587 524 587 525 643 467 644 440 671 467 644 472 643 1592 644 1593 643 1593 642 1594 641 1594 587 1649 585 1651 563 1682 562 14430 9008 2205 562 -# +# name: Vol_up type: parsed protocol: NECext address: 84 F4 00 00 command: 2C D3 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 84 F4 00 00 command: 2F D0 00 00 -# +# name: Mute type: parsed protocol: NECext address: 4F 50 00 00 command: 0F F0 00 00 -# +# name: Power type: parsed protocol: NEC address: 02 00 00 00 command: 12 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 02 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 02 00 00 00 command: 06 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 02 00 00 00 command: 00 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 04 B1 00 00 command: 58 A7 00 00 -# +# name: Power type: parsed protocol: NECext address: 04 B1 00 00 command: 58 A7 00 00 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 9107 4376 681 1573 681 472 655 472 654 474 652 475 652 476 651 476 652 476 651 476 651 1604 651 1604 651 1604 651 1603 652 1604 651 1604 651 1604 651 1604 650 476 651 477 650 477 650 476 651 477 651 1604 650 476 651 477 650 1604 651 1604 650 1604 651 1604 650 1604 651 477 650 1604 650 39498 9079 2178 651 -# +# name: Power type: parsed protocol: NECext address: 8B CA 00 00 command: 12 ED 00 00 -# +# name: Power type: parsed protocol: NECext address: 8B CA 00 00 command: 12 ED 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 8B CA 00 00 command: 44 BB 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 8B CA 00 00 command: 46 B9 00 00 -# +# name: Mute type: parsed protocol: NECext address: 8B CA 00 00 command: 11 EE 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 81 03 00 00 command: F0 0F 00 00 -# +# name: Power type: parsed protocol: NEC address: 01 00 00 00 command: 40 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 BD 00 00 command: 01 FE 00 00 -# +# name: Power type: parsed protocol: NEC address: 01 00 00 00 command: 40 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 BD 00 00 command: 01 FE 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 00 BD 00 00 command: 10 EF 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 00 BD 00 00 command: 0C F3 00 00 -# +# name: Mute type: parsed protocol: NECext address: 00 BD 00 00 command: 6A 95 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 02 00 00 00 command: 11 00 00 00 -# +# name: Mute type: parsed protocol: NECext address: 87 4E 00 00 command: 51 AE 00 00 -# +# name: Pause type: parsed protocol: NECext address: 00 30 00 00 command: A8 57 00 00 -# +# name: Play type: parsed protocol: NECext address: 00 30 00 00 command: A9 56 00 00 -# +# name: Pause type: parsed protocol: NECext address: 00 30 00 00 command: A9 56 00 00 -# +# name: Play type: parsed protocol: NECext address: 83 55 00 00 command: 5E A1 00 00 -# +# name: Pause type: parsed protocol: NECext address: 83 55 00 00 command: 5B A4 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 93 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 93 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 01 00 00 00 command: 15 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 01 00 00 00 command: 15 00 00 00 -# +# name: Play type: parsed protocol: NECext address: B8 57 00 00 command: 18 E7 00 00 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 8981 4411 532 1616 557 1592 557 465 556 465 556 466 555 467 554 468 554 1595 554 467 554 468 554 1595 554 467 580 1569 581 1568 581 1568 581 1569 580 441 581 1569 580 442 580 1570 579 1594 555 467 555 443 579 442 580 1594 554 467 555 1594 555 467 555 468 554 1595 554 1595 528 1620 529 42169 9008 2106 531 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 8981 4411 532 1616 557 1592 557 465 556 465 556 466 555 467 554 468 554 1595 554 467 554 468 554 1595 554 467 580 1569 581 1568 581 1568 581 1569 580 441 581 1569 580 442 580 1570 579 1594 555 467 555 443 579 442 580 1594 554 467 555 1594 555 467 555 468 554 1595 554 1595 528 1620 529 42169 9008 2106 531 -# +# name: Play type: parsed protocol: NEC address: 31 00 00 00 command: 41 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 31 00 00 00 command: 41 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 01 00 00 00 command: 03 00 00 00 -# +# name: Pause type: parsed protocol: NEC @@ -1483,73 +1481,73 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 353 1742 355 693 355 1742 355 1742 355 693 355 1744 353 694 354 1743 354 693 355 1743 354 694 354 693 355 694 354 1742 355 695 353 43687 354 1743 354 696 352 1744 353 1744 353 694 354 695 353 1742 355 694 354 1743 354 694 354 1743 354 1742 355 1742 355 694 354 1744 353 41606 351 1745 352 696 352 1746 351 1746 351 697 351 1747 350 696 352 1745 352 698 350 1746 351 698 350 696 352 698 350 1746 351 699 349 -# +# name: Power type: parsed protocol: Samsung32 address: 07 00 00 00 command: 02 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 07 00 00 00 command: 02 00 00 00 -# +# name: Mute type: parsed protocol: Samsung32 address: 07 00 00 00 command: D1 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 8A 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 8A 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 81 03 00 00 command: F0 0F 00 00 -# +# name: Power type: parsed protocol: NECext address: 81 03 00 00 command: F0 0F 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 9033 4255 562 543 564 1701 562 1674 563 567 565 541 564 567 565 567 537 1700 564 1700 537 568 564 1699 564 541 565 1699 565 540 565 567 565 570 535 567 565 567 565 1673 564 567 565 1673 564 567 565 540 618 514 565 1700 563 1674 564 567 564 1674 563 569 563 1672 565 1700 589 1648 564 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 9033 4255 562 543 564 1701 562 1674 563 567 565 541 564 567 565 567 537 1700 564 1700 537 568 564 1699 564 541 565 1699 565 540 565 567 565 570 535 567 565 567 565 1673 564 567 565 1673 564 567 565 540 618 514 565 1700 563 1674 564 567 564 1674 563 569 563 1672 565 1700 589 1648 564 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 9034 4255 564 567 564 1671 566 1699 564 540 565 567 565 540 564 566 566 1699 563 1672 567 565 566 1673 565 567 565 1672 565 566 566 539 565 567 565 1698 539 566 566 1698 564 541 565 565 567 1672 565 566 566 1672 565 567 565 1699 538 566 566 1698 564 1673 566 566 565 1672 566 566 566 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 9035 4254 565 566 565 1672 566 1699 564 539 567 566 566 540 564 567 565 1698 539 1698 566 566 564 1673 565 566 565 1672 565 566 566 539 566 566 566 567 563 541 565 566 566 538 566 566 566 1671 566 566 566 1697 565 1675 563 1699 563 1674 565 1699 538 1700 564 565 565 1674 564 567 565 -# +# name: Vol_dn type: raw frequency: 38000 From ffd0da145741cd0be71aaa8d94429f6f768c95c6 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:40:42 +0100 Subject: [PATCH 114/236] Update fans.ir Removed additional whitespaces --- .../resources/infrared/assets/fans.ir | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/fans.ir b/applications/main/infrared/resources/infrared/assets/fans.ir index 28e5fee1b8..d04e43b7de 100644 --- a/applications/main/infrared/resources/infrared/assets/fans.ir +++ b/applications/main/infrared/resources/infrared/assets/fans.ir @@ -1743,7 +1743,7 @@ type: parsed protocol: NEC address: 30 00 00 00 command: 86 00 00 00 -# +# name: Mode type: parsed protocol: NEC @@ -1989,115 +1989,115 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 8993 4485 589 1651 589 529 590 529 591 530 590 530 589 530 590 531 589 530 589 531 589 1649 590 1650 589 1649 590 1650 589 1651 588 1649 590 1650 589 1654 585 1650 589 1651 588 1650 590 533 586 531 588 532 588 530 590 530 590 532 587 531 588 530 589 1650 589 1650 589 1652 587 1651 588 1651 588 1650 589 1650 589 1652 587 530 590 530 589 530 589 531 588 531 588 531 588 530 589 531 588 1651 588 1650 590 1651 589 1651 589 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 2280 776 785 1565 783 796 782 790 783 1549 783 810 752 805 752 800 752 858 752 830 752 826 776 797 775 793 774 789 773 810 747 805 747 102605 2223 832 752 1595 753 825 752 820 752 1581 752 811 751 806 751 802 750 860 750 833 775 804 773 799 773 795 773 790 773 785 772 780 773 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4612 4435 543 1461 544 1460 545 1461 544 1460 543 1462 518 1487 518 2441 516 1513 491 1513 492 2465 492 1513 516 2441 516 1488 516 1489 515 2443 514 1492 514 1485 4580 4467 513 1492 513 1492 513 1492 513 1491 514 1492 513 1492 513 2445 513 1492 513 1492 513 2445 513 1492 513 2445 513 1492 513 1492 513 2445 513 1493 513 14064 9205 2275 513 -# +# name: Speed_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4611 4435 544 1460 545 1460 546 1459 546 1460 519 1487 517 1487 518 2440 518 1488 517 1488 516 1512 517 2440 517 1488 516 1488 516 1489 515 2444 513 2445 514 1485 4581 4467 513 1492 513 1492 513 1492 513 1492 513 1492 513 1492 513 2444 514 1492 513 1491 514 1492 513 2445 513 1492 513 1492 513 1492 513 2444 513 2446 513 14066 9203 2275 513 -# +# name: Speed_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4611 4434 545 1460 545 1459 546 1457 548 1458 521 1485 520 1485 545 2413 544 1462 542 2439 518 1487 517 1489 515 2444 512 1493 512 1493 512 1493 512 2446 512 1486 4581 4470 512 1493 511 1493 512 1493 512 1493 512 1493 512 1493 512 2446 511 1493 511 2446 512 1493 512 1493 512 2446 512 1493 512 1494 511 1493 512 2447 512 14071 9203 2279 511 -# +# name: Timer type: raw frequency: 38000 duty_cycle: 0.330000 data: 4608 4436 545 1458 547 1458 547 1458 547 1458 521 1484 521 1485 520 2439 544 1461 544 2440 518 1487 517 2441 516 1489 515 2444 513 1493 512 1492 513 1494 512 1486 4577 4471 512 1493 512 1493 512 1493 512 1492 513 1493 512 1493 512 2446 512 1493 512 2446 512 1493 512 2446 512 1493 512 2446 512 1493 512 1493 512 1494 512 14051 9198 2279 512 -# +# name: Rotate type: raw frequency: 38000 duty_cycle: 0.330000 data: 4605 4437 544 1460 545 1459 546 1458 546 1459 520 1485 520 1486 545 2414 543 1487 517 1488 515 1489 515 1491 513 2446 512 2446 511 2446 512 1493 512 1494 511 1487 4573 4472 511 1493 511 1493 511 1493 511 1493 512 1494 510 1494 511 2446 511 1493 511 1494 511 1494 510 1493 511 2447 511 2446 511 2446 511 1494 511 1495 511 14064 9191 2281 510 -# +# name: Mode type: raw frequency: 38000 duty_cycle: 0.330000 data: 4617 4436 544 1459 545 1458 545 1458 546 1458 520 1485 519 1485 545 2413 544 1462 542 1486 517 2440 515 2442 513 1492 512 1492 513 2444 512 1492 512 1493 513 1485 4582 4469 512 1493 512 1492 512 1492 512 1493 511 1493 512 1492 512 2445 511 1493 511 1493 511 2445 512 2446 511 1493 511 1493 511 2445 511 1493 511 1494 511 14048 9216 2280 512 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 9220 4459 632 565 628 539 628 539 627 540 626 542 624 545 622 548 619 571 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 1652 596 571 596 571 596 572 595 1652 596 571 596 571 596 571 596 572 595 1652 596 1652 596 1653 595 571 596 1652 596 39508 9197 2229 595 96159 9231 2229 596 -# +# name: Speed_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 9244 4432 633 564 629 538 630 538 629 539 627 540 626 542 624 570 598 547 620 1651 597 1651 597 1651 597 1651 597 1651 597 1651 597 1651 598 1651 597 1651 597 1651 597 1651 597 570 597 570 597 570 597 570 597 570 597 570 597 570 597 570 598 1651 597 1651 597 1651 597 1651 597 1652 596 39509 9207 2227 597 -# +# name: Speed_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 9223 4461 632 565 628 540 627 540 626 541 625 542 624 545 622 547 621 571 597 1629 620 1652 597 1653 595 1653 596 1653 596 1653 596 1653 596 1653 596 572 596 572 595 1653 596 1653 595 572 595 572 596 572 595 572 595 1653 596 1653 596 572 595 572 595 1653 596 1653 595 1653 596 1653 595 39519 9204 2229 596 -# +# name: Timer type: raw frequency: 38000 duty_cycle: 0.330000 data: 9227 4459 634 564 629 539 629 538 629 539 627 541 625 543 624 570 598 571 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 1652 597 571 597 571 596 1652 597 571 596 571 597 571 596 571 597 571 596 1652 597 1652 597 571 597 1652 597 1652 597 1653 596 1653 596 39523 9207 2229 596 -# +# name: Rotate type: raw frequency: 38000 duty_cycle: 0.330000 data: 9223 4459 633 564 629 538 629 538 628 539 627 541 624 544 622 570 597 570 597 1651 597 1651 597 1651 597 1651 598 1651 597 1651 597 1651 597 1651 597 570 597 1651 597 1651 597 1651 597 1651 597 570 598 1651 597 570 597 1651 597 570 597 570 597 570 597 570 597 1652 596 570 597 1652 596 39519 9202 2226 597 -# +# name: Mode type: raw frequency: 38000 duty_cycle: 0.330000 data: 9241 4434 633 565 602 565 628 539 628 539 627 540 626 542 624 545 622 547 620 1628 620 1629 619 1629 619 1652 596 1653 595 1629 619 1652 596 1653 595 571 596 1653 595 571 596 571 596 572 595 571 596 1653 595 572 595 1653 595 571 596 1653 595 1653 595 1653 595 1653 596 572 595 1653 595 39519 9226 2229 597 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.4 data: 20592 6864 2288 9152 11440 2288 2288 6864 2288 2288 2288 13728 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1328 375 1304 375 460 1196 1327 379 1299 379 430 1221 459 1221 458 1222 483 1197 483 1198 481 1222 1299 7068 1297 382 1297 382 453 1227 1296 382 1297 382 453 1227 453 1228 452 1228 452 1227 453 1228 452 1228 1296 7322 1295 383 1296 383 452 1228 1296 383 1296 383 452 1228 452 1228 452 1228 452 1228 452 1228 452 1228 1296 7070 1295 383 1296 384 451 1228 1296 383 1296 383 452 1228 452 1229 451 1228 451 1229 451 1228 451 1229 1295 -# +# name: Speed_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1327 373 1306 377 459 1194 1329 373 1305 377 1276 402 433 1219 486 1194 486 1195 484 1219 1302 379 1299 7067 1299 380 1299 381 455 1224 1299 381 1298 381 1298 381 455 1225 455 1225 455 1225 455 1225 1298 381 1298 7344 1298 381 1299 381 455 1225 1298 381 1298 381 1298 381 455 1225 455 1225 455 1225 455 1226 1298 381 1298 7069 1298 382 1298 382 454 1225 1298 382 1298 382 1298 382 454 1226 454 1226 454 1226 454 1226 1297 382 1297 -# +# name: Speed_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 1330 377 1302 377 459 1193 1330 376 1303 376 1277 401 435 1218 487 1194 486 1194 1329 377 1299 380 456 7911 1299 380 1299 380 456 1225 1299 380 1299 380 1299 380 456 1224 456 1225 455 1225 1298 381 1299 380 456 8149 1299 381 1298 381 455 1225 1299 380 1299 381 1299 381 455 1225 455 1225 455 1226 1298 381 1299 381 455 7913 1298 381 1299 381 455 1226 1298 381 1299 381 1298 382 454 1225 455 1226 454 1226 1298 382 1298 381 455 -# +# name: Rotate type: raw frequency: 38000 duty_cycle: 0.330000 data: 1329 373 1306 377 459 1193 1330 377 1302 377 1277 402 434 1219 485 1195 1329 377 1300 379 456 1224 455 7911 1299 381 1298 381 455 1224 1299 380 1299 381 1298 381 455 1225 455 1225 1299 380 1299 381 455 1225 455 8165 1298 381 1299 381 455 1225 1299 381 1299 381 1298 381 455 1225 455 1226 1298 381 1299 381 455 1225 455 7913 1298 381 1298 381 455 1226 1297 381 1298 382 1297 382 454 1226 454 1226 1297 382 1298 382 454 1226 454 -# +# name: Timer type: raw frequency: 38000 @@ -2139,67 +2139,67 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 2221 844 752 1600 758 1591 757 820 756 816 749 817 748 813 752 804 751 1632 758 1596 752 1597 751 1594 754 1585 752 1582 756 806 749 808 757 102464 2224 841 756 1597 751 1599 749 828 748 824 752 815 750 811 754 802 753 1629 750 1604 754 1595 753 1591 757 1583 755 1579 759 804 751 806 749 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1261 425 1261 425 444 1241 1261 425 1261 454 414 1241 445 1240 445 1241 444 1241 444 1242 443 1239 1263 7168 1261 424 1262 425 444 1241 1261 424 1261 425 444 1242 444 1240 445 1241 444 1240 445 1240 445 1241 1261 -# +# name: Speed_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1261 424 1261 424 445 1241 1261 425 1260 424 445 1242 444 1240 445 1240 445 1239 446 1241 1261 424 445 7984 1261 424 1261 424 445 1241 1261 424 1261 424 445 1240 446 1240 445 1240 445 1241 444 1240 1262 425 444 7983 1262 424 1261 424 445 1239 1263 424 1261 424 445 1241 445 1240 445 1240 445 1239 446 1240 1262 425 444 7983 1262 424 1261 425 444 1239 1263 425 1260 425 443 1241 445 1241 444 1241 444 1241 445 1240 1262 424 445 7982 1263 425 1260 424 445 1240 1262 424 1261 424 445 1240 446 1241 444 1241 444 1239 446 1239 1263 425 444 7984 1261 424 1261 424 444 1240 1262 425 1260 424 445 1240 445 1241 444 1240 445 1240 445 1241 1261 424 444 7984 1261 425 1261 424 444 1241 1261 424 1261 425 444 1240 445 1240 445 1240 445 1240 445 1240 1262 424 445 7984 1261 425 1260 425 444 1240 1262 425 1260 425 444 1241 445 1240 445 1240 445 1240 445 1240 1262 425 444 7984 1261 425 1260 426 443 1241 1261 425 1260 426 442 1240 446 1241 444 1241 444 1242 443 1240 1262 426 443 7985 1260 426 1259 426 442 1243 1259 426 1259 426 442 1241 445 1241 444 1240 445 1241 444 1241 1261 425 443 7984 1261 425 1260 425 443 1242 1261 426 1259 426 442 1242 444 1241 444 1241 444 1241 444 1241 1261 425 443 7986 1260 425 1260 425 444 1241 1261 426 1259 427 442 1242 444 1240 445 1241 444 1240 445 1240 1262 425 444 -# +# name: Rotate type: raw frequency: 38000 duty_cycle: 0.330000 data: 1261 425 1260 424 445 1241 1261 426 1260 424 445 1241 445 1240 1262 426 443 1240 446 1240 445 1241 444 7985 1260 424 1261 426 443 1241 1261 425 1260 426 443 1240 446 1240 1261 425 444 1241 445 1241 444 1241 444 -# +# name: Timer type: raw frequency: 38000 duty_cycle: 0.330000 data: 1261 425 1260 426 442 1241 1262 426 1259 425 444 1241 445 1241 444 1240 1262 425 443 1241 445 1241 444 7983 1262 425 1260 425 444 1241 1261 425 1260 425 444 1241 445 1240 445 1240 1262 425 444 1240 446 1240 445 7985 1260 424 1261 425 444 1241 1261 425 1260 425 444 1239 447 1240 445 1240 1262 425 444 1241 444 1241 444 7983 1262 425 1260 424 445 1241 1261 424 1261 424 445 1241 445 1240 445 1240 1262 424 445 1240 446 1241 444 7983 1262 425 1260 425 444 1240 1262 425 1260 424 445 1240 446 1240 445 1241 1261 425 444 1240 446 1240 445 7985 1260 424 1261 425 444 1241 1261 424 1261 425 444 1240 446 1239 446 1240 1262 426 443 1240 446 1241 444 7984 1261 424 1261 424 445 1241 1261 425 1260 425 444 1241 445 1240 445 1239 1263 424 445 1240 446 1240 445 7984 1261 424 1261 425 444 1240 1262 424 1261 425 444 1241 444 1241 444 1240 1262 425 444 1242 444 1239 446 7983 1262 424 1261 426 443 1240 1262 426 1259 425 444 1242 444 1241 444 1239 1263 425 444 1241 444 1241 445 7983 1262 424 1261 424 445 1240 1262 425 1260 425 444 1241 445 1240 445 1241 1261 425 444 1241 445 1239 446 7983 1262 425 1260 425 444 1240 1262 425 1260 425 444 1242 443 1239 446 1241 1261 425 444 1241 444 1241 444 7983 1262 425 1260 426 443 1241 1261 426 1259 425 443 1241 445 1241 444 1242 1260 426 442 1241 445 1241 444 7984 1261 425 1260 426 494 1190 1260 426 1259 425 496 1189 497 1188 497 1188 1262 427 494 1190 496 1189 496 7932 1261 426 1259 426 494 1190 1260 426 1259 425 495 1190 496 1189 496 1190 1260 428 492 1190 496 1189 496 -# +# name: Mode type: raw frequency: 38000 duty_cycle: 0.330000 data: 1259 425 1260 454 415 1244 1259 426 1259 425 444 1245 441 1241 444 1241 444 1241 1261 454 415 1242 444 7984 1261 425 1261 426 443 1243 1259 426 1260 427 442 1241 445 1240 445 1242 443 1242 1260 427 442 1242 443 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1292 432 1293 433 444 1244 1296 458 1266 459 418 1245 449 1244 449 1245 448 1244 448 1243 449 1245 1293 7239 1292 458 1263 458 418 1245 1292 432 1289 433 443 1244 448 1245 447 1244 448 1245 446 1244 447 1245 1290 8319 1290 432 1287 432 443 1244 1291 458 1261 458 417 1244 448 1245 446 1244 447 1244 447 1244 447 1245 1289 7239 1289 458 1260 458 417 1245 1289 458 1260 458 417 1245 447 1244 447 1244 446 1244 446 1244 446 1245 1287 8320 1287 458 1259 432 443 1245 1288 431 1286 432 443 1246 445 1244 446 1244 446 1243 447 1245 445 1244 1287 7237 1287 458 1258 458 416 1245 1287 431 1285 458 416 1244 447 1244 446 1244 446 1244 446 1244 446 1244 1287 8318 1286 458 1258 433 441 1245 1287 431 1285 458 416 1244 446 1243 447 1245 445 1243 446 1244 445 1244 1286 7237 1286 432 1284 458 416 1244 1287 432 1284 431 443 1245 445 1243 446 1244 445 1243 446 1244 445 1244 1286 8317 1285 431 1284 432 441 1244 1287 431 1285 431 443 1245 445 1244 445 1243 446 1243 446 1244 445 1244 1286 7236 1286 458 1257 431 443 1244 1287 431 1285 432 442 1245 445 1244 445 1244 446 1244 445 1244 446 1244 1287 8316 1287 458 1259 432 442 1245 1288 432 1285 432 442 1245 446 1245 445 1244 446 1244 446 1244 446 1243 1289 7236 1287 431 1286 432 442 1244 1289 431 1286 433 441 1245 446 1244 446 1244 446 1244 446 1244 446 1245 1286 8317 1286 432 1284 432 442 1245 1287 458 1259 432 442 1245 446 1244 446 1244 446 1244 446 1244 446 1244 1287 7237 1286 431 1285 431 443 1245 1287 433 1284 458 416 1245 445 1245 445 1243 446 1244 446 1244 445 1244 1287 8317 1286 432 1284 458 416 1245 1287 431 1285 432 442 1245 445 1244 445 1244 445 1243 446 1244 445 1244 1286 7235 1286 433 1282 431 442 1244 1287 431 1284 432 441 1245 445 1244 445 1244 445 1244 445 1244 445 1244 1286 8316 1285 431 1284 431 442 1244 1287 431 1284 433 440 1244 446 1244 445 1243 446 1244 445 1244 445 1243 1286 7236 1284 431 1283 431 442 1244 1286 431 1284 431 442 1245 445 1244 445 1244 445 1244 445 1244 445 1244 1286 8315 1285 431 1283 431 442 1245 1285 431 1284 432 441 1244 446 1244 445 1244 445 1244 445 1244 445 1244 1285 7235 1285 431 1283 430 443 1244 1286 431 1284 430 443 1245 445 1244 445 1244 445 1244 445 1244 445 1243 1286 8316 1284 457 1257 430 443 1244 1286 432 1283 431 442 1245 445 1244 445 1243 446 1243 446 1244 445 1243 1286 7235 1285 431 1283 432 441 1244 1286 431 1284 431 442 1245 445 1244 445 1244 445 1244 445 1244 445 1244 1285 8316 1284 431 1283 431 442 1245 1285 430 1285 430 443 1245 445 1243 446 1244 445 1244 445 1244 445 1244 1285 7235 1285 432 1282 431 442 1245 1285 432 1282 431 442 1245 445 1244 445 1243 446 1244 445 1244 445 1244 1285 8314 1285 431 1283 431 442 1245 1285 430 1284 431 442 1244 446 1244 445 1244 445 1244 445 1244 445 1244 1285 7235 1284 431 1283 431 442 1245 1285 431 1283 431 442 1244 446 1244 445 1244 445 1244 445 1244 445 1244 1285 8316 1284 431 1283 431 442 1245 1285 431 1283 430 443 1245 445 1244 445 1244 445 1244 445 1244 445 1243 1286 7236 1285 430 1285 431 442 1245 1287 431 1285 431 443 1245 446 1244 446 1244 446 1244 445 1244 446 1244 1285 8319 1292 432 1291 432 444 1246 1291 432 1292 432 444 1246 447 1245 447 1245 447 1245 447 1245 447 1245 1296 7239 1288 430 1287 431 443 1246 1288 431 1287 431 443 1246 446 1244 447 1245 446 1244 447 1245 445 1245 1288 8319 1287 431 1285 431 443 1245 1288 431 1285 431 442 1245 446 1244 446 1245 445 1245 445 1245 445 1245 1287 7237 1288 431 1286 431 443 1245 1291 431 1290 431 444 1246 446 1244 447 1245 446 1244 447 1244 446 1244 1288 -# +# name: Speed_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1290 458 1263 432 444 1245 1292 458 1263 433 443 1245 472 1221 448 1244 448 1245 447 1244 1292 433 443 8060 1289 458 1261 458 417 1245 1291 458 1261 458 418 1245 448 1244 472 1220 471 1220 448 1244 1290 431 444 9141 1290 458 1261 458 417 1245 1290 458 1261 432 444 1245 470 1220 472 1220 447 1244 447 1243 1290 457 418 8059 1288 457 1260 458 417 1245 1289 458 1260 431 444 1245 471 1220 471 1219 471 1220 448 1244 1288 457 417 9140 1287 432 1285 458 416 1244 1289 457 1260 431 444 1243 448 1244 471 1220 446 1244 471 1220 1288 430 445 8057 1289 431 1285 457 417 1244 1289 457 1259 458 416 1245 470 1220 471 1220 446 1244 447 1243 1288 432 442 9139 1287 457 1259 431 443 1244 1288 457 1259 432 443 1244 447 1243 447 1244 446 1243 472 1219 1288 457 417 8058 1287 457 1259 430 444 1245 1287 431 1285 457 417 1245 446 1244 447 1244 446 1244 446 1244 1287 457 417 9139 1287 457 1258 457 417 1244 1287 457 1259 432 442 1245 446 1243 447 1244 447 1244 446 1243 1288 431 443 8057 1287 431 1284 431 443 1245 1287 431 1285 431 443 1244 447 1243 447 1243 447 1244 446 1243 1287 431 443 9139 1286 431 1284 457 417 1245 1287 430 1286 457 417 1245 446 1243 447 1243 447 1243 447 1244 1287 431 443 8057 1287 430 1285 457 417 1243 1289 431 1284 457 417 1244 447 1244 446 1243 447 1243 447 1243 1287 431 443 9136 1286 431 1284 457 417 1244 1287 430 1285 457 417 1244 447 1243 447 1244 446 1243 447 1243 1287 430 444 8054 1286 430 1284 431 443 1244 1286 430 1285 431 443 1244 447 1243 447 1243 447 1243 447 1243 1287 431 443 -# +# name: Rotate type: raw frequency: 38000 duty_cycle: 0.330000 data: 1304 432 1273 432 443 1214 1304 433 1273 432 443 1213 475 1214 1304 432 443 1212 476 1215 472 1212 475 8025 1304 432 1273 432 443 1212 1306 432 1274 432 443 1215 473 1213 1304 432 443 1213 474 1212 475 1212 475 9100 1305 432 1273 432 443 1213 1305 432 1274 433 442 1214 474 1212 1305 432 443 1212 476 1213 474 1214 473 8026 1302 432 1273 433 442 1213 1305 432 1274 432 443 1213 475 1213 1304 432 443 1213 474 1213 474 1212 475 -# +# name: Timer type: raw frequency: 38000 duty_cycle: 0.330000 data: 1304 432 1276 404 472 1213 1307 404 1304 405 470 1214 473 1215 472 1212 1333 378 471 1214 526 1160 527 7968 1330 378 1329 378 523 1163 1329 379 1328 379 522 1163 524 1164 523 1163 1304 404 521 1165 522 1163 523 9058 1303 404 1302 405 519 1165 1304 405 1302 405 519 1166 521 1164 523 1165 1303 406 518 1166 521 1165 522 7973 1302 404 1302 405 519 1165 1304 404 1303 403 522 1165 522 1164 522 1164 1304 404 521 1164 523 1165 521 9062 1301 404 1302 405 468 1215 1304 404 1302 406 467 1216 471 1216 470 1216 1303 406 467 1218 469 1217 469 8025 1278 430 1276 430 440 1244 1278 429 1277 456 414 1245 442 1244 442 1244 1278 430 439 1244 443 1243 443 -# +# name: Mode type: raw frequency: 38000 duty_cycle: 0.330000 data: 1307 432 1277 432 444 1213 1309 432 1278 433 443 1213 476 1213 475 1212 476 1213 1308 432 444 1213 476 8021 1309 432 1277 432 444 1213 1309 432 1278 433 443 1212 476 1212 476 1212 476 1212 1309 432 444 1213 475 9107 1307 432 1277 432 444 1213 1308 432 1277 433 443 1212 476 1211 477 1212 476 1211 1310 432 444 1213 476 8022 1308 432 1277 432 444 1212 1309 432 1278 432 444 1212 476 1211 477 1212 476 1212 1308 432 444 1212 476 9107 1307 431 1305 405 497 1162 1333 379 1331 405 497 1163 525 1162 525 1164 523 1163 1308 432 442 1217 471 8026 1283 430 1279 430 442 1244 1281 431 1279 431 440 1244 445 1244 444 1243 444 1243 1282 430 441 1245 444 -# +# name: Power type: raw frequency: 38000 From f53a0d29beb88cc71c12c1ba55889dec0aa045b9 Mon Sep 17 00:00:00 2001 From: amec0e <88857687+amec0e@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:41:07 +0100 Subject: [PATCH 115/236] Update audio.ir Removed additional whitespaces --- .../resources/infrared/assets/audio.ir | 1300 ++++++++--------- 1 file changed, 650 insertions(+), 650 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index 76a3b80c78..a42dfd9c11 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -2,3661 +2,3661 @@ Filetype: IR library file Version: 1 # Last Updated 5th Oct, 2024 # Last Checked 5th Oct, 2024 -# +# name: Power type: parsed protocol: NEC address: 77 00 00 00 command: F1 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 77 00 00 00 command: F3 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 77 00 00 00 command: FB 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 77 00 00 00 command: FC 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 80 00 00 00 command: 1A 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 10 E7 00 00 command: 46 B9 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 10 E7 00 00 command: 06 F9 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 10 E7 00 00 command: 47 B8 00 00 -# +# name: Mute type: parsed protocol: NECext address: 10 E7 00 00 command: 41 BE 00 00 -# +# name: Power type: parsed protocol: RC5 address: 10 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: RC5 address: 10 00 00 00 command: 0D 00 00 00 -# +# name: Vol_up type: parsed protocol: RC5 address: 10 00 00 00 command: 10 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC5 address: 10 00 00 00 command: 11 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 40 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 41 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 45 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 48 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 2D D3 00 00 command: 12 ED 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 2D D3 00 00 command: 11 EE 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 2D D3 00 00 command: 10 EF 00 00 -# +# name: Mute type: parsed protocol: NECext address: 2D D3 00 00 command: 13 EC 00 00 -# +# name: Power type: parsed protocol: SIRC15 address: 44 00 00 00 command: 15 00 00 00 -# +# name: Vol_up type: parsed protocol: SIRC15 address: 44 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: SIRC15 address: 44 00 00 00 command: 13 00 00 00 -# +# name: Mute type: parsed protocol: SIRC15 address: 44 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 02 A0 00 00 command: 80 7F 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 02 A0 00 00 command: AA 55 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 02 A0 00 00 command: 6A 95 00 00 -# +# name: Mute type: parsed protocol: NECext address: 02 A0 00 00 command: EA 15 00 00 -# +# name: Power type: parsed protocol: NEC address: 04 00 00 00 command: 16 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 04 00 00 00 command: 13 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 04 00 00 00 command: 0B 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 04 00 00 00 command: 06 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 29 A1 00 00 command: 7F 80 00 00 -# +# name: Mute type: parsed protocol: NECext address: 29 A1 00 00 command: 9B 64 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 29 A1 00 00 command: 9E 61 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 29 A1 00 00 command: 9F 60 00 00 -# +# name: Power type: parsed protocol: NEC address: 7A 00 00 00 command: 1F 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 1C 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 0B 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 0F 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 05 00 00 00 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4617 4406 584 448 557 448 557 449 556 449 555 1430 580 1432 577 452 552 454 550 1460 549 1462 548 1462 549 1462 548 457 548 457 548 457 548 457 548 4463 548 457 548 457 548 457 548 458 548 1462 548 1462 548 1462 548 457 548 1463 547 1462 548 1462 548 458 547 458 548 458 548 458 547 1463 547 458 547 458 547 458 548 1463 547 55451 4606 4440 549 457 548 457 548 457 548 457 548 1462 548 1462 548 457 548 457 548 1462 548 1461 549 1462 548 1462 548 457 548 457 548 457 548 457 548 4462 548 457 548 457 548 457 548 457 548 1462 548 1462 548 1462 548 457 548 1462 548 1462 548 1462 547 457 548 458 547 458 547 458 547 1462 548 458 547 458 547 458 547 1462 548 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4614 4408 583 449 555 450 555 451 553 451 554 1432 577 1434 576 453 551 454 550 1460 549 1461 549 1461 549 1461 549 457 548 457 548 457 548 457 548 4461 548 457 548 457 548 457 548 457 548 457 548 457 548 457 548 1462 548 1461 549 1461 549 1461 548 457 548 1462 549 1461 549 1461 548 457 548 457 548 457 548 458 547 1462 548 55443 4606 4440 549 456 549 456 549 456 549 456 549 1461 549 1461 549 457 548 457 548 1461 549 1461 549 1461 549 1461 549 456 549 457 548 457 548 457 548 4461 549 456 549 457 548 457 548 457 548 457 548 457 548 457 548 1462 548 1461 549 1461 549 1461 548 457 548 1461 549 1461 549 1461 549 457 548 457 548 458 547 457 548 1462 548 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4588 4435 556 477 556 449 555 450 555 451 554 1428 582 1429 581 451 553 452 552 1457 552 1458 551 1461 548 1463 547 458 547 458 547 458 547 458 547 4464 547 458 547 458 547 458 547 458 548 458 547 458 547 458 548 458 547 1463 547 1463 547 1464 546 459 547 1464 546 1464 546 1463 547 1464 546 459 546 459 546 459 546 1464 547 55456 4581 4468 546 458 547 458 547 458 547 458 547 1463 547 1463 547 458 547 459 547 1463 547 1464 546 1464 546 1464 547 459 546 459 546 459 546 459 547 4465 546 459 546 459 546 459 546 459 546 459 547 459 546 459 546 459 546 1464 546 1464 546 1465 546 460 545 1465 545 1465 545 1465 546 1465 546 460 545 460 546 460 545 1466 544 -# +# name: Power type: parsed protocol: NECext address: 3F 5C 00 00 command: 18 E7 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 3F 5C 00 00 command: 55 AA 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 3F 5C 00 00 command: 59 A6 00 00 -# +# name: Mute type: parsed protocol: NECext address: 3F 5C 00 00 command: 15 EA 00 00 -# +# name: Mute type: parsed protocol: NECext address: 00 FB 00 00 command: 1E E1 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 FB 00 00 command: 1D E2 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 00 FB 00 00 command: 18 E7 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 00 FB 00 00 command: 0A F5 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 80 D9 00 00 command: 8A 75 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 80 D9 00 00 command: 88 77 00 00 -# +# name: Mute type: parsed protocol: NECext address: 80 D9 00 00 command: 8C 73 00 00 -# +# name: Power type: parsed protocol: NECext address: 78 0E 00 00 command: 00 FF 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 78 0E 00 00 command: 19 E6 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 78 0E 00 00 command: 1C E3 00 00 -# +# name: Mute type: parsed protocol: NECext address: 78 0E 00 00 command: 18 E7 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 20 00 00 00 command: 14 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 20 00 00 00 command: 10 00 00 00 -# +# name: Power type: parsed protocol: RC5 address: 10 00 00 00 command: 0E 00 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 10 E7 00 00 command: 0C F3 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 10 E7 00 00 command: 09 F6 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 3309 1906 410 1178 411 1177 412 416 435 445 406 448 414 440 411 1150 439 415 436 1178 411 1177 412 442 409 444 407 1181 408 419 432 1182 407 420 442 1146 433 448 414 440 411 442 409 444 407 446 416 438 413 441 410 443 408 419 432 1182 407 446 405 422 440 414 437 416 435 445 406 1181 408 446 405 448 414 440 411 442 409 418 433 446 416 438 413 1175 414 413 438 1176 413 414 437 443 408 419 432 421 441 413 438 42493 3308 3343 355 43011 3309 3316 382 43009 3310 3314 384 43007 3303 3347 361 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 3307 1879 437 1177 412 1176 413 415 436 417 434 446 405 448 414 1174 415 412 439 1149 440 1147 442 438 413 440 411 1177 412 441 410 1178 411 442 409 1179 410 417 434 419 432 448 414 440 411 442 409 444 407 446 416 438 413 441 410 1151 438 415 436 444 407 446 416 412 439 414 437 1177 412 1176 413 414 437 416 435 1152 437 1177 412 416 435 444 407 446 416 1146 433 421 441 1173 406 422 440 413 438 442 409 444 407 40133 3308 3341 357 42862 3301 3347 361 42859 3304 3319 379 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 3310 1876 440 1174 415 1173 405 422 440 414 437 443 408 445 406 1182 407 420 442 1173 406 1182 407 420 442 412 439 1175 414 440 411 1150 439 415 436 1152 437 416 435 419 432 447 415 440 411 442 409 418 433 420 442 438 413 440 411 1177 412 415 436 418 433 420 442 438 413 441 410 1151 438 1150 439 415 436 1178 411 1150 439 1149 440 414 437 417 434 445 406 1155 434 420 442 438 413 1175 414 413 438 442 409 444 407 39503 3303 3320 388 42857 3304 3319 379 42867 3305 3317 381 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 3305 1907 409 1178 411 1177 412 442 409 418 433 447 415 439 412 1176 413 441 410 1177 412 1176 413 441 410 443 408 1180 409 445 406 1181 408 446 416 1172 406 448 413 440 411 442 409 445 406 447 414 439 412 442 409 444 407 447 414 1173 405 449 413 441 410 443 408 446 405 448 413 1174 415 440 411 442 409 1178 411 1177 412 1177 412 442 409 444 407 447 415 439 412 441 410 444 407 1180 409 445 406 448 414 440 411 41125 3303 3347 360 42906 3308 3315 382 -# +# name: Mute type: parsed protocol: NECext address: BA A0 00 00 command: 01 FE 00 00 -# +# name: Power type: parsed protocol: Kaseikyo address: AC 02 20 00 command: D1 03 00 00 -# +# name: Vol_up type: parsed protocol: Kaseikyo address: A0 02 20 00 command: 00 02 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo address: A0 02 20 00 command: 10 02 00 00 -# +# name: Mute type: parsed protocol: Kaseikyo address: A0 02 20 00 command: 20 03 00 00 -# +# name: Power type: parsed protocol: NEC address: 01 00 00 00 command: 02 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 01 00 00 00 command: 01 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 01 00 00 00 command: 0B 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 01 00 00 00 command: 06 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 9150 4435 643 1608 643 468 644 469 642 364 749 468 643 447 665 449 663 469 643 452 660 470 642 450 662 442 670 449 662 469 643 1579 672 1608 642 1580 671 1609 641 1607 643 1578 672 1607 643 1608 642 1606 644 1606 644 1606 644 1607 643 1576 675 1579 671 1605 674 438 645 466 673 438 646 466 674 437 673 439 672 439 673 438 646 1604 673 1577 673 1578 673 1577 674 1577 673 23799 9095 4485 616 -# +# name: Vol_up type: parsed protocol: NEC42 address: 01 00 00 00 command: 0C 00 00 00 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 9151 4434 644 1608 643 376 737 379 733 446 666 449 663 468 644 469 643 468 644 468 644 468 644 447 665 448 664 468 644 450 662 1608 643 1607 644 1576 676 1607 644 1608 643 1578 674 1608 643 1577 674 1579 672 1607 643 1608 643 1607 644 1607 644 1608 643 448 664 1608 643 448 664 468 644 469 643 380 732 468 644 469 643 1607 644 468 644 1608 643 1608 644 1609 643 1608 643 23837 9152 4434 642 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8968 4344 670 460 670 460 670 1566 669 462 668 486 643 487 642 489 641 1595 640 490 640 491 640 1596 640 491 640 1596 640 1596 640 1596 640 491 640 1596 640 1596 640 1596 640 1596 640 1596 640 1596 640 1596 640 1622 640 491 640 491 640 491 640 491 640 491 640 491 640 491 640 491 639 -# +# name: Power type: parsed protocol: SIRC address: 10 00 00 00 command: 2E 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 FD 00 00 command: 01 FE 00 00 -# +# name: Mute type: parsed protocol: NECext address: 00 FD 00 00 command: 03 FC 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 00 FD 00 00 command: 09 F6 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 00 FD 00 00 command: 07 F8 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1042 1461 540 1460 541 1460 541 1460 541 1459 542 1459 542 454 536 460 540 456 544 452 538 458 542 454 546 450 540 456 544 1457 544 1456 545 1448 542 50531 1041 1462 539 1462 539 1461 540 1461 540 1460 541 1460 541 455 545 451 539 457 543 480 510 459 541 481 519 451 539 457 543 1457 543 1457 544 1449 541 50515 1037 1467 544 1456 545 1456 545 1455 546 1455 535 1465 536 486 514 483 517 479 511 485 515 481 509 487 513 483 517 478 512 1462 539 1462 539 1454 536 50537 1035 1467 544 1457 544 1457 544 1456 545 1456 544 1456 545 477 513 483 517 479 511 486 514 481 519 477 513 484 516 479 511 1464 536 1463 538 1455 546 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1010 1491 509 1490 510 1488 512 487 513 487 513 1486 514 1485 515 484 516 1483 517 482 518 482 518 1481 509 1489 511 488 512 487 513 1486 514 484 516 50963 1011 1489 511 1488 512 1487 513 485 515 486 514 1485 515 1484 516 484 516 1483 517 482 518 482 518 1481 509 1489 511 489 511 489 511 1487 513 486 514 50986 1008 1492 518 1480 510 1488 512 487 513 487 513 1486 514 1484 516 484 516 1483 517 482 518 481 519 1480 510 1488 512 487 513 487 513 1486 514 484 516 50972 1012 1488 512 1486 514 1484 516 483 517 483 517 1482 518 1480 510 489 511 1487 513 486 514 486 514 1485 515 1483 517 483 517 483 517 1481 509 490 510 50976 1008 1491 509 1489 511 1487 513 485 515 485 515 1484 516 1481 509 491 509 1489 511 488 512 488 512 1487 513 1485 515 484 516 484 516 1483 517 481 509 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1011 1479 517 477 516 481 512 1480 516 1473 513 482 511 485 518 1473 513 482 511 1481 515 1474 512 482 511 485 518 1473 513 1477 509 486 517 1473 513 50728 1014 1475 511 484 509 487 516 1475 511 1479 517 477 516 480 513 1478 508 487 516 1475 511 1479 517 478 514 480 513 1479 517 1473 513 508 484 1479 517 50725 1016 1473 513 481 512 484 519 1473 513 1477 509 485 518 478 515 1476 510 485 518 1473 513 1477 509 486 517 478 515 1476 510 1480 516 479 514 1476 510 50735 1069 1421 513 481 512 484 509 1483 513 1477 509 486 517 479 514 1477 509 486 517 1474 512 1479 507 488 515 480 513 1479 507 1483 513 482 511 1479 517 50733 1011 1478 508 513 490 506 486 1478 508 1483 513 508 485 511 482 1482 514 508 485 1480 516 1473 513 508 485 511 482 1483 513 1477 509 512 491 1473 513 50735 1008 1480 516 479 514 508 485 1480 516 1474 512 509 484 486 517 1472 514 508 485 1480 516 1474 512 509 484 512 481 1482 514 1477 509 512 491 1472 514 50738 1006 1509 487 508 485 486 507 1509 487 1503 483 513 490 505 488 1502 484 512 491 1473 513 1503 483 512 491 505 488 1476 510 1507 489 506 487 1503 483 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 1010 1479 517 1474 512 509 484 512 491 505 488 507 486 510 483 512 491 505 488 508 485 1505 491 1474 512 1478 508 1483 513 1477 509 1482 514 1475 511 50713 1005 1483 513 1475 511 511 482 513 490 505 488 507 486 483 510 511 482 487 516 505 488 1501 485 1478 508 1483 513 1476 510 1479 507 1483 513 1474 512 50707 1012 1501 485 1479 506 513 490 505 488 507 486 509 484 511 482 487 516 505 488 508 485 1504 482 1482 514 1476 592 1396 590 1400 513 1476 510 1478 508 50715 1015 1473 513 1476 510 484 508 513 490 504 489 480 513 508 485 483 510 511 482 488 515 1473 513 1477 509 1480 516 1474 512 1477 591 1397 516 1472 514 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1041 1462 538 1462 538 485 515 481 519 478 512 484 516 1458 542 1458 542 1459 541 481 519 1481 519 1455 545 1482 518 1456 544 479 511 486 514 474 516 50532 1039 1464 536 1490 510 487 513 483 517 480 510 486 514 1460 540 1486 514 1460 540 483 517 1457 543 1484 516 1458 542 1458 542 481 519 477 513 476 513 50534 1036 1467 543 1457 543 480 509 460 540 483 517 479 511 1463 537 1463 537 1464 536 486 514 1487 513 1461 539 1461 539 1462 538 484 516 481 519 469 510 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 1042 1461 539 457 543 1458 542 1458 542 1458 542 1458 542 454 536 461 539 457 543 1457 543 453 537 460 540 456 544 452 538 1463 537 1463 537 1456 544 50530 1065 1438 572 424 566 1434 566 1435 565 1435 565 1436 544 452 537 459 572 424 545 1456 544 451 539 458 542 454 536 460 540 1461 539 1461 539 1454 536 50538 1036 1467 543 452 537 1464 536 1464 536 1464 536 1465 545 450 540 456 544 452 537 1464 536 460 540 455 545 452 538 458 542 1459 541 1460 540 1452 538 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1044 1456 544 455 545 455 534 1464 536 1462 538 1460 540 1458 542 1456 544 1455 545 1453 537 1461 539 460 540 459 541 459 561 437 542 457 543 456 544 50915 1016 1483 517 482 518 482 518 1481 509 1489 511 1487 513 1486 514 1485 515 1483 517 1482 508 1490 510 489 511 488 512 487 513 487 513 486 514 485 515 50956 1047 1452 538 462 538 462 538 1461 539 1460 540 1458 542 1457 543 1456 544 1454 546 1453 547 1451 539 461 539 460 540 460 540 459 541 459 541 457 543 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 1017 1484 516 1482 518 481 519 1480 510 1489 511 1487 513 1486 514 1485 515 1483 517 482 518 1480 510 490 510 489 511 489 511 488 512 488 512 486 514 50956 1015 1486 514 1484 516 484 516 1483 517 1482 518 1480 510 1489 511 1488 512 1486 514 486 514 1485 515 484 516 484 516 483 517 483 517 482 518 481 509 50960 1011 1488 512 1486 514 486 514 1485 515 1483 517 1482 518 1480 510 1488 512 1486 514 486 514 1484 516 483 517 483 517 482 518 481 519 481 508 489 511 50961 1040 1461 539 1459 541 459 541 1458 542 1456 544 1455 545 1454 546 1452 538 1460 540 460 540 1458 542 457 543 456 544 456 544 455 545 455 545 453 547 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1006 1485 511 1479 517 1473 513 509 484 511 482 514 489 506 487 508 485 511 482 513 490 505 488 1477 509 1481 515 1475 511 1480 516 1474 512 1477 509 50734 1007 1483 513 1477 509 1482 514 507 486 509 484 511 492 503 490 506 487 508 485 510 483 513 490 1474 512 1479 517 1473 513 1477 509 1481 515 1474 512 50729 1012 1477 509 1481 515 1474 512 509 484 512 491 504 489 506 487 508 485 511 482 513 490 506 487 1477 509 1481 515 1475 511 1479 517 1473 513 1476 510 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 1012 1503 483 514 489 1475 511 483 510 486 517 478 515 481 512 483 510 486 517 1473 513 508 485 1480 516 1475 511 1479 507 1483 513 1477 509 1480 588 50646 1012 1476 510 512 491 1473 513 508 485 485 508 488 515 480 513 482 511 512 481 1482 514 482 511 1480 516 1473 513 1478 508 1482 514 1476 510 1479 590 50651 1007 1507 489 507 485 1478 508 514 489 506 487 509 484 485 508 488 515 506 487 1503 483 513 490 1474 512 1478 508 1483 513 1477 509 1481 515 1473 513 50721 1009 1505 491 478 515 1475 511 510 483 486 517 478 515 507 486 483 510 512 491 1472 514 508 485 1505 491 1473 513 1477 509 1481 587 1403 510 1478 508 50733 1008 1506 490 479 514 1477 509 512 481 514 489 506 487 508 485 484 509 513 490 1473 513 509 483 1481 515 1474 512 1479 507 1482 514 1476 510 1478 590 50642 1006 1508 488 508 485 1478 508 487 516 479 514 481 512 510 483 486 517 504 489 1474 512 510 483 1482 514 1475 511 1479 507 1483 513 1477 509 1480 516 -# +# name: Power type: parsed protocol: NEC address: 20 00 00 00 command: 09 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 20 00 00 00 command: 1F 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 20 00 00 00 command: 0E 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 20 00 00 00 command: 1A 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 20 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 20 00 00 00 command: 05 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 20 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 20 00 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 12 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 1E 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 03 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 01 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 1F 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 09 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 10 E7 00 00 command: 01 FE 00 00 -# +# name: Mute type: parsed protocol: NECext address: 10 E7 00 00 command: 00 FF 00 00 -# +# name: Mute type: parsed protocol: NECext address: 10 E7 00 00 command: 2B D4 00 00 -# +# name: Mute type: parsed protocol: NECext address: 78 0E 00 00 command: 09 F6 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 78 0E 00 00 command: 01 FE 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 78 0E 00 00 command: 02 FD 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 06 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 02 00 00 00 command: 0A 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 02 00 00 00 command: 0D 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 02 00 00 00 command: 1C 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 02 00 00 00 command: 07 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 14 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 08 00 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 86 FF 00 00 command: 14 EB 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 86 FF 00 00 command: 13 EC 00 00 -# +# name: Power type: parsed protocol: NECext address: 86 FF 00 00 command: 1B E4 00 00 -# +# name: Mute type: parsed protocol: NECext address: 86 FF 00 00 command: 2A D5 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 07 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 00 00 00 00 -# +# name: Mute type: parsed protocol: NECext address: 40 AF 00 00 command: 19 E6 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 02 BD 00 00 command: 26 D9 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 02 BD 00 00 command: 28 D7 00 00 -# +# name: Power type: parsed protocol: NECext address: 02 BD 00 00 command: 53 AC 00 00 -# +# name: Mute type: parsed protocol: NECext address: 02 BD 00 00 command: AD 52 00 00 -# +# name: Power type: parsed protocol: SIRC address: 10 00 00 00 command: 2F 00 00 00 -# +# name: Vol_up type: parsed protocol: SIRC address: 10 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: SIRC address: 10 00 00 00 command: 13 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 85 23 00 00 command: 99 66 00 00 -# +# name: Mute type: parsed protocol: NECext address: 85 23 00 00 command: 97 68 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 85 23 00 00 command: 57 A8 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 85 23 00 00 command: 47 B8 00 00 -# +# name: Power type: parsed protocol: NEC address: 08 00 00 00 command: 10 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 08 00 00 00 command: 16 00 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 86 FF 00 00 command: 21 DE 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 86 FF 00 00 command: 2B D4 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: BA 4B 00 00 command: 03 FC 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: BA 4B 00 00 command: 02 FD 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: BA A0 00 00 command: 03 FC 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: BA A0 00 00 command: 02 FD 00 00 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1014 1477 517 478 514 509 483 487 515 480 512 484 508 488 514 1479 515 1476 508 1483 511 1481 513 1478 516 1475 509 1482 512 1479 515 480 512 483 509 50775 1014 1477 517 504 488 508 484 513 489 506 486 510 482 514 488 1478 516 1475 509 1483 511 1480 514 1477 517 1474 510 1482 512 1478 516 505 487 509 483 50770 1009 1481 513 508 484 512 490 506 486 510 482 514 488 508 484 1481 513 1478 516 1475 509 1483 511 1480 514 1477 517 1475 509 1482 512 509 483 513 489 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 1010 1508 486 509 483 513 489 507 485 512 490 505 487 510 482 1509 485 511 491 1501 483 1509 485 1507 487 1504 490 1502 482 1509 485 511 491 1500 484 50779 1010 1506 488 508 484 512 490 505 487 509 483 513 489 506 486 1506 488 507 485 1506 488 1503 481 1510 484 1508 486 1505 489 1503 481 514 488 1503 491 -# +# name: Power type: parsed protocol: NECext address: BA A0 00 00 command: 4C B3 00 00 -# +# name: Mute type: parsed protocol: NECext address: BA A0 00 00 command: 01 FD 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1004 1513 481 515 487 1478 516 505 487 510 482 1484 510 1481 513 508 484 512 490 1475 509 513 489 1477 507 1484 510 511 481 515 487 1479 515 1474 510 50774 1005 1484 510 513 489 1476 508 513 489 508 484 1482 512 1479 515 506 486 511 481 1483 511 512 490 1475 509 1483 511 510 482 515 487 1504 490 1475 509 50777 1013 1503 491 506 486 1505 489 507 485 512 490 1501 483 1508 486 510 482 514 488 1503 481 515 487 1504 490 1500 484 512 490 506 486 1506 488 1502 482 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 1014 1477 507 514 488 508 484 512 490 505 487 509 483 513 489 507 485 1480 514 1477 517 1475 509 1483 511 1480 514 1477 517 1475 509 1482 512 508 484 50774 1004 1486 508 513 489 507 485 511 491 504 488 508 484 513 489 505 487 1479 515 1476 508 1484 510 1481 513 1478 516 1475 509 1482 512 1480 514 507 485 50771 1007 1507 487 509 483 513 489 507 485 511 481 515 487 508 484 513 489 1502 482 1483 511 1481 513 1479 515 1476 508 1484 510 1481 513 1478 516 506 486 -# +# name: Power type: parsed protocol: NECext address: BA 4B 00 00 command: 4C B3 00 00 -# +# name: Mute type: parsed protocol: NECext address: BA 4B 00 00 command: 01 FE 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 83 22 00 00 command: 0A F5 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 83 22 00 00 command: 01 FE 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 35 00 00 00 command: 45 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 35 00 00 00 command: 1B 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 35 00 00 00 command: 09 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 35 00 00 00 command: 51 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 83 22 00 00 command: 08 F7 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 0A 1D 00 00 command: 08 F7 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 0A 1D 00 00 command: 0A F5 00 00 -# +# name: Mute type: parsed protocol: NECext address: 0A 1D 00 00 command: 03 FC 00 00 -# +# name: Power type: parsed protocol: NECext address: 0A 1D 00 00 command: 01 FE 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 20 00 00 00 command: 06 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 20 00 00 00 command: 07 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 20 00 00 00 command: 1E 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 80 00 00 00 command: 01 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 80 00 00 00 command: 03 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 80 00 00 00 command: 06 00 00 00 -# +# name: Vol_up type: parsed protocol: Samsung32 address: 2C 00 00 00 command: 17 00 00 00 -# +# name: Vol_dn type: parsed protocol: Samsung32 address: 2C 00 00 00 command: 16 00 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 83 22 00 00 command: 16 E9 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 83 22 00 00 command: 0F F0 00 00 -# +# name: Power type: parsed protocol: NECext address: 83 22 00 00 command: 0C F3 00 00 -# +# name: Mute type: parsed protocol: NECext address: 83 22 00 00 command: 15 EA 00 00 -# +# name: Power type: parsed protocol: Kaseikyo address: A0 02 20 00 command: D0 03 00 00 -# +# name: Power type: parsed protocol: NECext address: C8 91 00 00 command: 00 FF 00 00 -# +# name: Mute type: parsed protocol: NECext address: C8 91 00 00 command: 20 DF 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: C8 91 00 00 command: 1E E1 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: C8 91 00 00 command: 1F E0 00 00 -# +# name: Vol_up type: parsed protocol: RC6 address: 10 00 00 00 command: 10 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC6 address: 10 00 00 00 command: 11 00 00 00 -# +# name: Power type: parsed protocol: RC6 address: 10 00 00 00 command: 0C 00 00 00 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4559 4461 546 490 515 495 521 490 515 495 489 1484 516 1482 550 486 519 491 493 1480 520 1478 522 1477 523 1475 546 490 515 495 521 490 515 495 489 4493 545 491 525 486 519 491 514 496 488 1484 516 1483 517 1481 551 486 488 1485 515 1483 517 1482 550 486 519 491 525 486 519 491 493 1479 542 494 522 489 516 467 517 1482 550 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4550 4469 548 462 543 467 549 461 544 466 518 1481 520 1479 542 468 548 462 522 1477 523 1476 514 1485 515 1483 549 461 544 466 550 461 544 466 518 4491 547 463 542 468 548 462 543 467 549 462 543 467 549 461 523 1476 514 1485 515 1484 516 1482 550 461 513 1486 514 1485 515 1483 549 461 544 466 550 461 544 493 491 1481 540 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4496 4442 513 503 488 502 489 501 490 500 491 1505 487 1508 484 505 486 504 487 1508 484 1511 492 1503 489 1499 514 484 486 504 487 502 489 501 490 4449 517 499 492 499 492 497 483 507 484 1511 492 1504 488 1499 514 483 487 1509 483 1512 491 1504 488 503 488 501 490 500 491 499 492 1504 488 501 490 500 491 492 509 1494 488 55126 4496 4446 541 482 488 502 489 501 490 500 491 1505 487 1508 484 505 486 504 487 1508 484 1503 510 1493 489 1480 512 504 487 503 488 502 489 500 491 4449 517 498 493 497 483 507 484 505 486 1502 511 1492 490 1504 488 502 489 1507 485 1509 483 1512 491 473 518 498 493 496 484 505 486 1510 483 499 512 484 486 504 487 1508 484 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4492 4434 510 505 486 505 486 504 487 503 488 1481 511 1484 518 499 492 498 493 1476 516 1479 513 1483 519 1469 596 402 516 500 491 499 492 498 493 4447 518 498 493 497 483 507 484 506 485 504 487 503 488 494 517 1485 486 1483 519 1476 516 1480 512 504 486 1508 484 1486 517 1479 565 425 513 502 488 501 490 492 509 1467 515 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4500 4436 516 504 486 510 490 505 485 510 490 1500 491 1474 517 505 485 484 516 1501 490 1501 490 1501 490 1501 490 505 485 511 489 506 484 485 515 4449 513 482 508 513 487 508 482 514 486 1504 487 1504 487 1504 487 482 508 1509 482 1509 492 1499 492 504 486 509 491 504 486 509 491 1500 491 504 486 510 490 478 512 1505 486 55017 4492 4444 508 512 488 508 482 513 487 508 482 1508 483 1482 509 513 487 508 482 1509 482 1509 482 1483 508 1483 508 513 487 508 482 514 486 509 481 4457 515 506 484 511 489 506 484 511 489 1501 490 1475 516 1501 490 506 484 1480 511 1507 484 1480 511 511 489 506 484 512 488 507 483 1507 484 512 488 507 483 512 488 1503 488 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4493 4443 509 512 488 507 483 512 488 507 483 1482 509 1482 509 512 488 507 483 1482 509 1482 509 1482 509 1482 509 512 488 507 483 513 487 508 482 4457 516 505 485 510 490 505 485 510 490 505 485 511 489 505 485 1480 511 1480 511 1480 511 1481 510 484 516 1476 515 1476 515 1476 515 481 509 486 514 481 509 486 514 1476 515 55014 4498 4438 514 482 508 487 513 482 508 488 512 1477 514 1477 514 482 508 487 513 1477 514 1477 514 1477 514 1477 514 482 518 477 513 482 518 477 513 4451 511 485 515 479 511 485 515 480 510 485 515 480 510 485 515 1475 516 1475 516 1476 515 1476 515 480 510 1481 510 1481 510 1481 510 486 514 481 509 486 514 481 509 1482 509 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4524 4473 512 516 491 511 486 516 491 511 496 1482 522 1482 522 506 491 512 495 1483 521 1483 521 1483 521 1483 521 506 491 512 495 507 490 512 495 4475 521 507 490 513 494 508 489 513 494 1484 520 1484 520 1483 521 507 490 1488 516 1488 516 1488 516 511 496 506 491 511 496 506 491 1488 516 511 496 506 491 512 495 1483 521 55356 4533 4463 512 516 491 511 486 516 491 511 496 1507 487 1492 512 515 492 510 487 1492 512 1492 512 1491 513 1491 513 515 492 510 487 515 492 510 487 4484 512 516 491 511 496 506 491 512 495 1483 521 1482 522 1482 522 506 491 1488 516 1487 517 1487 517 511 496 506 491 512 495 506 491 1488 516 512 495 507 490 512 495 1483 521 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4533 4464 521 507 490 512 495 507 490 513 494 1484 520 1484 520 508 489 513 494 1484 520 1484 520 1483 521 1483 521 506 491 511 496 506 491 512 495 4475 521 507 490 512 495 507 490 513 494 508 489 513 494 508 489 1490 514 1490 514 1490 514 1490 514 514 493 1485 519 1485 519 1485 519 509 488 515 492 510 487 516 491 1487 517 55369 4531 4465 520 508 489 514 493 509 488 515 492 1486 518 1486 518 509 488 514 493 1486 518 1485 519 1485 519 1485 519 509 488 514 493 509 488 515 492 4478 518 511 486 516 491 511 496 506 491 512 495 507 490 512 495 1483 521 1483 521 1483 521 1483 521 507 490 1488 516 1488 516 1488 516 512 495 507 490 513 494 508 489 1490 514 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4530 4465 521 507 490 513 494 507 490 513 494 1509 496 1483 521 506 491 512 495 1508 486 1492 512 1492 513 1491 513 514 493 509 488 514 493 509 488 4483 513 514 493 509 488 515 492 510 487 515 492 510 487 515 492 510 487 1517 488 1491 513 1490 515 513 494 1510 494 1484 520 1483 521 1483 521 506 491 512 495 506 491 1487 517 55357 4528 4468 518 510 487 515 492 510 487 515 492 1512 493 1485 519 509 488 514 493 1510 494 1483 522 1483 521 1482 512 516 491 511 486 516 491 511 486 4484 512 516 491 511 486 516 491 511 486 516 491 511 486 516 491 511 496 1507 487 1491 513 1491 514 514 493 1510 495 1484 521 1483 522 1483 522 506 491 511 486 516 491 1513 491 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4495 4440 512 509 491 504 486 509 491 504 486 1478 513 1479 512 509 491 504 486 1478 513 1479 512 1479 512 1479 512 509 491 504 486 509 491 504 486 4452 511 511 489 506 484 511 489 506 484 511 489 506 484 511 489 506 484 1480 511 1480 511 1480 511 510 491 1500 491 1475 516 1475 516 1475 516 504 486 510 490 505 485 1479 512 55017 4497 4439 513 507 483 513 487 507 483 513 487 1476 515 1477 514 507 483 513 487 1476 515 1476 515 1477 514 1477 514 481 509 512 488 507 483 512 488 4451 512 509 491 504 486 509 491 504 486 483 517 504 486 509 492 504 486 1504 487 1504 487 1478 513 508 482 1509 482 1509 482 1509 482 1483 508 513 487 508 482 513 487 1504 487 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 4501 4435 517 504 486 510 490 505 485 510 490 1475 516 1475 516 506 484 511 489 1476 515 1476 515 1476 515 1477 514 507 483 512 488 507 483 513 487 4451 511 510 490 479 511 484 516 506 484 1481 510 511 489 506 484 511 489 1476 515 1476 515 1477 514 507 483 512 488 1477 514 1477 514 1478 513 508 482 513 487 508 482 1484 517 55011 4496 4440 512 509 491 478 512 483 517 504 486 1480 511 1480 511 484 516 505 485 1480 511 1480 511 1481 510 1481 510 484 516 506 484 511 489 506 484 4455 517 504 486 509 491 504 486 483 517 1474 517 505 485 510 490 505 485 1480 511 1480 511 1481 510 511 489 480 510 1481 510 1481 510 1482 509 485 515 507 483 512 488 1477 514 -# +# name: Power type: parsed protocol: Samsung32 address: 2C 00 00 00 command: 1E 00 00 00 -# +# name: Mute type: parsed protocol: Samsung32 address: 2C 00 00 00 command: 1F 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4504 4432 511 509 492 504 486 509 492 504 486 1478 514 1477 515 507 484 512 489 1476 516 1475 517 1475 517 1474 518 503 487 509 492 503 487 508 493 4446 508 513 488 507 483 512 489 507 483 512 489 506 484 511 490 506 484 1481 511 1480 512 1479 513 509 492 1473 519 1473 519 1472 509 1482 510 511 490 506 484 511 490 1475 517 54985 4498 4437 517 504 486 509 492 504 486 509 492 1473 519 1472 509 512 489 506 484 1481 511 1480 512 1480 512 1479 513 508 493 502 488 507 483 512 489 4449 516 506 485 511 490 505 485 510 491 505 485 510 491 504 486 509 492 1473 519 1473 508 1483 561 434 515 1476 516 1475 517 1474 518 1473 519 502 488 507 483 512 489 1476 516 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 4531 4406 516 478 512 484 538 457 544 452 538 1453 518 1473 519 476 546 450 540 1451 509 1482 510 1481 511 1480 512 483 539 457 544 452 538 457 544 4420 513 482 540 456 545 450 540 455 546 1445 547 449 541 454 536 458 543 1449 543 1449 543 1448 544 451 539 456 545 1445 547 1445 547 1444 537 458 543 453 537 457 544 1448 544 54957 4495 4440 514 481 520 477 513 482 519 476 514 1476 516 1475 517 478 512 484 517 1474 518 1473 519 1472 520 1471 521 474 516 479 511 484 517 479 511 4452 512 509 492 503 487 508 493 503 487 1477 515 507 483 512 489 506 484 1481 511 1481 511 1480 512 509 492 503 487 1478 514 1478 514 1477 515 506 484 511 490 505 485 1480 512 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4551 4468 549 461 544 493 523 460 545 466 518 1480 520 1479 542 468 548 489 495 1477 523 1475 515 1484 516 1483 549 461 544 493 523 487 518 466 518 4490 548 462 543 493 523 488 517 466 550 461 544 466 550 460 545 465 519 1480 520 1478 522 1477 544 465 519 1480 520 1479 521 1477 523 1476 545 464 541 496 520 490 494 1479 542 55901 4554 4465 542 494 522 489 516 494 522 488 496 1477 523 1476 545 490 515 495 489 1484 516 1483 517 1481 519 1479 542 494 522 489 516 494 522 488 496 4487 541 495 521 490 515 495 521 489 516 494 522 488 517 493 523 488 496 1476 525 1475 515 1483 549 488 496 1476 524 1475 515 1484 516 1482 550 486 519 491 525 486 488 1485 547 55897 4548 4470 548 462 543 468 548 462 543 467 517 1482 518 1480 541 469 547 490 494 1478 575 1423 515 1485 515 1483 549 461 544 466 550 461 544 466 518 4491 547 462 543 467 549 462 543 467 549 461 544 466 539 497 519 492 492 1480 520 1478 522 1477 544 465 519 1480 520 1479 521 1477 513 1486 546 464 541 469 547 464 520 1478 543 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 4553 4467 550 459 546 464 541 469 547 464 520 1479 521 1477 544 492 524 486 498 1475 515 1484 516 1482 518 1481 540 496 520 490 515 495 521 490 494 4488 550 486 519 491 525 486 519 491 493 1479 542 494 522 489 516 494 490 1482 518 1481 519 1480 541 494 522 489 495 1478 522 1476 514 1485 547 489 516 494 522 489 495 1477 544 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4555 4483 516 516 488 519 495 512 492 515 489 1525 493 1519 489 518 496 511 493 1521 487 1525 493 1520 488 1524 494 513 491 517 487 520 494 514 490 4501 519 513 491 516 488 520 494 513 491 517 487 521 493 514 490 518 486 1527 491 1522 496 1517 491 516 488 1525 493 1520 488 1525 493 1520 488 519 495 512 492 516 488 1525 493 -# +# name: Vol_up type: parsed protocol: RC5 address: 13 00 00 00 command: 0D 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC5 address: 13 00 00 00 command: 0E 00 00 00 -# +# name: Power type: parsed protocol: RC5 address: 13 00 00 00 command: 0B 00 00 00 -# +# name: Mute type: parsed protocol: RC5 address: 13 00 00 00 command: 0C 00 00 00 -# +# name: Vol_up type: parsed protocol: SIRC address: 01 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: SIRC address: 01 00 00 00 command: 13 00 00 00 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4552 4468 574 436 566 444 568 442 570 441 540 1458 546 1453 572 439 573 437 544 1455 539 1460 544 1456 548 1450 575 436 566 444 568 442 570 440 541 4468 594 416 565 445 567 443 569 441 540 1459 545 1453 541 1459 566 444 547 1452 542 1457 547 1452 593 417 564 446 566 444 568 442 539 1460 565 446 566 444 568 442 539 1460 565 55957 4581 4437 543 467 545 466 546 464 538 472 519 1480 514 1485 540 471 541 469 512 1486 518 1481 513 1486 518 1481 575 436 566 444 568 442 570 440 541 4468 543 468 544 465 547 464 538 472 519 1480 514 1485 519 1479 546 465 516 1483 511 1488 516 1483 542 468 544 466 546 464 538 473 519 1480 545 466 546 464 538 472 519 1480 545 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4546 4473 538 473 539 470 542 469 543 467 514 1484 520 1479 545 465 547 463 518 1481 513 1486 518 1481 513 1487 538 472 540 470 542 469 543 466 515 4495 546 463 539 472 540 470 542 468 544 466 546 491 521 462 519 1480 513 1485 519 1480 514 1486 539 472 519 1479 515 1484 520 1479 546 464 548 463 539 471 541 469 512 1487 548 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4549 4469 542 468 544 467 545 465 537 473 519 1481 513 1485 540 471 541 469 512 1487 517 1482 512 1487 517 1482 543 467 545 465 547 463 539 472 519 4489 543 468 544 466 546 464 538 473 539 470 542 468 544 466 546 465 516 1482 512 1487 517 1482 543 468 513 1485 519 1480 514 1485 519 1480 545 465 547 463 539 472 520 1479 546 55899 4549 4470 541 469 574 436 566 445 546 463 549 1450 513 1486 539 471 572 438 543 1456 548 1451 543 1456 548 1451 574 436 566 445 567 443 569 441 540 4469 573 437 575 435 567 443 569 441 571 439 573 437 565 445 567 443 549 1451 543 1456 548 1451 574 436 545 1454 540 1459 545 1454 540 1459 566 444 568 442 570 440 541 1458 567 55878 4580 4439 572 439 573 437 565 445 567 443 548 1451 543 1456 569 441 571 439 542 1457 547 1452 542 1457 547 1452 573 437 575 435 567 443 569 442 539 4469 573 438 574 436 566 444 568 442 570 440 572 438 574 436 566 444 547 1452 542 1457 547 1452 573 437 544 1455 539 1460 544 1455 539 1460 575 435 567 444 568 442 539 1459 566 55879 4578 4442 569 441 571 439 573 437 575 435 546 1453 541 1458 567 444 568 441 540 1459 545 1454 540 1459 545 1454 571 439 573 437 575 435 567 444 548 4461 571 440 572 438 574 436 566 444 568 442 570 440 572 438 574 436 545 1454 540 1459 545 1454 571 439 542 1457 547 1452 542 1457 547 1452 573 437 575 435 567 444 547 1451 574 55871 4554 4465 546 464 538 473 539 471 541 469 512 1487 517 1481 544 467 545 465 516 1483 511 1488 516 1483 511 1488 547 463 539 472 540 470 542 468 513 4496 546 464 538 472 540 470 542 468 544 466 546 464 538 473 539 471 510 1488 516 1483 511 1488 547 463 518 1481 513 1486 518 1481 513 1486 539 472 540 470 542 467 514 1485 540 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 4555 4465 546 464 538 472 540 470 542 468 513 1486 518 1481 544 467 545 465 516 1483 511 1488 516 1483 511 1488 537 473 539 472 540 470 542 468 513 4496 546 464 538 473 539 471 541 469 512 1487 538 472 540 470 542 469 512 1486 518 1481 544 1455 570 441 571 439 542 1457 547 1452 542 1457 568 442 570 440 572 438 543 1456 569 55920 4555 4464 547 464 538 472 540 470 542 468 513 1486 518 1481 544 466 546 464 517 1482 512 1487 517 1482 512 1487 538 473 539 471 541 469 543 467 514 4495 547 463 539 472 540 470 542 468 513 1486 539 471 541 469 543 467 514 1485 519 1479 515 1485 540 470 542 468 513 1486 518 1481 513 1486 539 471 541 469 543 467 514 1485 540 -# +# name: Power type: parsed protocol: SIRC15 address: 10 00 00 00 command: 15 00 00 00 -# +# name: Power type: parsed protocol: SIRC address: 01 00 00 00 command: 15 00 00 00 -# +# name: Mute type: parsed protocol: SIRC address: 01 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: SIRC address: 10 00 00 00 command: 15 00 00 00 -# +# name: Power type: parsed protocol: SIRC15 address: 30 00 00 00 command: 15 00 00 00 -# +# name: Mute type: parsed protocol: SIRC15 address: 30 00 00 00 command: 14 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 86 FF 00 00 command: 1C E3 00 00 -# +# name: Mute type: parsed protocol: NECext address: 86 FF 00 00 command: 1D E2 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 80 00 00 00 command: 0D 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 80 00 00 00 command: 08 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 80 00 00 00 command: 12 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 80 00 00 00 command: 1E 00 00 00 -# +# name: Power type: parsed protocol: NECext address: EF 01 00 00 command: 25 DA 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: EF 01 00 00 command: 14 EB 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: EF 01 00 00 command: 13 EC 00 00 -# +# name: Mute type: parsed protocol: NECext address: EF 01 00 00 command: 28 D7 00 00 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 1048 580 597 1165 596 582 595 1167 594 875 599 578 599 1455 593 585 592 1171 600 577 600 576 601 13191 1042 586 591 1170 601 576 601 1161 600 869 595 582 595 1459 599 578 599 1163 598 580 597 579 598 13195 1048 580 597 1165 596 581 596 1167 594 875 599 578 599 1456 592 585 592 1171 600 577 600 577 600 13192 1052 576 601 1162 599 578 599 1163 598 871 593 584 593 1462 596 581 596 1166 595 582 595 582 595 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 1048 553 624 1138 623 554 623 1139 622 848 626 551 626 1427 621 556 652 1403 655 522 655 814 650 12579 1050 552 656 1106 624 553 624 1138 623 847 627 550 627 1427 621 556 621 1434 624 553 624 845 619 12612 1079 523 623 1139 622 555 622 1139 622 848 626 550 627 1427 621 556 621 1434 624 553 624 845 619 12613 1047 556 621 1141 620 556 621 1141 620 850 624 552 625 1429 619 558 619 1435 623 554 623 846 628 12600 1050 552 625 1137 624 553 624 1137 624 846 618 558 619 1435 623 554 623 1431 627 550 627 842 622 12609 1051 551 626 1136 625 551 626 1136 625 844 620 557 620 1434 624 554 623 1431 627 550 627 843 621 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 1073 528 649 1114 647 530 647 1116 645 825 649 527 650 1405 653 1402 625 552 646 1409 649 1113 648 11417 1077 524 653 1110 651 526 651 1112 649 820 654 523 654 1400 648 1407 651 526 651 1404 654 1109 652 11414 1079 523 654 1109 652 524 653 1111 650 819 624 554 654 1401 647 1408 650 529 648 1406 652 1111 650 11416 1077 525 652 1110 651 526 651 1112 649 820 654 523 654 1401 647 1408 650 528 649 1405 622 1141 651 11414 1080 521 646 1116 624 553 645 1117 654 816 648 529 648 1406 652 1403 655 523 644 1410 648 1114 647 11418 1075 526 651 1111 650 527 650 1112 649 820 623 554 654 1400 648 1407 651 526 651 1403 655 1107 654 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 1051 550 627 1135 626 551 626 1136 625 845 619 850 624 553 675 794 629 842 622 555 622 555 622 13780 1047 555 622 1140 621 557 620 1141 620 850 624 845 619 558 619 850 624 846 618 559 618 559 618 13782 1045 558 619 1143 618 559 618 1144 627 842 622 847 627 550 627 842 622 848 626 551 626 551 626 13774 1053 549 618 1144 627 550 627 1135 626 843 621 849 625 551 626 844 620 850 624 553 624 552 625 13776 1051 551 626 1137 624 553 624 1138 623 846 618 851 623 554 623 846 628 841 623 554 623 554 623 13776 1051 551 626 1136 625 552 625 1137 624 845 619 850 624 553 624 846 618 852 622 555 622 554 623 13778 1049 554 623 1139 622 555 622 1140 621 849 625 844 620 557 620 850 624 846 618 559 618 558 619 -# +# name: Power type: parsed protocol: NEC address: 78 00 00 00 command: CC 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 78 00 00 00 command: 9C 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 7E 00 00 00 command: 2A 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 7A 00 00 00 command: 1C 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 274 789 254 1792 275 814 250 787 246 816 248 1798 279 784 249 813 251 785 248 788 245 1827 281 1791 275 1825 272 790 253 783 250 43886 277 786 278 1795 272 791 252 783 281 782 251 785 269 1804 273 1800 277 1822 275 1798 279 783 270 766 277 759 274 1825 272 1800 277 43886 277 759 274 1825 272 764 279 756 277 786 278 1795 282 781 272 763 280 755 278 785 279 1794 273 1827 270 1802 275 761 272 791 273 43888 276 761 272 1800 277 786 278 758 275 760 273 790 274 1799 278 1821 276 1796 281 1792 275 788 276 760 273 789 275 1798 279 1794 273 43889 278 785 248 1825 272 790 253 782 272 764 279 1793 274 790 274 761 282 781 273 763 280 1793 273 1825 272 1800 277 813 220 789 275 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 270 793 250 1795 272 818 246 791 252 809 244 766 277 1795 282 781 272 790 253 809 224 1822 275 1797 280 1820 277 785 248 788 245 43889 273 790 274 1799 278 785 248 788 276 787 246 1826 271 792 251 1794 273 1827 270 1802 275 788 245 791 273 790 253 1819 278 1794 273 43889 274 789 254 1818 269 767 276 786 247 789 275 788 245 1827 270 792 251 785 248 814 250 1796 281 1819 248 1825 272 790 253 783 271 43889 245 791 273 1799 278 786 278 784 249 787 246 1827 281 781 252 1821 276 1796 281 1792 274 814 250 786 247 789 244 1829 279 1793 274 43888 275 815 218 1828 280 783 250 786 278 785 248 788 245 1827 280 782 251 785 268 794 249 1797 280 1819 278 1794 272 791 252 810 254 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 277 759 274 1799 278 784 280 783 250 812 242 1804 273 816 227 808 246 791 252 1819 248 1825 273 1826 251 1822 276 786 247 789 244 43888 274 815 249 1797 280 783 250 812 252 784 249 813 241 1805 272 1827 250 1822 275 787 246 816 217 819 255 807 226 1820 278 1795 272 43888 273 789 254 1817 270 793 251 785 248 814 250 1795 282 807 247 790 253 783 250 1822 276 1796 281 1818 249 1824 274 814 229 807 247 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 275 762 271 1800 277 786 278 784 249 813 241 795 248 1824 253 784 270 792 251 1821 246 1826 272 1801 276 1823 275 762 271 790 243 43889 274 789 275 1797 280 783 250 812 252 784 249 1823 275 762 271 1827 250 1822 276 787 246 816 217 818 246 790 253 1819 268 1804 273 43886 277 786 247 1825 273 764 280 783 250 811 253 784 249 1822 276 761 272 816 228 1819 279 1794 273 1826 251 1821 277 786 247 815 249 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 275 762 271 1800 277 786 278 784 249 813 241 795 248 814 219 817 247 789 254 1818 249 1824 274 1798 279 1820 278 759 274 788 245 43887 274 789 275 1798 279 783 250 812 252 784 249 1823 274 1798 279 1820 247 1826 271 765 278 809 224 812 252 784 249 1823 275 1798 279 43880 281 782 251 1821 277 760 273 789 244 818 246 790 253 808 246 791 252 809 224 1822 275 1797 280 1819 248 1825 273 790 253 808 246 -# +# name: Power type: parsed protocol: NECext address: 87 7C 00 00 command: 80 7F 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 87 7C 00 00 command: 88 77 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 87 7C 00 00 command: 8C 73 00 00 -# +# name: Mute type: parsed protocol: NECext address: 87 7C 00 00 command: 94 6B 00 00 -# +# name: Power type: parsed protocol: NECext address: D2 6C 00 00 command: CB 34 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: D2 6D 00 00 command: 02 FD 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: D2 6D 00 00 command: 03 FC 00 00 -# +# name: Mute type: parsed protocol: NECext address: D2 6D 00 00 command: 05 FA 00 00 -# +# name: Power type: parsed protocol: NECext address: D2 03 00 00 command: 04 FB 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: D2 03 00 00 command: 02 FD 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: D2 03 00 00 command: 03 FC 00 00 -# +# name: Mute type: parsed protocol: NECext address: D2 03 00 00 command: 05 FA 00 00 -# +# name: Power type: parsed protocol: RC5 address: 14 00 00 00 command: 0C 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC5 address: 14 00 00 00 command: 11 00 00 00 -# +# name: Vol_up type: parsed protocol: RC5 address: 14 00 00 00 command: 10 00 00 00 -# +# name: Power type: parsed protocol: SIRC15 address: 10 00 00 00 command: 60 00 00 00 -# +# name: Vol_up type: parsed protocol: SIRC15 address: 30 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: SIRC15 address: 30 00 00 00 command: 13 00 00 00 -# +# name: Vol_up type: parsed protocol: SIRC15 address: 10 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: SIRC15 address: 10 00 00 00 command: 13 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 7E 81 00 00 command: 2A D4 00 00 -# +# name: Mute type: parsed protocol: NECext address: 7A 85 00 00 command: 1C E2 00 00 -# +# name: Power type: parsed protocol: NEC address: 78 00 00 00 command: 0F 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 78 00 00 00 command: 4F 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 7A 00 00 00 command: 1A 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 7A 00 00 00 command: 1B 00 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 7A 85 00 00 command: 1A E4 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 7A 85 00 00 command: 1B E5 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 78 00 00 00 command: 1E 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 78 00 00 00 command: 1F 00 00 00 -# +# name: Mute type: parsed protocol: RC5X address: 0A 00 00 00 command: 2F 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 15 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8437 4188 538 1565 539 1565 539 513 544 508 538 513 544 1559 545 507 539 1564 540 1564 540 1563 541 1563 541 511 546 1557 547 505 542 511 546 505 542 20497 597 1507 545 1559 545 507 539 512 545 507 539 1564 540 512 545 1558 546 1558 546 1558 546 1557 547 505 542 1562 542 510 547 505 542 510 547 20492 540 1564 540 1564 540 512 545 506 540 511 546 1558 546 505 542 1562 542 1562 542 1562 542 1561 543 509 548 1555 538 514 543 509 537 514 543 20495 547 1558 546 1557 547 505 541 511 546 505 542 1562 542 510 547 1556 548 1556 548 1556 548 1556 537 514 543 1560 544 508 538 514 543 508 538 20501 541 1562 542 1562 542 510 547 505 541 510 547 1556 548 504 543 1561 543 1561 543 1560 544 1560 544 508 538 1565 539 513 544 507 539 513 544 20494 548 1556 548 1556 548 504 543 509 548 504 543 1560 544 508 539 1565 539 1565 539 1564 540 1564 540 512 545 1559 545 506 540 512 545 506 540 20499 543 1560 544 1560 544 508 539 513 544 508 538 1564 540 512 545 1559 545 1558 546 1558 546 1558 546 506 541 1563 541 510 547 505 542 510 547 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 8430 4194 542 1562 542 1562 542 510 547 505 541 510 547 1556 548 504 542 1561 543 509 548 1556 548 1556 548 1556 548 1556 548 504 542 509 537 514 543 20496 545 1559 545 1559 545 507 539 512 545 507 539 1564 540 512 545 1559 545 507 539 1564 540 1564 540 1563 541 1563 541 511 546 506 540 511 546 20494 546 1557 547 1557 547 505 541 510 547 505 541 1562 542 510 547 1556 548 505 541 1562 542 1562 542 1561 543 1561 543 509 548 504 542 509 537 20501 540 1565 539 1565 539 512 545 507 539 512 545 1559 545 507 539 1564 540 512 545 1559 545 1558 546 1558 546 1558 546 506 540 511 546 506 540 20498 543 1562 542 1562 542 510 547 505 541 510 547 1557 547 505 541 1562 542 509 548 1556 548 1556 548 1556 548 1556 548 504 542 509 548 504 542 20497 543 1560 544 1560 544 508 538 513 544 508 538 1565 539 513 544 1560 544 508 538 1565 539 1565 539 1565 539 1564 540 513 544 507 539 512 545 20495 545 1558 546 1558 546 506 540 511 546 506 540 1563 541 511 546 1558 546 506 540 1563 541 1563 541 1563 541 1562 542 510 547 505 541 510 547 20493 548 1556 548 1556 548 504 542 509 548 504 542 1561 543 509 537 1566 538 514 543 1560 544 1560 544 1560 544 1560 544 508 538 513 544 508 538 20501 539 1564 540 1564 540 512 545 507 539 512 545 1559 545 507 539 1564 540 512 545 1559 545 1558 546 1558 546 1558 546 506 540 511 546 506 540 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 8434 4191 545 1559 545 1559 545 534 512 513 544 507 539 1564 540 538 519 1559 545 1558 546 1558 546 1558 546 1558 546 1558 546 533 513 511 546 506 540 19446 547 1557 547 1557 547 532 514 511 546 532 514 1562 593 459 546 1557 547 1557 547 1557 547 1557 547 1557 547 1557 547 531 515 510 547 505 541 19446 548 1556 548 1556 548 530 516 509 548 504 543 1561 543 535 512 1566 538 1565 539 1565 539 1565 539 1565 539 1565 539 540 517 509 537 514 543 19444 539 1565 539 1565 539 513 544 508 538 513 544 1559 545 534 512 1564 540 1564 540 1564 540 1564 540 1564 540 1564 540 539 518 507 539 513 544 19442 541 1563 541 1563 541 538 519 507 539 512 545 1558 546 506 540 1563 541 1563 541 1563 541 1563 541 1563 541 1562 542 537 520 505 541 511 546 19440 595 1509 543 1561 543 536 521 504 542 509 548 1555 538 540 517 1560 544 1560 544 1560 544 1560 544 1560 544 1560 544 534 512 513 544 508 538 19448 546 1559 545 1559 545 533 513 512 545 507 539 1563 541 538 519 1558 546 1558 546 1558 546 1557 547 1558 546 1558 546 533 513 512 545 506 540 19447 546 1557 547 1557 547 532 514 511 546 506 540 1562 542 537 520 1557 547 1557 547 1557 547 1557 547 1557 547 1557 547 505 542 510 547 505 541 19445 548 1556 548 1556 548 531 515 510 547 504 542 1561 543 536 521 1555 538 1566 538 1566 538 1566 538 1566 538 1565 539 514 543 509 537 514 543 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 8435 4189 547 1557 547 1557 547 505 541 510 547 505 541 1562 542 510 547 1557 547 505 541 510 547 1557 546 1557 547 1557 547 505 541 510 547 505 541 21550 547 1558 545 1558 546 506 540 511 546 506 540 1563 541 511 546 1558 545 506 540 511 546 1558 546 1558 546 1558 546 506 540 511 546 506 540 21551 546 1558 546 1558 546 506 540 512 545 506 540 1563 540 512 545 1558 546 506 540 512 545 1558 546 1558 546 1558 546 506 540 512 545 506 540 21551 546 1559 545 1559 545 507 539 512 545 507 539 1564 539 512 545 1559 545 507 539 512 545 1559 545 1559 545 1559 545 507 539 512 545 507 539 21552 545 1559 545 1559 545 507 539 513 544 507 539 1564 540 512 545 1559 545 507 539 512 545 1559 545 1559 545 1559 545 507 539 512 545 507 539 21552 545 1559 545 1559 545 507 539 513 544 507 539 1565 538 513 544 1559 545 507 539 513 544 1559 545 1559 544 1559 545 534 512 513 544 507 539 21553 544 1560 544 1560 544 534 512 513 544 508 538 1565 539 539 518 1560 544 534 512 513 544 1560 544 1560 544 1560 544 534 512 513 544 508 538 -# +# name: Power type: parsed protocol: NEC address: 20 00 00 00 command: 41 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 20 00 00 00 command: 42 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 20 00 00 00 command: 43 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 20 00 00 00 command: 1B 00 00 00 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 8498 4205 651 1471 576 530 550 1572 547 535 545 536 544 1578 541 540 550 1572 547 535 545 1576 543 539 541 1580 549 1572 547 534 546 1576 543 539 541 540 550 1571 548 533 547 1574 545 537 543 539 541 541 549 532 548 1574 545 536 544 1578 541 540 550 1571 548 1572 547 1574 545 1576 543 26533 8497 4203 653 1468 569 538 542 1579 550 531 549 533 547 1573 546 535 545 1576 543 538 542 1579 550 531 549 1571 548 1573 546 536 544 1576 543 539 541 540 550 1571 548 533 547 1574 545 536 544 538 542 540 550 530 550 1572 547 534 546 1575 544 537 543 1578 541 1579 550 1570 549 1572 547 26524 8496 4207 576 1570 549 533 547 1574 545 537 543 539 541 1580 549 532 548 1573 546 536 544 1577 542 539 551 1570 549 1572 547 534 546 1575 544 538 542 540 550 1571 548 534 546 1575 544 538 542 540 550 531 549 533 547 1575 544 537 543 1579 550 531 549 1571 548 1572 547 1574 545 1576 543 26529 8491 4211 573 1573 546 535 545 1576 543 539 551 530 550 1571 548 533 547 1574 545 536 544 1578 541 540 550 1570 549 1572 547 534 546 1575 544 538 542 539 541 1580 549 532 548 1572 547 535 545 537 543 539 541 540 550 1571 548 533 547 1574 545 537 543 1578 541 1579 550 1571 548 1574 545 26522 8498 4202 571 1574 545 537 543 1578 541 541 549 532 548 1573 546 534 546 1575 544 537 543 1577 542 540 550 1570 549 1571 548 533 547 1574 545 537 543 538 542 1579 550 531 549 1572 547 534 546 536 544 537 543 538 542 1579 550 531 549 1572 547 535 545 1575 544 1577 542 1579 550 1571 548 26522 8498 4203 570 1575 544 537 543 1578 541 541 549 532 548 1573 546 535 545 1575 544 538 542 1579 550 531 549 1571 548 1572 547 535 545 1575 544 538 542 539 541 1580 549 532 548 1572 547 534 546 536 544 537 543 539 541 1579 550 531 549 1571 548 533 547 1573 546 1574 545 1575 544 1577 542 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 8490 4211 573 1573 546 535 545 1576 543 539 541 540 550 1571 548 532 548 1573 546 535 545 1576 543 539 541 1579 550 1571 548 533 547 1574 545 536 544 1576 543 1578 541 540 550 1570 549 533 547 534 546 536 544 537 543 539 541 540 550 1570 549 532 548 1572 547 1573 546 1574 545 1576 543 26528 8492 4208 648 1475 572 534 546 1575 544 537 543 539 541 1580 549 532 548 1573 546 535 545 1576 543 538 542 1579 550 1571 548 533 547 1575 544 537 543 1577 542 1579 550 532 548 1573 546 535 545 537 543 539 541 541 549 532 548 533 547 1575 544 537 543 1578 541 1579 550 1571 548 1573 546 26523 8496 4203 622 1499 569 537 543 1578 541 541 549 532 548 1572 547 534 546 1574 545 537 543 1578 541 540 550 1570 549 1571 548 534 546 1575 544 538 542 1578 541 1580 549 532 548 1573 546 536 544 538 542 540 550 532 548 533 547 535 545 1576 543 539 541 1579 550 1571 548 1573 546 1574 545 26521 8498 4201 572 1573 546 536 544 1577 542 540 550 531 549 1571 548 533 547 1573 546 536 544 1576 543 539 541 1579 550 1570 549 532 548 1573 546 535 545 1576 543 1578 541 540 550 1571 548 533 547 535 545 537 543 538 542 540 550 531 549 1572 547 534 546 1575 544 1576 543 1577 542 1579 550 26522 8498 4203 570 1575 544 538 542 1579 550 532 548 533 547 1575 544 537 543 1578 541 540 550 1570 549 533 547 1573 546 1575 544 537 543 1578 541 540 550 1571 548 1573 546 536 544 1578 551 531 549 533 547 535 545 537 543 539 541 540 550 1571 548 533 547 1575 544 1576 543 1578 541 1580 549 26521 8498 4203 570 1576 543 538 542 1580 549 532 548 533 547 1574 545 536 544 1577 542 540 550 1570 549 533 547 1573 546 1575 544 538 542 1579 550 531 549 1571 548 1573 546 536 544 1576 543 539 551 531 549 532 548 534 546 536 544 538 542 1579 550 531 549 1572 547 1573 546 1574 545 1576 543 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8500 4203 653 1468 569 538 542 1579 550 532 548 534 546 1575 544 536 544 1578 541 540 550 1572 547 534 546 1575 544 1577 542 539 541 1580 550 532 548 534 546 535 545 1576 543 1577 542 1579 551 532 548 534 546 536 544 1577 542 1578 552 531 549 533 547 534 546 1574 545 1575 544 1577 542 26533 8492 4211 573 1573 546 535 545 1576 543 539 551 530 550 1571 548 533 547 1574 545 536 544 1577 542 540 550 1570 549 1572 547 534 546 1576 543 539 541 541 549 532 548 1573 546 1574 545 1576 543 539 551 531 549 533 547 1573 546 1576 543 539 541 541 549 532 548 1573 546 1574 545 1576 543 26532 8493 4209 575 1571 548 533 547 1575 544 537 543 539 541 1580 550 531 549 1572 547 534 546 1576 543 538 542 1579 551 1570 549 532 548 1574 545 537 543 538 542 540 550 1570 549 1572 547 1575 544 537 543 539 541 540 550 1571 548 1573 546 536 544 538 542 539 551 1570 549 1572 547 1574 545 26530 8496 4207 567 1579 551 531 549 1572 547 535 545 536 544 1576 543 538 542 1579 551 530 550 1571 548 534 546 1574 545 1576 543 538 542 1579 551 532 548 533 547 534 546 1576 543 1577 542 1579 551 532 548 534 546 535 545 1576 543 1578 541 540 550 532 548 533 547 1574 545 1575 544 1577 542 26531 8495 4210 574 1571 548 534 546 1575 544 538 542 539 551 1569 550 531 549 1572 547 535 545 1576 543 539 541 1579 550 1571 548 534 546 1575 544 538 542 540 550 531 549 1572 547 1549 570 1551 568 539 551 531 549 532 548 1573 546 1550 569 538 542 540 550 531 549 1571 548 1548 571 1550 569 -# +# name: Power type: parsed protocol: NECext address: 00 EF 00 00 command: 1C E3 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 00 EF 00 00 command: 00 FF 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 00 EF 00 00 command: 04 FB 00 00 -# +# name: Power type: parsed protocol: NEC42 address: 6E 00 00 00 command: 00 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC42 address: 6E 00 00 00 command: 4D 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC42 address: 6E 00 00 00 command: 4E 00 00 00 -# +# name: Power type: parsed protocol: NEC42 address: 6E 00 00 00 command: 0E 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8441 4184 542 1562 541 1562 542 511 546 506 540 511 546 1557 546 506 540 1563 541 1563 540 1563 541 512 545 507 539 512 545 507 539 512 545 507 539 22605 538 1565 539 1565 538 514 543 509 537 514 543 1560 543 509 548 1555 548 1556 548 1556 547 504 542 510 547 505 541 510 547 505 541 510 547 22597 546 1559 544 1559 545 507 539 513 544 507 539 1564 539 513 544 1559 544 1559 545 545 508 549 513 508 538 513 544 508 538 513 544 22601 542 1562 542 1561 543 510 547 505 541 510 547 1557 547 505 541 1562 542 1562 542 1562 542 510 547 505 541 510 547 505 541 511 546 505 541 22603 540 1564 539 1565 538 513 544 508 538 513 544 1560 544 508 538 1565 538 1565 538 1566 537 514 543 509 548 504 542 509 548 504 542 509 548 22597 546 1558 546 1558 546 506 540 512 545 507 539 1564 540 512 545 1559 545 1559 544 1559 544 507 539 513 544 508 538 513 544 508 538 513 544 22600 543 1561 542 1562 542 510 547 505 541 510 547 1557 547 505 541 1562 541 1563 540 1563 541 511 546 506 540 511 546 506 540 511 546 506 540 22604 539 1565 538 1566 537 514 543 509 548 504 542 1561 543 509 548 1556 548 1556 548 1556 547 504 542 510 547 505 541 510 547 505 541 510 547 22598 545 1559 544 1559 545 507 539 512 545 507 539 1564 540 512 545 1559 544 1559 545 1559 545 508 538 513 544 508 538 513 544 508 538 513 544 22601 542 1562 542 1562 542 511 546 505 541 511 546 1557 546 506 540 1563 541 1563 540 1563 541 511 546 506 540 511 546 506 540 512 545 506 540 22604 539 1565 538 1566 537 514 543 509 548 504 542 1561 543 509 548 1556 547 1556 547 1556 548 504 542 510 547 505 541 510 547 505 541 510 547 -# +# name: Power type: parsed protocol: SIRC address: 10 00 00 00 command: 60 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 08 00 00 00 command: 0E 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 08 00 00 00 command: 1A 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 57 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 4D 00 00 00 command: 04 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 4D 00 00 00 command: 05 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 4D 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 4D 00 00 00 command: 03 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 4D 00 00 00 command: 00 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 19 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 16 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 44 00 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 12 36 00 00 command: 0A F5 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 12 36 00 00 command: 0B F4 00 00 -# +# name: Mute type: parsed protocol: NECext address: 12 36 00 00 command: 09 F6 00 00 -# +# name: Power type: parsed protocol: NECext address: 12 36 00 00 command: 01 FE 00 00 -# +# name: Power type: parsed protocol: RC5 address: 10 00 00 00 command: 0F 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 02 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 0A 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 0E 00 00 00 -# +# name: Power type: parsed protocol: SIRC address: 0F 00 00 00 command: 15 00 00 00 -# +# name: Power type: parsed protocol: Samsung32 address: 10 00 00 00 command: 1E 00 00 00 -# +# name: Vol_up type: parsed protocol: Samsung32 address: 10 00 00 00 command: 17 00 00 00 -# +# name: Vol_dn type: parsed protocol: Samsung32 address: 10 00 00 00 command: 16 00 00 00 -# +# name: Mute type: parsed protocol: Samsung32 address: 10 00 00 00 command: 1F 00 00 00 -# +# name: Power type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 03 00 00 00 -# +# name: Vol_up type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 04 00 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 05 00 00 00 -# +# name: Mute type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 06 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4567 4454 549 481 522 481 523 480 498 506 498 1509 499 1509 523 480 499 505 523 1484 523 1484 523 1485 521 1487 520 484 519 485 519 485 519 471 519 4488 518 485 519 485 518 485 519 485 518 485 519 485 519 485 519 485 519 1489 519 1489 518 1489 518 485 519 1489 518 1490 518 1490 517 1490 518 486 518 486 518 486 518 1490 518 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4571 4453 552 478 525 478 526 478 500 504 500 1507 501 1506 526 478 525 477 526 1481 526 1481 526 1482 524 1483 523 481 522 482 521 481 522 468 522 4484 521 482 521 482 521 482 522 481 522 1486 521 1486 521 1486 521 482 521 1486 522 1486 522 1486 522 482 522 482 522 482 522 482 522 1486 522 483 521 482 521 483 521 1487 521 55474 4547 4477 524 479 524 480 524 480 523 480 523 1485 523 1484 524 481 523 481 523 1485 523 1485 522 1485 523 1485 523 480 524 480 524 481 523 467 524 4484 523 481 523 481 523 481 523 481 523 1485 523 1485 523 1485 523 481 523 1485 523 1485 523 1485 523 481 523 481 523 481 523 481 523 1485 523 481 523 481 523 481 523 1486 522 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4572 4453 552 456 548 479 524 479 500 504 500 1482 526 1482 551 478 526 478 525 1483 524 1484 523 1486 521 1487 521 483 521 483 521 483 521 470 520 4487 521 483 521 483 521 483 521 483 521 483 521 483 521 483 521 1488 520 1488 520 1488 520 1488 520 483 521 1488 520 1488 520 1488 520 483 521 483 521 483 521 483 521 1488 520 55457 4565 4483 521 483 521 483 521 483 521 483 520 1488 520 1487 521 483 521 483 520 1488 520 1488 520 1488 520 1488 520 483 520 484 520 484 520 470 521 4487 520 484 520 483 520 484 520 484 520 484 520 484 519 484 520 1488 520 1488 520 1488 519 1488 520 484 520 1488 520 1488 520 1488 520 484 520 484 520 484 520 484 520 1489 519 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 4573 4451 552 477 527 478 526 477 526 478 501 1507 501 1506 502 502 527 477 526 1481 527 1481 527 1481 526 1482 525 479 524 480 523 481 523 468 523 4485 522 481 522 482 522 482 522 482 522 1486 522 482 521 482 522 482 522 1486 522 1486 522 1486 522 482 522 482 521 1487 521 1487 521 1487 521 482 521 483 521 483 521 1487 521 55462 4547 4474 524 478 525 479 524 480 523 480 523 1484 523 1484 523 480 523 480 523 1484 523 1484 524 1484 523 1485 522 480 523 480 524 480 523 467 523 4484 522 480 524 481 523 481 523 481 523 1485 523 481 522 481 523 481 523 1485 522 1485 523 1485 523 481 523 481 522 1485 522 1485 522 1485 523 481 522 481 522 481 523 1485 523 -# +# name: Vol_dn type: parsed protocol: NECext address: 10 E7 00 00 command: 49 B6 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 10 E7 00 00 command: 05 FA 00 00 -# +# name: Mute type: parsed protocol: NEC address: 20 00 00 00 command: 50 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 47 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 80 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 80 00 00 00 command: 05 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 80 00 00 00 command: 04 00 00 00 -# +# name: Mute type: parsed protocol: NEC42 address: 6E 00 00 00 command: 4C 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 01 00 00 00 command: 11 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 01 00 00 00 command: 10 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4555 4463 532 473 532 473 531 474 530 474 530 1478 531 1478 531 475 529 476 528 1482 551 1480 529 1480 528 1481 528 477 527 478 527 478 526 478 527 4498 526 477 527 477 527 478 526 478 526 478 526 478 526 478 527 478 526 1483 527 1483 526 1483 526 478 526 1483 527 1483 527 1483 526 1483 526 478 526 478 527 478 527 1484 526 55527 4527 4492 526 478 526 478 526 478 526 479 526 1483 527 1483 526 478 526 478 526 1483 527 1483 526 1483 526 1483 526 478 527 478 526 479 526 478 526 4497 526 478 526 478 526 478 526 478 526 478 526 478 526 478 526 478 526 1484 525 1483 526 1484 525 478 526 1484 525 1484 525 1484 525 1484 525 478 526 479 526 479 525 1484 525 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4552 4463 531 474 530 474 530 474 530 474 530 1478 531 1478 531 475 529 476 553 1455 554 1478 530 1479 529 1481 527 477 527 478 526 478 526 478 526 4497 526 478 526 478 526 478 526 477 527 1483 526 1482 527 1482 527 478 526 1483 526 1483 526 1483 526 478 526 478 526 478 526 478 526 1483 526 478 526 478 526 478 526 1483 526 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 325 50440 173 137541 4551 4465 530 475 529 475 529 474 530 474 530 1479 530 1479 529 476 528 477 527 1480 554 1457 551 1480 528 1481 527 477 527 478 526 478 526 478 526 4497 525 478 526 478 526 478 526 478 526 479 525 479 525 479 525 1484 525 1483 526 1484 525 1483 526 479 525 1483 526 1483 526 1483 526 479 525 479 525 479 525 479 525 1484 525 -# +# name: Power type: parsed protocol: NECext address: 30 FC 00 00 command: 10 EF 00 00 -# +# name: Mute type: parsed protocol: NECext address: 30 FC 00 00 command: 0C F3 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 30 FC 00 00 command: 0D F2 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 30 FC 00 00 command: 17 E8 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 04 00 00 00 -# +# name: Power type: parsed protocol: NEC address: FD 00 00 00 command: E2 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: FD 00 00 00 command: E1 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: FD 00 00 00 command: E7 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: FD 00 00 00 command: B9 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 2760 833 499 418 470 419 416 887 445 872 893 468 416 469 414 442 443 441 444 440 446 440 446 440 446 440 892 882 445 444 466 446 439 447 438 448 437 449 884 447 437 450 437 894 436 449 437 449 437 449 437 449 437 449 884 448 436 894 437 450 436 116126 2673 887 445 473 440 449 439 893 438 880 884 448 437 449 437 449 437 449 437 449 437 449 437 449 437 449 884 890 437 449 437 449 437 450 436 449 437 449 884 448 436 451 437 894 436 449 437 450 436 450 436 449 437 450 883 448 436 894 436 449 437 116123 2672 888 469 449 439 450 437 893 438 881 883 448 437 449 437 449 437 449 437 449 438 449 437 449 437 449 884 891 437 449 437 449 437 449 437 449 437 449 884 448 436 451 437 894 437 449 437 449 437 449 437 449 437 449 884 448 436 894 437 449 437 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 2701 861 496 420 445 444 444 885 446 871 892 441 444 441 444 441 471 415 471 415 470 417 467 444 440 446 883 891 436 449 437 449 437 450 436 450 436 449 883 448 437 451 436 894 436 450 436 450 436 450 436 450 436 450 882 449 436 894 883 116552 2698 862 469 448 438 449 438 894 437 880 884 448 437 449 437 448 438 449 437 449 437 449 437 449 437 449 884 890 437 449 437 449 437 449 437 449 437 449 884 448 437 450 437 893 437 449 437 449 437 449 437 449 437 449 884 448 436 894 883 -# +# name: Vol_up type: parsed protocol: NECext address: 10 E7 00 00 command: 3C C3 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 10 E7 00 00 command: 4D B2 00 00 -# +# name: Power type: parsed protocol: Kaseikyo address: 52 54 32 00 command: 83 00 00 00 -# +# name: Mute type: parsed protocol: Kaseikyo address: 52 54 32 00 command: 86 00 00 00 -# +# name: Vol_up type: parsed protocol: Kaseikyo address: 52 54 32 00 command: 84 00 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo address: 52 54 32 00 command: 85 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 46 00 00 00 -# +# name: Prev type: parsed protocol: RC5 address: 14 00 00 00 command: 2C 00 00 00 -# +# name: Next type: parsed protocol: RC5 address: 14 00 00 00 command: 2B 00 00 00 -# +# name: Next type: parsed protocol: NECext address: D2 03 00 00 command: 1D E2 00 00 -# +# name: Next type: parsed protocol: RC5 address: 14 00 00 00 command: 34 00 00 00 -# +# name: Prev type: parsed protocol: SIRC20 address: 10 01 00 00 command: 30 00 00 00 -# +# name: Next type: parsed protocol: SIRC20 address: 10 01 00 00 command: 31 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 78 00 00 00 command: 03 00 00 00 -# +# name: Next type: parsed protocol: NECext address: 7F 01 00 00 command: 6B 94 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 09 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 00 00 00 00 command: 15 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 00 00 00 00 command: 07 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 04 00 00 00 command: 12 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 04 00 00 00 command: 04 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 04 00 00 00 command: 5E 00 00 00 -# +# name: Pause type: parsed protocol: RC5 address: 14 00 00 00 command: 30 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 10 E7 00 00 command: 5F A0 00 00 -# +# name: Pause type: parsed protocol: NECext address: 10 E7 00 00 command: 5F A0 00 00 -# +# name: Next type: parsed protocol: NEC address: 00 00 00 00 command: 02 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 00 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 30 FC 00 00 command: 02 FD 00 00 -# +# name: Pause type: parsed protocol: NECext address: 30 FC 00 00 command: 02 FD 00 00 -# +# name: Play type: parsed protocol: NECext address: D2 03 00 00 command: 1B E4 00 00 -# +# name: Pause type: parsed protocol: NECext address: D2 03 00 00 command: 1F E0 00 00 -# +# name: Prev type: parsed protocol: NECext address: D2 02 00 00 command: 90 6F 00 00 -# +# name: Next type: parsed protocol: NECext address: D2 02 00 00 command: 8F 70 00 00 -# +# name: Pause type: parsed protocol: NECext address: D2 02 00 00 command: 91 6E 00 00 -# +# name: Play type: parsed protocol: NECext address: D2 02 00 00 command: 8D 72 00 00 -# +# name: Play type: parsed protocol: NECext address: D2 19 00 00 command: 5C A3 00 00 -# +# name: Play type: parsed protocol: Kaseikyo address: AA 02 20 00 command: A0 00 00 00 -# +# name: Pause type: parsed protocol: NECext address: D2 19 00 00 command: 5C A3 00 00 -# +# name: Pause type: parsed protocol: Kaseikyo address: AA 02 20 00 command: A0 00 00 00 -# +# name: Next type: parsed protocol: Kaseikyo address: AC 02 20 01 command: A1 00 00 00 -# +# name: Prev type: parsed protocol: Kaseikyo address: AC 02 20 01 command: 91 00 00 00 -# +# name: Next type: parsed protocol: Kaseikyo address: AC 02 20 00 command: 31 00 00 00 -# +# name: Prev type: parsed protocol: Kaseikyo address: AC 02 20 00 command: 21 00 00 00 -# +# name: Play type: parsed protocol: Kaseikyo address: AC 02 20 00 command: 61 00 00 00 -# +# name: Play type: parsed protocol: RC5 address: 14 00 00 00 command: 35 00 00 00 -# +# name: Prev type: parsed protocol: RC5 address: 14 00 00 00 command: 21 00 00 00 -# +# name: Next type: parsed protocol: RC5 address: 14 00 00 00 command: 20 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 00 00 00 00 command: 4D 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 00 00 00 00 command: 54 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 40 00 00 00 command: 59 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 40 00 00 00 command: 18 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 40 00 00 00 command: 58 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 40 00 00 00 command: 5B 00 00 00 -# +# name: Play type: parsed protocol: SIRC20 address: 3A 07 00 00 command: 32 00 00 00 -# +# name: Pause type: parsed protocol: SIRC20 address: 3A 07 00 00 command: 39 00 00 00 -# +# name: Play type: parsed protocol: SIRC20 address: 10 01 00 00 command: 3A 00 00 00 -# +# name: Pause type: parsed protocol: SIRC20 address: 10 01 00 00 command: 3A 00 00 00 -# +# name: Play type: parsed protocol: RC5X address: 0A 00 00 00 command: 31 00 00 00 -# +# name: Pause type: parsed protocol: RC5X address: 0A 00 00 00 command: 0F 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 78 00 00 00 command: 02 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 7F 01 00 00 command: 68 97 00 00 -# +# name: Pause type: parsed protocol: NECext address: 7F 01 00 00 command: 67 98 00 00 -# +# name: Next type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 0E 00 00 00 -# +# name: Play type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 0A 00 00 00 -# +# name: Play type: parsed protocol: Samsung32 address: 10 00 00 00 command: 07 00 00 00 -# +# name: Pause type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 0A 00 00 00 -# +# name: Pause type: parsed protocol: Samsung32 address: 10 00 00 00 command: 07 00 00 00 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 8546 4227 563 511 563 1558 566 511 563 483 566 508 567 1558 566 509 566 1558 566 1586 564 483 566 1561 588 1558 566 1560 564 536 539 1558 566 535 539 1558 566 1586 564 1557 567 486 589 1560 564 511 563 510 539 508 567 485 589 484 565 508 567 1559 565 509 566 1560 564 1560 590 1585 539 25430 8575 4251 538 485 590 1557 567 486 588 511 538 536 539 1557 567 509 566 1558 566 1560 590 483 566 1560 589 1559 565 1562 562 536 539 1560 564 509 566 1558 566 1564 586 1586 538 484 590 1559 565 487 588 482 567 509 566 511 563 483 566 508 567 1558 566 509 566 1560 564 1559 591 1586 538 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 8571 4226 564 509 540 1587 563 510 539 510 565 510 564 1558 566 510 565 1584 540 1585 539 535 540 1587 537 1586 564 1585 539 510 564 1584 540 511 563 482 567 535 539 485 590 1584 540 1585 539 509 566 510 564 510 539 1586 564 1585 539 1585 539 536 539 510 564 1585 539 1557 567 1558 592 25434 8571 4199 591 510 539 1586 564 509 540 535 540 485 590 1585 539 484 591 1561 563 1584 540 507 568 1585 539 1586 564 1585 539 486 589 1585 539 484 591 425 624 535 540 486 589 1585 539 1585 539 511 564 511 563 486 563 1586 564 1558 566 1558 566 508 567 486 589 1560 564 1558 566 1561 589 -# +# name: Next type: parsed protocol: SIRC15 address: 64 00 00 00 command: 3B 00 00 00 -# +# name: Play type: parsed protocol: NEC42 address: 6E 00 00 00 command: 40 00 00 00 -# +# name: Pause type: parsed protocol: NEC42 address: 6E 00 00 00 command: 44 00 00 00 -# +# name: Prev type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 47 00 00 00 -# +# name: Next type: parsed protocol: Kaseikyo address: 51 54 32 01 command: 46 00 00 00 -# +# name: Play type: parsed protocol: Samsung32 address: 81 00 00 00 command: 01 00 00 00 -# +# name: Pause type: parsed protocol: Samsung32 address: 81 00 00 00 command: 01 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 4D 00 00 00 command: 0F 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 4D 00 00 00 command: 10 00 00 00 -# +# name: Play type: parsed protocol: SIRC15 address: 64 00 00 00 command: 32 00 00 00 -# +# name: Pause type: parsed protocol: SIRC15 address: 64 00 00 00 command: 39 00 00 00 -# +# name: Play type: parsed protocol: SIRC address: 0F 00 00 00 command: 2A 00 00 00 -# +# name: Pause type: parsed protocol: SIRC address: 0F 00 00 00 command: 29 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 86 61 00 00 command: 08 F7 00 00 -# +# name: Pause type: parsed protocol: NECext address: 86 61 00 00 command: 05 FA 00 00 -# +# name: Play type: parsed protocol: NECext address: 00 EF 00 00 command: 03 FC 00 00 -# +# name: Pause type: parsed protocol: NECext address: 00 EF 00 00 command: 03 FC 00 00 -# +# name: Next type: parsed protocol: NECext address: 00 EF 00 00 command: 02 FD 00 00 -# +# name: Prev type: parsed protocol: NECext address: 00 EF 00 00 command: 01 FE 00 00 -# +# name: Play type: parsed protocol: NECext address: 29 A1 00 00 command: 9A 65 00 00 -# +# name: Pause type: parsed protocol: NECext address: 29 A1 00 00 command: 9A 65 00 00 -# +# name: Next type: parsed protocol: Samsung32 address: 2C 00 00 00 command: 07 00 00 00 -# +# name: Prev type: parsed protocol: Samsung32 address: 2C 00 00 00 command: 06 00 00 00 -# +# name: Next type: parsed protocol: NECext address: C8 91 00 00 command: 24 DB 00 00 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4572 4451 552 478 526 478 526 478 500 503 501 1483 525 1482 551 478 525 478 525 1481 526 1482 524 1483 523 1485 522 482 521 482 522 482 522 469 521 4486 521 482 522 482 522 483 521 483 521 483 521 1487 521 483 521 1487 521 483 521 483 521 483 521 1487 521 1487 521 483 520 1487 521 483 521 1488 520 1487 521 1487 521 483 521 55487 4544 4482 522 482 522 482 522 482 522 482 522 1487 521 1487 521 482 522 482 522 1487 521 1487 521 1487 521 1487 521 482 522 483 521 483 521 469 522 4486 521 483 521 483 520 483 521 483 521 483 521 1487 521 483 521 1487 521 483 521 483 521 483 521 1488 521 1487 521 483 521 1488 520 483 521 1488 521 1488 521 1488 520 483 521 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4497 4438 513 508 492 503 487 509 491 504 486 1479 512 1479 511 510 490 505 485 1481 510 1481 510 1481 509 1482 509 512 488 507 483 513 487 508 482 4457 515 506 484 511 489 506 484 512 488 507 483 1482 509 513 487 1478 513 508 482 513 487 509 491 1473 518 1474 517 504 486 1479 512 510 490 1475 515 1475 516 1476 514 480 510 55019 4494 4442 509 511 489 507 483 512 488 507 483 1508 483 1508 483 513 487 508 482 1482 509 1483 508 1509 492 1499 492 504 486 510 490 505 485 510 490 4449 513 508 482 487 513 508 482 488 512 483 517 1499 492 504 486 1505 486 510 490 479 511 510 490 1501 490 1501 490 506 484 1506 485 511 489 1502 489 1476 515 1476 515 507 483 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 4572 4451 552 478 526 478 526 478 500 503 501 1483 525 1482 551 478 525 478 525 1481 526 1482 524 1483 523 1485 522 482 521 482 522 482 522 469 521 4486 521 482 522 482 522 483 521 483 521 483 521 1487 521 483 521 1487 521 483 521 483 521 483 521 1487 521 1487 521 483 520 1487 521 483 521 1488 520 1487 521 1487 521 483 521 55487 4544 4482 522 482 522 482 522 482 522 482 522 1487 521 1487 521 482 522 482 522 1487 521 1487 521 1487 521 1487 521 482 522 483 521 483 521 469 522 4486 521 483 521 483 520 483 521 483 521 483 521 1487 521 483 521 1487 521 483 521 483 521 483 521 1488 521 1487 521 483 521 1488 520 483 521 1488 521 1488 521 1488 520 483 521 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 4497 4438 513 508 492 503 487 509 491 504 486 1479 512 1479 511 510 490 505 485 1481 510 1481 510 1481 509 1482 509 512 488 507 483 513 487 508 482 4457 515 506 484 511 489 506 484 512 488 507 483 1482 509 513 487 1478 513 508 482 513 487 509 491 1473 518 1474 517 504 486 1479 512 510 490 1475 515 1475 516 1476 514 480 510 55019 4494 4442 509 511 489 507 483 512 488 507 483 1508 483 1508 483 513 487 508 482 1482 509 1483 508 1509 492 1499 492 504 486 510 490 505 485 510 490 4449 513 508 482 487 513 508 482 488 512 483 517 1499 492 504 486 1505 486 510 490 479 511 510 490 1501 490 1501 490 506 484 1506 485 511 489 1502 489 1476 515 1476 515 507 483 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 4503 4434 517 504 486 510 490 505 485 510 490 1475 515 1476 514 507 483 512 488 1477 513 1477 513 1478 512 1479 511 483 517 479 511 484 516 479 511 4455 516 478 512 483 517 479 511 485 515 480 510 486 514 1477 513 1477 513 482 518 477 513 483 517 1474 516 1474 516 1475 515 480 510 485 515 1476 514 1477 513 1477 513 483 517 55012 4497 4440 511 484 516 480 510 485 515 480 510 1481 509 1482 509 487 513 482 518 1473 517 1474 516 1474 516 1475 515 480 510 486 514 481 509 486 514 4451 510 485 567 428 510 486 514 481 509 487 565 430 570 1421 518 1473 518 478 512 483 517 478 512 1479 511 1480 510 1480 510 486 514 481 509 1481 510 1481 509 1482 509 487 513 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4583 4441 575 454 550 455 549 457 548 456 549 1461 550 1460 549 456 549 457 549 1461 548 1462 549 1461 549 1461 548 457 548 457 549 457 547 457 549 4462 548 458 548 457 548 457 548 457 548 457 548 1462 548 457 548 1461 549 457 548 457 548 457 548 1462 548 1461 549 457 548 1461 549 457 548 1461 549 1461 548 1462 547 458 547 55448 4580 4465 550 456 549 456 549 456 550 456 549 1461 549 1460 549 457 549 456 549 1461 549 1461 549 1461 548 1461 549 457 548 457 549 457 548 457 548 4462 549 457 548 457 548 458 548 457 548 457 548 1462 549 457 547 1462 549 457 548 457 548 457 548 1462 548 1462 549 457 548 1462 548 457 548 1462 548 1461 549 1462 548 457 548 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 4583 4441 575 454 550 455 549 457 548 456 549 1461 550 1460 549 456 549 457 549 1461 548 1462 549 1461 549 1461 548 457 548 457 549 457 547 457 549 4462 548 458 548 457 548 457 548 457 548 457 548 1462 548 457 548 1461 549 457 548 457 548 457 548 1462 548 1461 549 457 548 1461 549 457 548 1461 549 1461 548 1462 547 458 547 55448 4580 4465 550 456 549 456 549 456 550 456 549 1461 549 1460 549 457 549 456 549 1461 549 1461 549 1461 548 1461 549 457 548 457 549 457 548 457 548 4462 549 457 548 457 548 458 548 457 548 457 548 1462 549 457 547 1462 549 457 548 457 548 457 548 1462 548 1462 549 457 548 1462 548 457 548 1462 548 1461 549 1462 548 457 548 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 4582 4465 551 454 550 455 550 455 549 456 549 1460 550 1460 549 457 548 456 549 1461 549 1461 548 1461 549 1460 549 457 549 456 548 457 548 457 548 4461 549 456 548 457 548 457 548 457 548 457 548 457 548 1462 548 1461 547 457 549 457 548 456 549 1460 549 1461 547 1461 549 457 547 457 548 1461 549 1461 548 1461 548 457 548 55436 4578 4464 549 455 549 456 549 455 549 456 550 1460 549 1460 549 456 550 455 550 1460 550 1460 549 1460 550 1460 549 456 549 457 548 457 548 457 549 4461 548 457 548 457 548 457 548 457 547 457 549 457 547 1462 548 1461 549 457 548 457 548 457 548 1461 548 1461 548 1462 548 457 548 457 549 1461 549 1461 548 1461 548 457 548 -# +# name: Prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 4554 4466 540 469 547 490 515 495 521 490 494 1478 522 1477 544 466 539 471 513 1512 488 1484 516 1483 570 1429 540 497 519 465 540 470 546 491 493 4490 548 462 543 467 549 488 517 493 491 1481 519 1480 541 495 489 1510 522 462 543 494 521 489 495 1476 545 466 539 471 513 1485 547 464 520 1505 495 1477 513 1487 545 465 540 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 4555 4464 542 468 548 462 543 467 549 488 496 1477 523 1476 545 465 551 459 525 1475 515 1484 516 1483 517 1482 550 486 519 465 551 459 546 464 520 4490 547 488 517 467 549 462 543 467 548 462 543 467 517 1482 518 1481 540 496 520 464 541 469 515 1484 516 1483 517 1482 550 460 545 465 519 1480 520 1479 521 1478 543 493 522 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4549 4471 546 464 541 469 547 463 542 469 515 1484 516 1483 549 461 544 466 518 1481 519 1480 520 1479 521 1477 544 466 550 461 544 467 549 461 523 4486 541 469 546 464 541 469 547 464 541 469 515 1483 549 462 522 1477 544 465 540 471 545 465 572 1428 520 1479 542 468 569 1430 549 462 522 1476 514 1486 514 1485 547 463 542 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4552 4464 586 420 584 420 584 420 612 392 529 1478 531 1478 531 476 553 451 553 1455 554 1479 528 1480 528 1481 527 478 526 478 526 478 526 478 526 4497 526 478 526 478 526 478 526 478 526 478 526 1483 526 478 526 1483 526 478 526 478 526 478 526 1483 526 1483 526 478 526 1483 526 479 525 1483 526 1483 526 1483 526 478 526 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4548 4471 540 471 541 468 544 467 545 465 516 1482 512 1488 537 473 539 472 519 1479 514 1484 520 1479 515 1485 539 470 542 468 544 466 546 464 517 4493 538 472 540 470 542 468 544 466 546 464 517 1482 543 468 513 544 471 469 574 436 514 1485 519 1480 545 465 547 1452 573 438 543 1456 548 1451 543 1456 569 442 570 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 4549 4471 546 464 541 469 547 463 542 469 515 1484 516 1483 549 461 544 466 518 1481 519 1480 520 1479 521 1477 544 466 550 461 544 467 549 461 523 4486 541 469 546 464 541 469 547 464 541 469 515 1483 549 462 522 1477 544 465 540 471 545 465 572 1428 520 1479 542 468 569 1430 549 462 522 1476 514 1486 514 1485 547 463 542 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 4552 4464 586 420 584 420 584 420 612 392 529 1478 531 1478 531 476 553 451 553 1455 554 1479 528 1480 528 1481 527 478 526 478 526 478 526 478 526 4497 526 478 526 478 526 478 526 478 526 478 526 1483 526 478 526 1483 526 478 526 478 526 478 526 1483 526 1483 526 478 526 1483 526 479 525 1483 526 1483 526 1483 526 478 526 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 4548 4471 540 471 541 468 544 467 545 465 516 1482 512 1488 537 473 539 472 519 1479 514 1484 520 1479 515 1485 539 470 542 468 544 466 546 464 517 4493 538 472 540 470 542 468 544 466 546 464 517 1482 543 468 513 544 471 469 574 436 514 1485 519 1480 545 465 547 1452 573 438 543 1456 548 1451 543 1456 569 442 570 -# +# name: Next type: parsed protocol: SIRC20 address: 10 01 00 00 command: 34 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 80 00 00 00 command: 1B 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 80 00 00 00 command: 1B 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 80 00 00 00 command: 1F 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 41 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 41 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 86 FF 00 00 command: 2A D5 00 00 -# +# name: Prev type: parsed protocol: NECext address: 86 FF 00 00 command: 13 EC 00 00 -# +# name: Next type: parsed protocol: NECext address: 86 FF 00 00 command: 14 EB 00 00 -# +# name: Next type: parsed protocol: NECext address: BA A0 00 00 command: 5A A5 00 00 -# +# name: Prev type: parsed protocol: NECext address: BA A0 00 00 command: 59 A6 00 00 -# +# name: Pause type: parsed protocol: NECext address: BA A0 00 00 command: 56 A9 00 00 -# +# name: Play type: parsed protocol: NECext address: BA 4B 00 00 command: 55 AA 00 00 -# +# name: Play type: parsed protocol: NEC address: 35 00 00 00 command: 46 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 83 22 00 00 command: 09 F6 00 00 -# +# name: Pause type: parsed protocol: NECext address: BA 4B 00 00 command: 55 AA 00 00 -# +# name: Pause type: parsed protocol: NEC address: 35 00 00 00 command: 46 00 00 00 -# +# name: Pause type: parsed protocol: NECext address: 83 22 00 00 command: 09 F6 00 00 -# +# name: Prev type: parsed protocol: NECext address: 83 22 00 00 command: 07 F8 00 00 -# +# name: Next type: parsed protocol: NECext address: 83 22 00 00 command: 06 F9 00 00 -# +# name: Play type: parsed protocol: NECext address: 00 FB 00 00 command: 1C E3 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 1B 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 06 00 00 00 -# +# name: Pause type: parsed protocol: NECext address: 00 FB 00 00 command: 1C E3 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 1B 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 06 00 00 00 -# +# name: Play type: parsed protocol: Samsung32 address: 2C 00 00 00 command: 4F 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 20 00 00 00 command: 1A 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 20 00 00 00 command: 4F 00 00 00 -# +# name: Pause type: parsed protocol: NECext address: C8 91 00 00 command: 21 DE 00 00 -# +# name: Play type: parsed protocol: SIRC20 address: 10 01 00 00 command: 32 00 00 00 -# +# name: Pause type: parsed protocol: SIRC20 address: 10 01 00 00 command: 39 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 8E 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 8E 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 00 00 00 00 command: 8B 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 00 00 00 00 command: 8A 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 20 00 00 00 command: 1B 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 20 00 00 00 command: 1B 00 00 00 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 1041 1461 540 457 543 453 537 459 541 1459 541 1459 542 1459 541 1459 542 1458 542 1458 543 1458 542 1458 542 453 537 459 541 455 545 451 539 450 540 50518 1041 1461 540 457 543 452 538 459 541 1459 541 1459 542 1459 541 1458 543 1458 542 1458 542 1458 542 1457 544 453 537 459 541 455 545 451 539 449 541 50534 1036 1467 544 452 538 458 542 454 546 1455 545 1454 546 1454 536 1464 537 1464 536 1463 537 1463 537 1463 537 459 541 454 546 450 540 457 543 445 545 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 1041 1461 540 457 543 453 537 459 541 1459 541 1459 542 1459 541 1459 542 1458 542 1458 543 1458 542 1458 542 453 537 459 541 455 545 451 539 450 540 50518 1041 1461 540 457 543 452 538 459 541 1459 541 1459 542 1459 541 1458 543 1458 542 1458 542 1458 542 1457 544 453 537 459 541 455 545 451 539 449 541 50534 1036 1467 544 452 538 458 542 454 546 1455 545 1454 546 1454 536 1464 537 1464 536 1463 537 1463 537 1463 537 459 541 454 546 450 540 457 543 445 545 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 1044 1460 540 456 544 1457 543 1457 543 453 536 1465 545 450 539 457 543 1458 542 1458 542 454 546 451 539 1462 538 458 542 1459 541 1460 540 448 542 50525 1042 1462 538 458 542 1458 542 1459 541 455 545 1456 544 452 537 459 541 1460 540 1460 540 456 544 452 537 1464 536 460 540 1461 539 1461 539 449 540 50542 1046 1458 542 480 520 1455 545 1455 545 477 512 1463 547 474 515 481 519 1456 544 1457 543 478 522 475 514 1460 540 482 518 1457 543 1458 542 445 544 -# +# name: Prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 1046 1457 543 1458 542 454 546 450 539 1461 539 456 544 1458 542 1458 542 454 546 451 539 1461 539 1462 538 458 542 1459 541 454 546 451 539 1454 546 50538 1043 1461 539 1461 539 456 544 453 547 1454 546 449 541 1461 539 1462 538 457 543 453 547 1455 545 1455 545 451 539 1462 538 458 542 454 546 1447 543 50526 1044 1459 541 1459 541 455 545 451 539 1463 537 458 542 1459 541 1460 540 455 545 452 538 1463 537 1464 536 460 540 1461 539 456 544 453 536 1456 544 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 1013 1488 512 486 514 486 514 1485 515 484 516 484 516 1483 517 1482 518 1481 509 1490 510 1488 512 488 512 1487 513 1485 515 484 516 483 517 481 519 50939 1010 1489 511 488 512 488 512 1486 514 486 514 486 514 1485 515 1483 517 1482 518 1481 509 1489 511 489 511 1487 513 1485 515 484 516 484 516 482 518 50958 1013 1488 512 487 513 487 513 1485 515 485 515 485 515 1484 516 1483 517 1482 518 1481 509 1490 510 490 572 1426 512 1487 513 486 514 486 514 485 566 50908 1011 1490 510 489 511 489 511 1487 513 486 514 487 513 1485 515 1484 516 1483 517 1482 518 1480 510 491 571 1427 511 1488 512 488 512 487 513 486 565 50905 1013 1488 512 488 512 488 512 1486 514 486 514 486 514 1485 515 1484 516 1483 517 1481 509 1489 511 489 511 1488 512 1486 514 485 515 485 515 484 516 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 1013 1488 512 486 514 486 514 1485 515 484 516 484 516 1483 517 1482 518 1481 509 1490 510 1488 512 488 512 1487 513 1485 515 484 516 483 517 481 519 50939 1010 1489 511 488 512 488 512 1486 514 486 514 486 514 1485 515 1483 517 1482 518 1481 509 1489 511 489 511 1487 513 1485 515 484 516 484 516 482 518 50958 1013 1488 512 487 513 487 513 1485 515 485 515 485 515 1484 516 1483 517 1482 518 1481 509 1490 510 490 572 1426 512 1487 513 486 514 486 514 485 566 50908 1011 1490 510 489 511 489 511 1487 513 486 514 487 513 1485 515 1484 516 1483 517 1482 518 1480 510 491 571 1427 511 1488 512 488 512 487 513 486 565 50905 1013 1488 512 488 512 488 512 1486 514 486 514 486 514 1485 515 1484 516 1483 517 1481 509 1489 511 489 511 1488 512 1486 514 485 515 485 515 484 516 -# +# name: Next type: parsed protocol: NEC address: 00 00 00 00 command: 19 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 77 00 00 00 command: F8 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 10 E7 00 00 command: 14 EB 00 00 -# +# name: Pause type: parsed protocol: NEC address: 77 00 00 00 command: F8 00 00 00 -# +# name: Pause type: parsed protocol: NECext address: 10 E7 00 00 command: 14 EB 00 00 -# +# name: Prev type: parsed protocol: NECext address: 10 E7 00 00 command: 1E E1 00 00 -# +# name: Play type: parsed protocol: NECext address: 3F 5C 00 00 command: 07 F8 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 12 00 00 00 -# +# name: Pause type: parsed protocol: NECext address: 3F 5C 00 00 command: 07 F8 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 12 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 00 00 00 00 command: 10 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 00 00 00 00 command: 13 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 0A 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 0A 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 00 00 00 00 command: 0B 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 40 AF 00 00 command: 02 FD 00 00 -# +# name: Pause type: parsed protocol: NECext address: 40 AF 00 00 command: 02 FD 00 00 -# +# name: Prev type: parsed protocol: NECext address: 40 AF 00 00 command: 12 ED 00 00 -# +# name: Next type: parsed protocol: NECext address: 40 AF 00 00 command: 05 FA 00 00 -# +# name: Play type: parsed protocol: NECext address: 02 BD 00 00 command: 0D F2 00 00 -# +# name: Next type: parsed protocol: NECext address: 02 BD 00 00 command: 27 D8 00 00 -# +# name: Prev type: parsed protocol: NECext address: 02 BD 00 00 command: 25 DA 00 00 -# +# name: Play type: parsed protocol: NEC address: 80 00 00 00 command: 18 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 80 00 00 00 command: 18 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 80 00 00 00 command: 4B 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 80 00 00 00 command: 4A 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 80 00 00 00 command: 21 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 02 00 00 00 command: 1E 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 80 00 00 00 command: 21 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 02 00 00 00 command: 1E 00 00 00 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 3303 1908 408 1179 410 1177 412 442 409 444 407 446 405 448 414 1173 405 448 414 1173 416 1172 406 447 415 439 412 1175 414 440 411 1175 414 440 411 1175 414 440 411 442 409 444 407 447 415 438 413 440 411 442 409 444 407 446 405 1182 407 447 415 438 413 440 411 442 409 444 407 1180 409 1178 411 443 408 445 406 1180 409 445 406 447 415 438 413 440 411 442 409 444 407 1180 409 444 407 446 405 448 414 440 411 41789 3301 3346 362 42926 3307 3341 357 -# +# name: Prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 3301 1910 406 1155 434 1180 409 419 432 421 441 439 412 441 410 1178 411 416 435 1152 437 1177 412 442 409 417 434 1180 409 418 433 1180 409 445 406 1155 434 419 432 447 415 413 438 442 409 444 407 419 432 448 414 439 412 442 409 1177 412 442 409 444 407 420 431 448 414 413 438 1176 413 1174 415 413 438 1175 414 1173 405 422 440 440 411 416 435 418 433 447 415 439 412 441 410 1177 412 415 436 417 434 446 405 41164 3301 3321 387 42935 3307 3316 381 42941 3311 3312 385 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 9175 4433 644 1605 645 442 669 466 646 445 666 438 673 466 645 440 671 466 673 410 700 365 720 465 675 367 742 367 744 438 645 1604 675 1574 674 1549 702 1575 673 1576 646 1604 675 1574 676 1574 675 1576 644 1604 645 1606 672 1576 645 466 645 466 645 465 646 1575 675 1605 645 466 645 467 644 467 644 1605 644 1605 645 1606 644 467 644 467 644 1606 643 1578 672 1607 643 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 9146 4435 642 1577 673 449 662 454 658 451 661 469 642 468 644 469 642 453 659 470 642 470 642 469 643 469 642 469 642 469 642 1608 642 1606 643 1607 642 1607 642 1607 642 1577 673 1607 643 1607 642 1607 642 1607 642 1606 643 1576 674 1606 643 468 643 439 672 1607 642 1607 642 448 663 469 642 439 672 469 642 1577 672 1607 642 468 643 469 642 1607 642 1608 641 1608 640 -# +# name: Prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 9144 4405 671 1607 642 469 642 469 642 469 642 470 641 365 747 469 641 443 669 469 642 468 643 446 665 469 642 469 642 470 642 1580 669 1607 643 1607 642 1608 641 1578 672 1580 670 1608 641 1608 641 1609 590 1660 590 1635 615 1660 590 1660 590 1632 618 1633 617 521 590 1660 590 521 590 521 618 493 590 521 590 520 591 519 592 1657 592 519 592 1658 617 1633 616 1633 615 23844 9114 4461 615 -# +# name: Play type: parsed protocol: SIRC address: 10 00 00 00 command: 0C 00 00 00 -# +# name: Play type: parsed protocol: NEC address: FD 00 00 00 command: BD 00 00 00 -# +# name: Play type: parsed protocol: NECext address: 2D D3 00 00 command: 00 FF 00 00 -# +# name: Pause type: parsed protocol: NEC address: FD 00 00 00 command: BD 00 00 00 -# +# name: Pause type: parsed protocol: NECext address: 2D D3 00 00 command: 00 FF 00 00 -# +# name: Prev type: parsed protocol: NECext address: 2D D3 00 00 command: 07 F8 00 00 -# +# name: Next type: parsed protocol: NECext address: 2D D3 00 00 command: 06 F9 00 00 -# +# name: Next type: parsed protocol: NEC address: 77 00 00 00 command: F7 00 00 00 -# +# name: Next type: parsed protocol: NECext address: 10 E7 00 00 command: 08 F7 00 00 -# +# name: Play type: parsed protocol: NECext address: 10 E7 00 00 command: 5E A1 00 00 -# +# name: Pause type: parsed protocol: NECext address: 10 E7 00 00 command: 5E A1 00 00 -# +# name: Next type: parsed protocol: NECext address: 3F 5C 00 00 command: 16 E9 00 00 -# +# name: Prev type: parsed protocol: NECext address: 3F 5C 00 00 command: 17 E8 00 00 -# +# name: Prev type: parsed protocol: NEC address: 02 00 00 00 command: 02 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 02 00 00 00 command: 05 00 00 00 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 3309 1901 415 1173 405 1182 407 420 442 412 439 441 410 443 408 1179 410 444 407 1180 409 1179 410 444 407 420 431 1183 406 447 415 1147 442 412 439 1148 441 439 412 441 410 443 408 446 405 448 414 440 411 415 436 444 407 446 405 1182 407 447 415 412 439 440 411 443 408 418 433 1181 408 1180 409 418 433 447 415 439 412 441 410 443 408 445 406 1181 408 1180 409 1178 411 417 434 445 406 421 441 439 412 415 436 41161 3303 3320 388 42939 3301 3347 361 -# +# name: Pause type: raw frequency: 38000 duty_cycle: 0.330000 data: 3309 1901 415 1173 405 1182 407 420 442 412 439 441 410 443 408 1179 410 444 407 1180 409 1179 410 444 407 420 431 1183 406 447 415 1147 442 412 439 1148 441 439 412 441 410 443 408 446 405 448 414 440 411 415 436 444 407 446 405 1182 407 447 415 412 439 440 411 443 408 418 433 1181 408 1180 409 418 433 447 415 439 412 441 410 443 408 445 406 1181 408 1180 409 1178 411 417 434 445 406 421 441 439 412 415 436 41161 3303 3320 388 42939 3301 3347 361 -# +# name: Next type: parsed protocol: NEC address: FD 00 00 00 command: EA 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 45 00 00 00 -# +# name: Power type: parsed protocol: NEC42 address: 51 00 00 00 command: 00 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC42 address: 51 00 00 00 command: 1F 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC42 address: 51 00 00 00 command: 1E 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 10 E7 00 00 command: 00 FF 00 00 -# +# name: Mute type: parsed protocol: NECext address: 10 E7 00 00 command: 01 FE 00 00 -# +# name: Power type: parsed protocol: NECext address: 00 79 00 00 command: 80 7F 00 00 -# +# name: Mute type: parsed protocol: NECext address: 00 79 00 00 command: 99 66 00 00 -# +# name: Power type: parsed protocol: NECext address: D2 6C 00 00 command: 47 B8 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 55 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 40 00 00 00 command: 02 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 40 00 00 00 command: 01 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 40 00 00 00 command: 12 00 00 00 -# +# name: Power type: parsed protocol: RC5X address: 1B 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: RC5X address: 1B 00 00 00 command: 0D 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 01 00 00 00 -# +# name: Pause type: parsed protocol: Kaseikyo address: AC 02 20 00 command: 61 00 00 00 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 8560 4222 563 1558 563 511 562 1557 565 537 536 512 561 1558 564 488 585 1558 564 486 587 1559 563 486 587 1558 564 1557 565 509 565 1559 563 510 564 485 588 1557 565 487 586 1557 565 486 587 511 536 514 560 485 588 1557 565 487 586 1559 563 486 587 1558 564 1558 563 1585 562 1557 565 25407 8561 4220 564 1557 564 511 563 1558 563 508 566 485 588 1558 564 512 561 1557 565 486 587 1557 565 487 586 1558 564 1559 562 536 537 1558 564 508 565 487 586 1585 536 485 589 1558 564 486 587 485 563 511 562 487 586 1558 564 488 585 1557 565 485 588 1557 565 1558 564 1586 561 1556 566 25433 8534 4220 565 1584 537 536 538 1585 537 508 565 511 562 1584 538 511 562 1585 537 511 562 1584 538 511 562 1584 537 1584 538 536 537 1585 537 536 538 511 562 1584 538 510 563 1584 537 510 564 510 537 506 568 483 590 1584 538 511 562 1585 537 510 563 1584 538 1557 565 1585 562 1584 538 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 8562 4223 564 1557 591 509 538 1560 588 484 563 508 566 1558 563 508 566 1555 566 510 564 1556 565 536 538 1556 565 1558 590 483 564 1557 591 510 537 1559 589 1583 539 483 590 1555 567 510 563 482 565 536 538 484 589 509 538 510 565 1556 565 536 537 1585 537 1558 590 1583 539 1559 562 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 8571 4203 588 509 539 1561 589 510 538 536 539 510 565 1555 569 511 699 1449 539 1585 539 536 539 1558 566 1562 588 1585 539 511 563 1556 703 350 590 482 567 535 540 1560 564 1559 591 1584 540 485 590 510 539 507 568 1558 566 1586 564 510 539 508 567 511 563 1585 539 1585 539 1559 591 25433 8571 4228 563 510 539 1586 564 510 539 509 565 511 564 1558 566 486 589 1585 539 1585 539 536 539 1585 539 1586 621 1528 596 455 620 1501 623 454 620 453 674 400 596 1528 674 1425 647 1528 655 394 681 393 673 402 656 1468 672 1453 681 393 671 404 655 395 679 1469 655 1470 668 1457 678 -# +# name: Mute type: parsed protocol: NECext address: 83 22 00 00 command: 0C F3 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 1E 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 9028 3517 173 801 573 559 573 559 573 557 575 583 539 565 567 1676 568 590 542 563 569 1674 570 1675 569 1676 568 1677 567 1678 576 556 566 1679 575 1669 575 557 575 556 566 1678 576 1696 548 557 575 556 566 565 567 564 568 1675 569 1676 568 564 568 563 569 1674 570 1702 542 1703 541 1678 576 40469 9030 2232 568 96806 9035 2229 571 96799 9033 2230 570 -# +# name: Power type: parsed protocol: NEC address: 80 00 00 00 command: 1E 00 00 00 -# +# name: Pause type: parsed protocol: Samsung32 address: 2C 00 00 00 command: 4F 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 83 B6 00 00 command: 4D B2 00 00 -# +# name: Mute type: parsed protocol: NECext address: 83 B6 00 00 command: 43 BC 00 00 -# +# name: Play type: parsed protocol: NECext address: C8 91 00 00 command: 21 DE 00 00 -# +# name: Pause type: parsed protocol: NECext address: 86 FF 00 00 command: 2A D5 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 45 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 16 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 15 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 03 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 00 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 48 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 1D 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 1F 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 00 00 00 00 command: 1F 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 11 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 15 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 01 00 00 00 command: 02 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 09 00 00 00 -# +# name: Pause type: parsed protocol: SIRC address: 10 00 00 00 command: 0C 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 42 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 43 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 00 00 00 00 command: 40 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 00 00 00 00 command: 44 00 00 00 -# +# name: Pause type: parsed protocol: NEC @@ -3674,43 +3674,43 @@ type: parsed protocol: NEC address: 80 00 00 00 command: 05 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 80 00 00 00 command: 02 00 00 00 -# +# name: Power type: parsed protocol: RC5 address: 15 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: RC5 address: 15 00 00 00 command: 0D 00 00 00 -# +# name: Vol_up type: parsed protocol: RC5 address: 15 00 00 00 command: 10 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC5 address: 15 00 00 00 command: 11 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 00 00 00 00 command: 40 00 00 00 -# +# name: Next type: parsed protocol: NEC @@ -3752,25 +3752,25 @@ type: parsed protocol: NEC address: A0 00 00 00 command: 0A 00 00 00 -# +# name: Power type: parsed protocol: NECext address: D9 14 00 00 command: 6D 92 00 00 -# +# name: Mute type: parsed protocol: NECext address: D9 14 00 00 command: 6E 91 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: D9 14 00 00 command: 4F B0 00 00 -# +# name: Vol_down type: parsed protocol: NECext @@ -4058,175 +4058,175 @@ type: parsed protocol: NEC address: 00 00 00 00 command: 43 00 00 00 -# +# name: Power type: parsed protocol: NECext address: 84 79 00 00 command: 12 ED 00 00 -# +# name: Play type: parsed protocol: NECext address: 84 79 00 00 command: 0A F5 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 84 79 00 00 command: 05 FA 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 84 79 00 00 command: 00 FF 00 00 -# +# name: Next type: parsed protocol: NECext address: 84 79 00 00 command: 01 FE 00 00 -# +# name: Prev type: parsed protocol: NECext address: 84 79 00 00 command: 02 FD 00 00 -# +# name: Power type: parsed protocol: NEC address: 41 00 00 00 command: 42 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 41 00 00 00 command: 0B 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 41 00 00 00 command: 1B 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 41 00 00 00 command: 56 00 00 00 -# +# name: Power type: parsed protocol: NEC address: 04 00 00 00 command: 08 00 00 00 -# +# name: Play type: parsed protocol: NEC address: 04 00 00 00 command: B0 00 00 00 -# +# name: Pause type: parsed protocol: NEC address: 04 00 00 00 command: BA 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 04 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 04 00 00 00 command: 03 00 00 00 -# +# name: Prev type: parsed protocol: NEC address: 04 00 00 00 command: 8F 00 00 00 -# +# name: Next type: parsed protocol: NEC address: 04 00 00 00 command: 8E 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 04 00 00 00 command: 09 00 00 00 -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4637 4376 612 419 584 420 584 420 583 421 582 1427 531 1477 531 472 532 472 557 1452 556 1451 557 1451 557 1452 556 447 557 448 556 449 555 449 555 4453 554 450 554 450 554 451 553 450 554 451 553 451 553 451 553 450 554 1455 553 1454 554 1454 554 451 553 1454 554 1454 554 1455 553 1454 554 451 553 450 554 450 554 1455 553 55439 4554 4458 555 449 555 449 555 450 554 450 554 1455 553 1454 554 451 553 450 554 1454 554 1454 554 1454 554 1455 553 450 554 451 553 451 553 451 553 4453 554 451 553 451 552 451 553 451 553 451 553 451 553 451 553 451 553 1455 553 1455 553 1455 553 451 553 1455 553 1455 553 1455 553 1455 553 451 553 451 552 451 553 1455 553 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4636 4380 612 392 612 394 610 394 610 419 583 1399 557 1452 556 473 556 448 556 1428 581 1453 555 1453 555 1453 555 449 555 450 554 451 553 452 552 4457 551 452 552 452 552 452 552 452 552 452 552 1457 551 452 552 1457 551 452 552 452 552 453 551 1457 552 1457 551 452 552 1457 551 452 552 1457 551 1457 551 1457 552 452 552 55450 4551 4461 553 451 553 452 552 452 552 452 552 1456 552 1456 552 452 552 452 552 1456 552 1456 552 1456 552 1456 552 452 552 452 552 453 551 453 551 4456 551 453 551 453 551 453 551 453 551 453 551 1457 551 453 551 1457 552 453 551 454 550 454 550 1457 552 1457 551 454 550 1457 551 454 550 1458 551 1457 551 1458 550 454 550 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4640 4405 583 420 583 421 582 421 583 422 581 1427 531 1478 530 473 531 472 557 1452 557 1452 556 1452 556 1452 556 448 556 448 556 449 555 450 554 4454 554 451 553 451 553 451 553 451 553 1455 554 1455 553 1455 553 451 553 1455 553 1456 553 1456 553 451 553 451 553 451 553 451 554 1455 554 451 553 452 553 451 553 1456 553 55447 4556 4458 555 449 555 450 554 450 554 450 554 1455 553 1455 553 451 553 451 553 1455 553 1455 553 1455 553 1455 553 451 553 451 553 451 553 451 553 4454 553 451 553 450 554 451 553 450 554 1455 553 1455 553 1455 553 451 553 1455 553 1455 553 1455 553 451 553 451 553 451 553 450 554 1455 553 451 553 451 553 451 553 1455 553 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4636 4378 613 393 611 392 612 393 611 393 557 1451 558 1450 610 420 583 421 557 1427 581 1452 556 1452 556 1452 556 448 555 449 555 450 554 450 554 4455 553 451 553 451 553 451 553 451 553 451 553 451 553 452 552 1456 553 1456 552 1456 553 1456 552 451 553 1456 553 1456 552 1456 553 451 553 451 553 452 552 451 553 1456 552 55452 4553 4461 553 450 554 451 553 451 553 451 553 1456 553 1456 552 451 553 451 553 1456 552 1455 553 1455 553 1455 553 451 553 451 553 451 553 451 553 4456 552 451 553 451 553 451 553 451 553 451 553 451 553 451 553 1456 552 1455 553 1455 553 1455 553 451 553 1455 553 1456 552 1456 552 451 553 451 553 451 553 451 553 1456 552 -# +# name: Prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 255 113623 4638 4378 613 391 612 392 559 446 558 446 558 1477 531 1477 532 472 532 472 532 1476 532 1476 532 1476 532 1477 531 473 555 449 555 449 555 450 554 4455 554 450 554 450 554 450 554 450 554 1455 554 1455 554 450 554 1455 554 450 555 450 554 450 554 1455 554 451 553 451 553 1455 554 450 554 1455 554 1456 553 1455 554 450 554 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 4557 4430 611 392 610 394 559 445 559 446 558 1451 558 1477 531 448 556 472 532 1476 532 1477 532 1477 531 1477 531 473 556 449 555 449 555 450 554 4454 554 450 554 450 554 450 554 450 555 450 554 450 554 1455 554 1455 554 450 554 450 554 450 554 1455 554 1455 554 1455 553 451 553 450 554 1455 554 1455 554 1455 554 450 554 55458 4555 4459 554 450 554 450 554 450 554 450 554 1455 553 1455 553 450 554 450 554 1455 553 1455 553 1455 553 1455 553 450 554 450 554 450 554 450 554 4454 554 450 554 450 554 450 554 451 553 450 554 450 554 1455 553 1455 553 451 553 450 554 450 554 1455 553 1455 553 1455 553 450 554 451 553 1455 554 1455 553 1455 553 450 554 -# +# name: Mute type: raw frequency: 38000 duty_cycle: 0.330000 data: 4639 4406 586 418 585 393 559 447 557 447 557 1477 532 1477 532 472 532 472 532 1476 533 1476 532 1476 532 1476 532 473 555 449 555 449 555 449 555 4455 554 450 554 450 554 450 554 450 554 1455 554 450 554 450 554 450 554 1455 554 1455 553 1455 553 450 554 450 554 1455 554 1455 554 1455 554 450 554 450 554 450 554 1455 554 55454 4557 4458 555 449 555 449 555 450 554 450 554 1455 554 1455 553 450 554 450 554 1455 554 1455 554 1454 554 1455 554 450 554 450 554 450 555 450 554 4455 553 450 554 450 554 450 554 450 554 1455 554 450 554 450 554 450 554 1455 554 1455 554 1455 553 450 554 450 554 1455 554 1455 553 1455 554 450 554 450 554 450 554 1455 554 -# +# name: Next type: parsed protocol: NECext address: 10 E7 00 00 command: 02 FD 00 00 -# +# name: Next type: parsed protocol: NECext address: 30 FC 00 00 command: 13 EC 00 00 -# +# name: Prev type: parsed protocol: NECext address: 30 FC 00 00 command: 11 EE 00 00 -# +# name: Power type: parsed protocol: NECext From cfb9c991cb025445091bfc0bb9ef23d15cc0a4b2 Mon Sep 17 00:00:00 2001 From: Nikolay Marchuk Date: Sun, 6 Oct 2024 22:56:35 +0700 Subject: [PATCH 116/236] furi_hal_random: Wait for ready state and no errors before sampling (#3933) When random output is not ready, but error state flags are not set, sampling of random generator samples zero until next value is ready. --- targets/f7/furi_hal/furi_hal_random.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f7/furi_hal/furi_hal_random.c b/targets/f7/furi_hal/furi_hal_random.c index 8b75a05c5b..6269a90e1b 100644 --- a/targets/f7/furi_hal/furi_hal_random.c +++ b/targets/f7/furi_hal/furi_hal_random.c @@ -11,7 +11,7 @@ #define TAG "FuriHalRandom" static uint32_t furi_hal_random_read_rng(void) { - while(LL_RNG_IsActiveFlag_CECS(RNG) && LL_RNG_IsActiveFlag_SECS(RNG) && + while(LL_RNG_IsActiveFlag_CECS(RNG) || LL_RNG_IsActiveFlag_SECS(RNG) || !LL_RNG_IsActiveFlag_DRDY(RNG)) { /* Error handling as described in RM0434, pg. 582-583 */ if(LL_RNG_IsActiveFlag_CECS(RNG)) { From 6ead328bb7e703dc0d0db40cc05ed45ce3bcb5bf Mon Sep 17 00:00:00 2001 From: assasinfil Date: Sun, 6 Oct 2024 19:33:07 +0300 Subject: [PATCH 117/236] Moscow social card parser (#3464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated troyka layout (full version) * Changed to furi func * Small refactor * Bitlib refactor * Moved to API * Rollback troyka parser * Fix functions * Added MSK Social card parser * Parser func refactor start * Layout E3 refactored * Layout E4 refactored * Layout 6 refactored * Layout E5 refactored * Layout 2 refactored * Layout E5 fix * Layout E6 refactored, valid_date need fix * Layout E6 fix * Layout FCB refactored * Layout F0B refactored * Layout 8 refactored * Layout A refactored * Layout C refactored * Layout D refactored * Layout E1 refactored * Layout E2 refactored * Old code cleanup * Memory cleanup * Unused imports cleanup * Keys struct refactor * Keys struct refactor * Layout E1 fix * Added debug info for layout and department * Fix social card parse validation * Added card number validation * Added transport data ui improvements from Astrrra's troyka render func. Co-authored-by: gornekich Co-authored-by: あく --- .../nfc/api/mosgortrans/mosgortrans_util.c | 14 + .../nfc/api/mosgortrans/mosgortrans_util.h | 5 + .../main/nfc/api/nfc_app_api_table_i.h | 9 +- applications/main/nfc/application.fam | 9 + .../plugins/supported_cards/social_moscow.c | 301 ++++++++++++++++++ .../main/nfc/plugins/supported_cards/troika.c | 36 +-- 6 files changed, 349 insertions(+), 25 deletions(-) create mode 100644 applications/main/nfc/plugins/supported_cards/social_moscow.c diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c index 3138d790b3..261f24ce00 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -2,6 +2,20 @@ #define TAG "Mosgortrans" +void render_section_header( + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + for(uint8_t i = 0; i < prefix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } + furi_string_cat_printf(str, "[ %s ]", name); + for(uint8_t i = 0; i < suffix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } +} + void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) { uint32_t timestamp = days * 24 * 60 * 60; DateTime start_datetime = {0}; diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h index 2dc469c45c..e8cbd7a37d 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h @@ -10,6 +10,11 @@ extern "C" { #endif +void render_section_header( + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt); bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result); #ifdef __cplusplus diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index bf0e926ee6..790fa57664 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -15,4 +15,11 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( API_METHOD( mosgortrans_parse_transport_block, bool, - (const MfClassicBlock* block, FuriString* result)))); + (const MfClassicBlock* block, FuriString* result)), + API_METHOD( + render_section_header, + void, + (FuriString * str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt)))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 898434bb78..180be62243 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -92,6 +92,15 @@ App( sources=["plugins/supported_cards/troika.c"], ) +App( + appid="social_moscow_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="social_moscow_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/social_moscow.c"], +) + App( appid="washcity_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c new file mode 100644 index 0000000000..ed2ee6c1d4 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -0,0 +1,301 @@ +#include "nfc_supported_card_plugin.h" +#include + +#include + +#include +#include +#include +#include "../../api/mosgortrans/mosgortrans_util.h" +#include "furi_hal_rtc.h" + +#define TAG "Social_Moscow" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + const MfClassicKeyPair* keys; + uint32_t data_sector; +} SocialMoscowCardConfig; + +static const MfClassicKeyPair social_moscow_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, + {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, + {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, + {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, + {.a = 0x40ead80721ce, .b = 0x100533b89331}, + {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}}; + +static const MfClassicKeyPair social_moscow_4k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //1 + {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, //2 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //3 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //4 + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, //5 + {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, //6 + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, //7 + {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, //8 + {.a = 0x40ead80721ce, .b = 0x100533b89331}, //9 + {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, //10 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //11 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //12 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //13 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //14 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //15 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //16 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //17 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //18 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //19 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //20 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //21 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //22 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //23 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //24 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //25 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //26 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //27 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //28 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //29 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //30 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //31 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //32 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //33 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //34 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //35 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //36 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //37 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //38 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //39 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //40 +}; + +static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) { + bool success = true; + if(type == MfClassicType1k) { + config->data_sector = 15; + config->keys = social_moscow_1k_keys; + } else if(type == MfClassicType4k) { + config->data_sector = 15; + config->keys = social_moscow_4k_keys; + } else { + success = false; + } + + return success; +} + +static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); + + MfClassicKey key = {0}; + bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + FURI_LOG_D(TAG, "Verify success!"); + verified = true; + } while(false); + + return verified; +} + +static bool social_moscow_verify(Nfc* nfc) { + return social_moscow_verify_type(nfc, MfClassicType1k) || + social_moscow_verify_type(nfc, MfClassicType4k); +} + +static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType4k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, data->type)) break; + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (error == MfClassicErrorNone); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static uint8_t calculate_luhn(uint64_t number) { + // https://en.wikipedia.org/wiki/Luhn_algorithm + // Drop existing check digit to form payload + uint64_t payload = number / 10; + int sum = 0; + int position = 0; + + while(payload > 0) { + int digit = payload % 10; + if(position % 2 == 0) { + digit *= 2; + } + if(digit > 9) { + digit = (digit / 10) + (digit % 10); + } + sum += digit; + payload /= 10; + position++; + } + + return (10 - (sum % 10)) % 10; +} + +static uint64_t hex_num(uint64_t hex) { + uint64_t result = 0; + for(uint8_t i = 0; i < 8; ++i) { + uint8_t half_byte = hex & 0x0F; + uint64_t num = 0; + for(uint8_t j = 0; j < 4; ++j) { + num += (half_byte & 0x1) * (1 << j); + half_byte = half_byte >> 1; + } + result += num * pow(10, i); + hex = hex >> 4; + } + return result; +} + +static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, data->type)) break; + + // Verify key + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); + + const uint64_t key_a = + bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + const uint64_t key_b = + bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); + if((key_a != cfg.keys[cfg.data_sector].a) || (key_b != cfg.keys[cfg.data_sector].b)) break; + + uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24); + uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8); + uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40); + uint8_t card_control = bit_lib_get_bits(data->block[60].data, 80, 4); + uint64_t omc_number = bit_lib_get_bits_64(data->block[21].data, 8, 64); + uint8_t year = data->block[60].data[11]; + uint8_t month = data->block[60].data[12]; + + uint64_t number = hex_num(card_control) + hex_num(card_number) * 10 + + hex_num(card_region) * 10 * 10000000000 + + hex_num(card_code) * 10 * 10000000000 * 100; + + uint8_t luhn = calculate_luhn(number); + if(luhn != card_control) break; + + FuriString* metro_result = furi_string_alloc(); + FuriString* ground_result = furi_string_alloc(); + bool is_metro_data_present = + mosgortrans_parse_transport_block(&data->block[4], metro_result); + bool is_ground_data_present = + mosgortrans_parse_transport_block(&data->block[16], ground_result); + furi_string_cat_printf( + parsed_data, + "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n", + card_code, + card_region, + card_number, + card_control, + omc_number, + month, + year, + data->block[60].data[13], + data->block[60].data[14]); + if(is_metro_data_present && !furi_string_empty(metro_result)) { + render_section_header(parsed_data, "Metro", 22, 21); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result)); + } + if(is_ground_data_present && !furi_string_empty(ground_result)) { + render_section_header(parsed_data, "Ground", 21, 20); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); + } + furi_string_free(ground_result); + furi_string_free(metro_result); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin social_moscow_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = social_moscow_verify, + .read = social_moscow_read, + .parse = social_moscow_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor social_moscow_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &social_moscow_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* social_moscow_plugin_ep() { + return &social_moscow_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 0c93fa59ae..bd36d40e5b 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -82,20 +82,6 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40 }; -static void troika_render_section_header( - FuriString* str, - const char* name, - uint8_t prefix_separator_cnt, - uint8_t suffix_separator_cnt) { - for(uint8_t i = 0; i < prefix_separator_cnt; i++) { - furi_string_cat_printf(str, ":"); - } - furi_string_cat_printf(str, "[ %s ]", name); - for(uint8_t i = 0; i < suffix_separator_cnt; i++) { - furi_string_cat_printf(str, ":"); - } -} - static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; @@ -212,23 +198,25 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { FuriString* ground_result = furi_string_alloc(); FuriString* tat_result = furi_string_alloc(); - bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result); - bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result); - bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result); + bool is_metro_data_present = + mosgortrans_parse_transport_block(&data->block[32], metro_result); + bool is_ground_data_present = + mosgortrans_parse_transport_block(&data->block[28], ground_result); + bool is_tat_data_present = mosgortrans_parse_transport_block(&data->block[16], tat_result); furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); - if(result1 && !furi_string_empty(metro_result)) { - troika_render_section_header(parsed_data, "Metro", 22, 21); + if(is_metro_data_present && !furi_string_empty(metro_result)) { + render_section_header(parsed_data, "Metro", 22, 21); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result)); } - if(result2 && !furi_string_empty(ground_result)) { - troika_render_section_header(parsed_data, "Ediny", 22, 22); + if(is_ground_data_present && !furi_string_empty(ground_result)) { + render_section_header(parsed_data, "Ediny", 22, 22); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); } - if(result3 && !furi_string_empty(tat_result)) { - troika_render_section_header(parsed_data, "TAT", 24, 23); + if(is_tat_data_present && !furi_string_empty(tat_result)) { + render_section_header(parsed_data, "TAT", 24, 23); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result)); } @@ -236,7 +224,7 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_free(ground_result); furi_string_free(metro_result); - parsed = result1 || result2 || result3; + parsed = is_metro_data_present || is_ground_data_present || is_tat_data_present; } while(false); return parsed; From c3dc0ae6b985d4420b94376bebdd34a6dd90fbc8 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Sun, 6 Oct 2024 19:48:12 +0300 Subject: [PATCH 118/236] Plantain parser improvements (#3469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactored card nubmer and balance * Podorozhnik refactor * Balance fix and BSK card support added Co-authored-by: あく --- .../nfc/plugins/supported_cards/plantain.c | 112 +++++++++++++++--- 1 file changed, 93 insertions(+), 19 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index bed9645546..59253194ee 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -4,10 +4,21 @@ #include #include +#include #include #define TAG "Plantain" +void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + typedef struct { uint64_t a; uint64_t b; @@ -208,29 +219,92 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // Point to block 0 of sector 4, value 0 - const uint8_t* temp_ptr = data->block[16].data; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = data->block[0].data; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t + furi_string_printf(parsed_data, "\e#Plantain card\n"); uint64_t card_number = 0; for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; + card_number = (card_number << 8) | data->block[0].data[6 - i]; } - furi_string_printf( - parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance); + // Print card number with 4-digit groups + furi_string_cat_printf(parsed_data, "Number: "); + FuriString* card_number_s = furi_string_alloc(); + furi_string_cat_printf(card_number_s, "%lld", card_number); + FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 "); + for(uint8_t i = 0; i < 24; i += 4) { + for(uint8_t j = 0; j < 4; j++) { + furi_string_push_back(tmp_s, furi_string_get_char(card_number_s, i + j)); + } + furi_string_push_back(tmp_s, ' '); + } + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s)); + if(data->type == MfClassicType1k) { + //balance + uint32_t balance = 0; + for(uint8_t i = 0; i < 4; i++) { + balance = (balance << 8) | data->block[16].data[3 - i]; + } + furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100); + + //trips + uint8_t trips_metro = data->block[21].data[0]; + uint8_t trips_ground = data->block[21].data[1]; + furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground); + //trip time + uint32_t last_trip_timestamp = 0; + for(uint8_t i = 0; i < 3; i++) { + last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i]; + } + DateTime last_trip = {0}; + from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010); + furi_string_cat_printf( + parsed_data, + "Trip start: %02d.%02d.%04d %02d:%02d\n", + last_trip.day, + last_trip.month, + last_trip.year, + last_trip.hour, + last_trip.minute); + //validator + uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4]; + furi_string_cat_printf(parsed_data, "Validator: %d\n", validator); + //tariff + uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6]; + furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100); + //trips in metro + furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro); + //trips on ground + furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground); + //last payment + uint32_t last_payment_timestamp = 0; + for(uint8_t i = 0; i < 3; i++) { + last_payment_timestamp = (last_payment_timestamp << 8) | + data->block[18].data[4 - i]; + } + DateTime last_payment_date = {0}; + from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010); + furi_string_cat_printf( + parsed_data, + "Last pay: %02d.%02d.%04d %02d:%02d\n", + last_payment_date.day, + last_payment_date.month, + last_payment_date.year, + last_payment_date.hour, + last_payment_date.minute); + //payment summ + uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; + furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); + furi_string_free(card_number_s); + furi_string_free(tmp_s); + } else if(data->type == MfClassicType4k) { + //trips + uint8_t trips_metro = data->block[36].data[0]; + uint8_t trips_ground = data->block[36].data[1]; + furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground); + //trips in metro + furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro); + //trips on ground + furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground); + } parsed = true; } while(false); From 8c14361e6ae213f8f827a44acf69d6e1d52d5e70 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:55:13 +0300 Subject: [PATCH 119/236] [FL-3830] Emulation freeze (#3930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../nfc/helpers/protocol_support/nfc_protocol_support.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 7a07404fdc..0d63dc56bb 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -559,6 +559,7 @@ static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) { */ enum { NfcSceneEmulateStateWidget, /**< Widget view is displayed. */ + NfcSceneEmulateStateWidgetLog, /**< Widget view with Log button is displayed */ NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */ }; @@ -633,12 +634,14 @@ static bool "Log", nfc_protocol_support_common_widget_callback, instance); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog); } // Update TextBox data text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); consumed = true; } else if(event.event == GuiButtonTypeCenter) { - if(state == NfcSceneEmulateStateWidget) { + if(state == NfcSceneEmulateStateWidgetLog) { view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); scene_manager_set_scene_state( instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox); @@ -649,7 +652,7 @@ static bool if(state == NfcSceneEmulateStateTextBox) { view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); scene_manager_set_scene_state( - instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog); consumed = true; } } From 0469ef0e55295bd19be82b9be32dd45c11ffd542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sun, 6 Oct 2024 19:36:05 +0100 Subject: [PATCH 120/236] FuriHal, drivers: rework gauge initialization routine (#3912) * FuriHal, drivers: rework gauge initialization, ensure that we can recover from any kind of internal/external issue * Make PVS happy * Format sources * bq27220: add gaps injection into write operations * Drivers: bq27220 cleanup and various fixes * Drivers: bq27220 verbose logging and full access routine fix * Drivers: better cfg mode exit handling in bq27220 driver * Drivers: rewrite bq27220 based on bqstudio+ev2400, experiments and guessing. Fixes all known issues. * PVS: hello license check * Drivers: minimize reset count in bq27220 init sequence * Drivers: bq27220 hide debug logging, reorganize routine to ensure predictable result and minimum amount of interaction with gauge, add documentation and notes. * Drivers: more reliable bq27220_full_access routine * Drivers: replace some warning with error in bq27220 * Drivers: move static asserts to headers in bq27220 * Fix PVS warnings * Drivers: simplify logic in bq27220 --------- Co-authored-by: hedger --- .../nfc/plugins/supported_cards/plantain.c | 2 +- lib/drivers/bq27220.c | 468 +++++++++++++++--- lib/drivers/bq27220.h | 267 ++++++++-- lib/drivers/bq27220_data_memory.h | 2 + lib/drivers/bq27220_reg.h | 136 ++--- scripts/fbt_tools/pvsstudio.py | 2 +- targets/f7/furi_hal/furi_hal_power.c | 24 +- 7 files changed, 698 insertions(+), 203 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 59253194ee..c38140de27 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -228,7 +228,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { // Print card number with 4-digit groups furi_string_cat_printf(parsed_data, "Number: "); FuriString* card_number_s = furi_string_alloc(); - furi_string_cat_printf(card_number_s, "%lld", card_number); + furi_string_cat_printf(card_number_s, "%llu", card_number); FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 "); for(uint8_t i = 0; i < 24; i += 4) { for(uint8_t j = 0; j < 4; j++) { diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index a3a88603d7..d60e287da4 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -1,29 +1,77 @@ - #include "bq27220.h" #include "bq27220_reg.h" #include "bq27220_data_memory.h" -_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); - #include #include #define TAG "Gauge" -static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { - uint16_t buf = 0; +#define BQ27220_ID (0x0220u) - furi_hal_i2c_read_mem( - handle, BQ27220_ADDRESS, address, (uint8_t*)&buf, 2, BQ27220_I2C_TIMEOUT); +/** Delay between 2 writes into Subclass/MAC area. Fails at ~120us. */ +#define BQ27220_MAC_WRITE_DELAY_US (250u) - return buf; +/** Delay between we ask chip to load data to MAC and it become valid. Fails at ~500us. */ +#define BQ27220_SELECT_DELAY_US (1000u) + +/** Delay between 2 control operations(like unseal or full access). Fails at ~2500us.*/ +#define BQ27220_MAGIC_DELAY_US (5000u) + +/** Delay before freshly written configuration can be read. Fails at ? */ +#define BQ27220_CONFIG_DELAY_US (10000u) + +/** Config apply delay. Must wait, or DM read returns garbage. */ +#define BQ27220_CONFIG_APPLY_US (2000000u) + +/** Timeout for common operations. */ +#define BQ27220_TIMEOUT_COMMON_US (2000000u) + +/** Timeout for reset operation. Normally reset takes ~2s. */ +#define BQ27220_TIMEOUT_RESET_US (4000000u) + +/** Timeout cycle interval */ +#define BQ27220_TIMEOUT_CYCLE_INTERVAL_US (1000u) + +/** Timeout cycles count helper */ +#define BQ27220_TIMEOUT(timeout_us) ((timeout_us) / (BQ27220_TIMEOUT_CYCLE_INTERVAL_US)) + +#ifdef BQ27220_DEBUG +#define BQ27220_DEBUG_LOG(...) FURI_LOG_D(TAG, ##__VA_ARGS__) +#else +#define BQ27220_DEBUG_LOG(...) +#endif + +static inline bool bq27220_read_reg( + FuriHalI2cBusHandle* handle, + uint8_t address, + uint8_t* buffer, + size_t buffer_size) { + return furi_hal_i2c_trx( + handle, BQ27220_ADDRESS, &address, 1, buffer, buffer_size, BQ27220_I2C_TIMEOUT); } -static bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { - bool ret = furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandControl, (uint8_t*)&control, 2, BQ27220_I2C_TIMEOUT); +static inline bool bq27220_write( + FuriHalI2cBusHandle* handle, + uint8_t address, + const uint8_t* buffer, + size_t buffer_size) { + return furi_hal_i2c_write_mem( + handle, BQ27220_ADDRESS, address, buffer, buffer_size, BQ27220_I2C_TIMEOUT); +} - return ret; +static inline bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { + return bq27220_write(handle, CommandControl, (uint8_t*)&control, 2); +} + +static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { + uint16_t buf = BQ27220_ERROR; + + if(!bq27220_read_reg(handle, address, (uint8_t*)&buf, 2)) { + FURI_LOG_E(TAG, "bq27220_read_word failed"); + } + + return buf; } static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) { @@ -56,49 +104,49 @@ static bool bq27220_parameter_check( if(update) { // Datasheet contains incorrect procedure for memory update, more info: // https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/719878/bq27220-technical-reference-manual-sluubd4-is-missing-extended-data-commands-chapter + // Also see note in the header - // 2. Write the address AND the parameter data to 0x3E+ (auto increment) - if(!furi_hal_i2c_write_mem( - handle, - BQ27220_ADDRESS, - CommandSelectSubclass, - buffer, - size + 2, - BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM write failed"); + // Write the address AND the parameter data to 0x3E+ (auto increment) + if(!bq27220_write(handle, CommandSelectSubclass, buffer, size + 2)) { + FURI_LOG_E(TAG, "DM write failed"); break; } - furi_delay_us(10000); + // We must wait, otherwise write will fail + furi_delay_us(BQ27220_MAC_WRITE_DELAY_US); - // 3. Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF + // Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF uint8_t checksum = bq27220_get_checksum(buffer, size + 2); - // 4. Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61 + // Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61 buffer[0] = checksum; // 2 bytes address, `size` bytes data, 1 byte check sum, 1 byte length buffer[1] = 2 + size + 1 + 1; - if(!furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "CRC write failed"); + if(!bq27220_write(handle, CommandMACDataSum, buffer, 2)) { + FURI_LOG_E(TAG, "CRC write failed"); break; } - - furi_delay_us(10000); + // Final wait as in gm.fs specification + furi_delay_us(BQ27220_CONFIG_DELAY_US); ret = true; } else { - if(!furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandSelectSubclass, buffer, 2, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM SelectSubclass for read failed"); + if(!bq27220_write(handle, CommandSelectSubclass, buffer, 2)) { + FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); break; } - if(!furi_hal_i2c_rx(handle, BQ27220_ADDRESS, old_data, size, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM read failed"); + // bqstudio uses 15ms wait delay here + furi_delay_us(BQ27220_SELECT_DELAY_US); + + if(!bq27220_read_reg(handle, CommandMACData, old_data, size)) { + FURI_LOG_E(TAG, "DM read failed"); break; } + // bqstudio uses burst reads with continue(CommandSelectSubclass without argument) and ~5ms between burst + furi_delay_us(BQ27220_SELECT_DELAY_US); + if(*(uint32_t*)&(old_data[0]) != *(uint32_t*)&(buffer[2])) { - FURI_LOG_W( //-V641 + FURI_LOG_E( //-V641 TAG, "Data at 0x%04x(%zu): 0x%08lx!=0x%08lx", address, @@ -119,22 +167,34 @@ static bool bq27220_data_memory_check( const BQ27220DMData* data_memory, bool update) { if(update) { - if(!bq27220_control(handle, Control_ENTER_CFG_UPDATE)) { + const uint16_t cfg_request = Control_ENTER_CFG_UPDATE; + if(!bq27220_write( + handle, CommandSelectSubclass, (uint8_t*)&cfg_request, sizeof(cfg_request))) { FURI_LOG_E(TAG, "ENTER_CFG_UPDATE command failed"); return false; }; // Wait for enter CFG update mode - uint32_t timeout = 100; - OperationStatus status = {0}; - while((status.CFGUPDATE != true) && (timeout-- > 0)) { - bq27220_get_operation_status(handle, &status); + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.CFGUPDATE) { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); } if(timeout == 0) { - FURI_LOG_E(TAG, "CFGUPDATE mode failed"); + FURI_LOG_E( + TAG, + "Enter CFGUPDATE mode failed, CFG %u, SEC %u", + operation_status.CFGUPDATE, + operation_status.SEC); return false; } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); } // Process data memory records @@ -179,43 +239,283 @@ static bool bq27220_data_memory_check( } // Finalize configuration update - if(update) { + if(update && result) { bq27220_control(handle, Control_EXIT_CFG_UPDATE_REINIT); - furi_delay_us(10000); + + // Wait for gauge to apply new configuration + furi_delay_us(BQ27220_CONFIG_APPLY_US); + + // ensure that we exited config update mode + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.CFGUPDATE != true) { + break; + } + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + // Check timeout + if(timeout == 0) { + FURI_LOG_E(TAG, "Exit CFGUPDATE mode failed"); + return false; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); } return result; } -bool bq27220_init(FuriHalI2cBusHandle* handle) { - // Request device number(chip PN) - if(!bq27220_control(handle, Control_DEVICE_NUMBER)) { - FURI_LOG_E(TAG, "Device is not present"); - return false; - }; - // Check control response - uint16_t data = 0; - data = bq27220_read_word(handle, CommandControl); - if(data != 0xFF00) { - FURI_LOG_E(TAG, "Invalid control response: %x", data); - return false; - }; +bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { + bool result = false; + bool reset_and_provisioning_required = false; + + do { + // Request device number(chip PN) + BQ27220_DEBUG_LOG("Checking device ID"); + if(!bq27220_control(handle, Control_DEVICE_NUMBER)) { + FURI_LOG_E(TAG, "ID: Device is not responding"); + break; + }; + // Enterprise wait(MAC read fails if less than 500us) + // bqstudio uses ~15ms + furi_delay_us(BQ27220_SELECT_DELAY_US); + // Read id data from MAC scratch space + uint16_t data = bq27220_read_word(handle, CommandMACData); + if(data != BQ27220_ID) { + FURI_LOG_E(TAG, "Invalid Device Number %04x != 0x0220", data); + break; + } + + // Unseal device since we are going to read protected configuration + BQ27220_DEBUG_LOG("Unsealing"); + if(!bq27220_unseal(handle)) { + break; + } + + // Try to recover gauge from forever init + BQ27220_DEBUG_LOG("Checking initialization status"); + Bq27220OperationStatus operation_status; + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Failed to get operation status"); + break; + } + if(!operation_status.INITCOMP || operation_status.CFGUPDATE) { + FURI_LOG_E(TAG, "Incorrect state, reset needed"); + reset_and_provisioning_required = true; + } + + // Ensure correct profile is selected + BQ27220_DEBUG_LOG("Checking chosen profile"); + Bq27220ControlStatus control_status; + if(!bq27220_get_control_status(handle, &control_status)) { + FURI_LOG_E(TAG, "Failed to get control status"); + break; + } + if(control_status.BATT_ID != 0) { + FURI_LOG_E(TAG, "Incorrect profile, reset needed"); + reset_and_provisioning_required = true; + } - data = bq27220_read_word(handle, CommandMACData); - FURI_LOG_I(TAG, "Device Number %04x", data); + // Ensure correct configuration loaded into gauge DataMemory + // Only if reset is not required, otherwise we don't + if(!reset_and_provisioning_required) { + BQ27220_DEBUG_LOG("Checking data memory"); + if(!bq27220_data_memory_check(handle, data_memory, false)) { + FURI_LOG_E(TAG, "Incorrect configuration data, reset needed"); + reset_and_provisioning_required = true; + } + } + + // Reset needed + if(reset_and_provisioning_required) { + FURI_LOG_W(TAG, "Resetting device"); + if(!bq27220_reset(handle)) { + FURI_LOG_E(TAG, "Failed to reset device"); + break; + } - return data == 0x0220; + // Get full access to read and modify parameters + // Also it looks like this step is totally unnecessary + BQ27220_DEBUG_LOG("Acquiring Full Access"); + if(!bq27220_full_access(handle)) { + break; + } + + // Update memory + FURI_LOG_W(TAG, "Updating data memory"); + bq27220_data_memory_check(handle, data_memory, true); + if(!bq27220_data_memory_check(handle, data_memory, false)) { + FURI_LOG_E(TAG, "Data memory update failed"); + break; + } + } + + BQ27220_DEBUG_LOG("Sealing"); + if(!bq27220_seal(handle)) { + FURI_LOG_E(TAG, "Seal failed"); + break; + } + + result = true; + } while(0); + + return result; } -bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { - FURI_LOG_I(TAG, "Verifying data memory"); - if(!bq27220_data_memory_check(handle, data_memory, false)) { - FURI_LOG_I(TAG, "Updating data memory"); - bq27220_data_memory_check(handle, data_memory, true); - } - FURI_LOG_I(TAG, "Data memory verification complete"); +bool bq27220_reset(FuriHalI2cBusHandle* handle) { + bool result = false; + do { + if(!bq27220_control(handle, Control_RESET)) { + FURI_LOG_E(TAG, "Reset request failed"); + break; + }; + + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_RESET_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.INITCOMP == true) { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + if(timeout == 0) { + FURI_LOG_E(TAG, "INITCOMP timeout after reset"); + break; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); + + result = true; + } while(0); + + return result; +} + +bool bq27220_seal(FuriHalI2cBusHandle* handle) { + Bq27220OperationStatus operation_status = {0}; + bool result = false; + do { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC == Bq27220OperationStatusSecSealed) { + result = true; + break; + } + + if(!bq27220_control(handle, Control_SEALED)) { + FURI_LOG_E(TAG, "Seal request failed"); + break; + } + + furi_delay_us(BQ27220_SELECT_DELAY_US); + + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecSealed) { + FURI_LOG_E(TAG, "Seal failed"); + break; + } + + result = true; + } while(0); + + return result; +} + +bool bq27220_unseal(FuriHalI2cBusHandle* handle) { + Bq27220OperationStatus operation_status = {0}; + bool result = false; + do { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecSealed) { + result = true; + break; + } + + // Hai, Kazuma desu + bq27220_control(handle, UnsealKey1); + furi_delay_us(BQ27220_MAGIC_DELAY_US); + bq27220_control(handle, UnsealKey2); + furi_delay_us(BQ27220_MAGIC_DELAY_US); - return true; + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecUnsealed) { + FURI_LOG_E(TAG, "Unseal failed %u", operation_status.SEC); + break; + } + + result = true; + } while(0); + + return result; +} + +bool bq27220_full_access(FuriHalI2cBusHandle* handle) { + bool result = false; + + do { + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + if(timeout == 0) { + FURI_LOG_E(TAG, "Failed to get operation status"); + break; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); + + // Already full access + if(operation_status.SEC == Bq27220OperationStatusSecFull) { + result = true; + break; + } + // Must be unsealed to get full access + if(operation_status.SEC != Bq27220OperationStatusSecUnsealed) { + FURI_LOG_E(TAG, "Not in unsealed state"); + break; + } + + // Explosion!!! + bq27220_control(handle, FullAccessKey); //-V760 + furi_delay_us(BQ27220_MAGIC_DELAY_US); + bq27220_control(handle, FullAccessKey); + furi_delay_us(BQ27220_MAGIC_DELAY_US); + + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecFull) { + FURI_LOG_E(TAG, "Full access failed %u", operation_status.SEC); + break; + } + + result = true; + } while(0); + + return result; } uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) { @@ -226,24 +526,30 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandCurrent); } -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status) { - uint16_t data = bq27220_read_word(handle, CommandBatteryStatus); - if(data == BQ27220_ERROR) { - return false; - } else { - *(uint16_t*)battery_status = data; - return true; - } +bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status) { + return bq27220_read_reg(handle, CommandControl, (uint8_t*)control_status, 2); +} + +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status) { + return bq27220_read_reg(handle, CommandBatteryStatus, (uint8_t*)battery_status, 2); +} + +bool bq27220_get_operation_status( + FuriHalI2cBusHandle* handle, + Bq27220OperationStatus* operation_status) { + return bq27220_read_reg(handle, CommandOperationStatus, (uint8_t*)operation_status, 2); } -bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status) { - uint16_t data = bq27220_read_word(handle, CommandOperationStatus); - if(data == BQ27220_ERROR) { +bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status) { + // Request gauging data to be loaded to MAC + if(!bq27220_control(handle, Control_GAUGING_STATUS)) { + FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); return false; - } else { - *(uint16_t*)operation_status = data; - return true; } + // Wait for data being loaded to MAC + furi_delay_us(BQ27220_SELECT_DELAY_US); + // Read id data from MAC scratch space + return bq27220_read_reg(handle, CommandMACData, (uint8_t*)gauging_status, 2); } uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle) { diff --git a/lib/drivers/bq27220.h b/lib/drivers/bq27220.h index fc76e318f8..cdfcb20b1a 100644 --- a/lib/drivers/bq27220.h +++ b/lib/drivers/bq27220.h @@ -1,3 +1,31 @@ +/** + * @file bq27220.h + * + * Quite problematic chip with quite bad documentation. + * + * Couple things to keep in mind: + * + * - Datasheet and technical reference manual are full of bullshit + * - bqstudio is ignoring them + * - bqstudio i2c exchange tracing gives some ideas on timings that works, but there is a catch + * - bqstudio timings contradicts to gm.fs file specification + * - it's impossible to reproduce all situations in bqstudio + * - experiments with blackbox can not cover all edge cases + * - final timings are kinda blend between all of those sources + * - device behavior differs depending on i2c clock speed + * - The Hero Himmel would not have used this gauge in the first place + * + * Couple advises if you'll need to modify this driver: + * - Reset and wait for INITCOMP if something is not right. + * - Do not do partial config update, it takes unpredictable amount of time to apply. + * - Don't forget to reset chip before writing new config. + * - If something fails at config update stage, wait for 4 seconds before doing next cycle. + * - If you can program and lock chip at factory stage - do it. It will save you a lot of time. + * - Keep sealed or strange things may happen. + * - There is a condition when it may stuck at INITCOMP state, just "press reset button". + * + */ + #pragma once #include @@ -9,26 +37,45 @@ typedef struct { // Low byte, Low bit first - bool DSG : 1; // The device is in DISCHARGE - bool SYSDWN : 1; // System down bit indicating the system should shut down - bool TDA : 1; // Terminate Discharge Alarm - bool BATTPRES : 1; // Battery Present detected - bool AUTH_GD : 1; // Detect inserted battery - bool OCVGD : 1; // Good OCV measurement taken - bool TCA : 1; // Terminate Charge Alarm - bool RSVD : 1; // Reserved + uint8_t BATT_ID : 3; /**< Battery Identification */ + bool SNOOZE : 1; /**< SNOOZE mode is enabled */ + bool BCA : 1; /**< fuel gauge board calibration routine is active */ + bool CCA : 1; /**< Coulomb Counter Calibration routine is active */ + uint8_t RSVD0 : 2; /**< Reserved */ // High byte, Low bit first - bool CHGINH : 1; // Charge inhibit - bool FC : 1; // Full-charged is detected - bool OTD : 1; // Overtemperature in discharge condition is detected - bool OTC : 1; // Overtemperature in charge condition is detected - bool SLEEP : 1; // Device is operating in SLEEP mode when set - bool OCVFAIL : 1; // Status bit indicating that the OCV reading failed due to current - bool OCVCOMP : 1; // An OCV measurement update is complete - bool FD : 1; // Full-discharge is detected -} BatteryStatus; - -_Static_assert(sizeof(BatteryStatus) == 2, "Incorrect structure size"); + uint8_t RSVD1; /**< Reserved */ +} Bq27220ControlStatus; + +_Static_assert(sizeof(Bq27220ControlStatus) == 2, "Incorrect Bq27220ControlStatus structure size"); + +typedef struct { + // Low byte, Low bit first + bool DSG : 1; /**< The device is in DISCHARGE */ + bool SYSDWN : 1; /**< System down bit indicating the system should shut down */ + bool TDA : 1; /**< Terminate Discharge Alarm */ + bool BATTPRES : 1; /**< Battery Present detected */ + bool AUTH_GD : 1; /**< Detect inserted battery */ + bool OCVGD : 1; /**< Good OCV measurement taken */ + bool TCA : 1; /**< Terminate Charge Alarm */ + bool RSVD : 1; /**< Reserved */ + // High byte, Low bit first + bool CHGINH : 1; /**< Charge inhibit */ + bool FC : 1; /**< Full-charged is detected */ + bool OTD : 1; /**< Overtemperature in discharge condition is detected */ + bool OTC : 1; /**< Overtemperature in charge condition is detected */ + bool SLEEP : 1; /**< Device is operating in SLEEP mode when set */ + bool OCVFAIL : 1; /**< Status bit indicating that the OCV reading failed due to current */ + bool OCVCOMP : 1; /**< An OCV measurement update is complete */ + bool FD : 1; /**< Full-discharge is detected */ +} Bq27220BatteryStatus; + +_Static_assert(sizeof(Bq27220BatteryStatus) == 2, "Incorrect Bq27220BatteryStatus structure size"); + +typedef enum { + Bq27220OperationStatusSecSealed = 0b11, + Bq27220OperationStatusSecUnsealed = 0b10, + Bq27220OperationStatusSecFull = 0b01, +} Bq27220OperationStatusSec; typedef struct { // Low byte, Low bit first @@ -40,53 +87,189 @@ typedef struct { bool SMTH : 1; /**< RemainingCapacity is scaled by smooth engine */ bool BTPINT : 1; /**< BTP threshold has been crossed */ // High byte, Low bit first - uint8_t RSVD1 : 2; + uint8_t RSVD1 : 2; /**< Reserved */ bool CFGUPDATE : 1; /**< Gauge is in CONFIG UPDATE mode */ - uint8_t RSVD0 : 5; -} OperationStatus; + uint8_t RSVD0 : 5; /**< Reserved */ +} Bq27220OperationStatus; -_Static_assert(sizeof(OperationStatus) == 2, "Incorrect structure size"); +_Static_assert( + sizeof(Bq27220OperationStatus) == 2, + "Incorrect Bq27220OperationStatus structure size"); + +typedef struct { + // Low byte, Low bit first + bool FD : 1; /**< Full Discharge */ + bool FC : 1; /**< Full Charge */ + bool TD : 1; /**< Terminate Discharge */ + bool TC : 1; /**< Terminate Charge */ + bool RSVD0 : 1; /**< Reserved */ + bool EDV : 1; /**< Cell voltage is above or below EDV0 threshold */ + bool DSG : 1; /**< DISCHARGE or RELAXATION */ + bool CF : 1; /**< Battery conditioning is needed */ + // High byte, Low bit first + uint8_t RSVD1 : 2; /**< Reserved */ + bool FCCX : 1; /**< fcc1hz clock going into CC: 0 = 1 Hz, 1 = 16 Hz*/ + uint8_t RSVD2 : 2; /**< Reserved */ + bool EDV1 : 1; /**< Cell voltage is above or below EDV1 threshold */ + bool EDV2 : 1; /**< Cell voltage is above or below EDV2 threshold */ + bool VDQ : 1; /**< Charge cycle FCC update qualification */ +} Bq27220GaugingStatus; + +_Static_assert(sizeof(Bq27220GaugingStatus) == 2, "Incorrect Bq27220GaugingStatus structure size"); typedef struct BQ27220DMData BQ27220DMData; /** Initialize Driver - * @return true on success, false otherwise + * + * This routine performs a lot of things under the hood: + * - Verifies that gauge is present on i2c bus and got correct ID(0220) + * - Unseals gauge + * - Checks various internal statuses + * - Checks that current profile is 0 + * - Checks configuration again provided data_memory + * - Reset gauge if something on previous stages was fishy + * - Updates configuration if needed + * - Sealing gauge to prevent configuration and state from accidental damage + * + * @param handle The I2C Bus handle + * @param[in] data_memory The data memory to be uploaded into gauge + * + * @return true on success, false otherwise */ -bool bq27220_init(FuriHalI2cBusHandle* handle); +bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); -/** Initialize Driver - * @return true on success, false otherwise +/** Reset gauge + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_reset(FuriHalI2cBusHandle* handle); + +/** Seal gauge access + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_seal(FuriHalI2cBusHandle* handle); + +/** Unseal gauge access + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_unseal(FuriHalI2cBusHandle* handle); + +/** Get full access + * + * @warning must be done in unsealed state + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise */ -bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); +bool bq27220_full_access(FuriHalI2cBusHandle* handle); -/** Get battery voltage in mV or error */ +/** Get battery voltage + * + * @param handle The I2C Bus handle + * + * @return voltage in mV or BQ27220_ERROR + */ uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); -/** Get current in mA or error*/ +/** Get current + * + * @param handle The I2C Bus handle + * + * @return current in mA or BQ27220_ERROR + */ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle); -/** Get battery status */ -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status); +/** Get control status + * + * @param handle The handle + * @param control_status The control status + * + * @return true on success, false otherwise + */ +bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status); + +/** Get battery status + * + * @param handle The handle + * @param battery_status The battery status + * + * @return true on success, false otherwise + */ +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status); + +/** Get operation status + * + * @param handle The handle + * @param operation_status The operation status + * + * @return true on success, false otherwise + */ +bool bq27220_get_operation_status( + FuriHalI2cBusHandle* handle, + Bq27220OperationStatus* operation_status); -/** Get operation status */ -bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status); +/** Get gauging status + * + * @param handle The handle + * @param gauging_status The gauging status + * + * @return true on success, false otherwise + */ +bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status); -/** Get temperature in units of 0.1°K */ +/** Get temperature + * + * @param handle The I2C Bus handle + * + * @return temperature in units of 0.1°K + */ uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle); -/** Get compensated full charge capacity in in mAh */ +/** Get compensated full charge capacity + * + * @param handle The I2C Bus handle + * + * @return full charge capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle); -/** Get design capacity in mAh */ +/** Get design capacity + * + * @param handle The I2C Bus handle + * + * @return design capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle); -/** Get remaining capacity in in mAh */ +/** Get remaining capacity + * + * @param handle The I2C Bus handle + * + * @return remaining capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle); -/** Get predicted remaining battery capacity in percents */ +/** Get predicted remaining battery capacity + * + * @param handle The I2C Bus handle + * + * @return state of charge in percents or BQ27220_ERROR + */ uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle); -/** Get ratio of full charge capacity over design capacity in percents */ +/** Get ratio of full charge capacity over design capacity + * + * @param handle The I2C Bus handle + * + * @return state of health in percents or BQ27220_ERROR + */ uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle); - -void bq27220_change_design_capacity(FuriHalI2cBusHandle* handle, uint16_t capacity); diff --git a/lib/drivers/bq27220_data_memory.h b/lib/drivers/bq27220_data_memory.h index 30f2dae1ec..0bd9348d2b 100644 --- a/lib/drivers/bq27220_data_memory.h +++ b/lib/drivers/bq27220_data_memory.h @@ -82,3 +82,5 @@ typedef struct { const bool SME0 : 1; const uint8_t RSVD3 : 3; } BQ27220DMGaugingConfig; + +_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); diff --git a/lib/drivers/bq27220_reg.h b/lib/drivers/bq27220_reg.h index 1c1ec9d8f9..2d93e31d0e 100644 --- a/lib/drivers/bq27220_reg.h +++ b/lib/drivers/bq27220_reg.h @@ -1,68 +1,76 @@ #pragma once -#define BQ27220_ADDRESS 0xAA -#define BQ27220_I2C_TIMEOUT 50 +#define BQ27220_ADDRESS (0xAAu) +#define BQ27220_I2C_TIMEOUT (50u) -#define CommandControl 0x00 -#define CommandAtRate 0x02 -#define CommandAtRateTimeToEmpty 0x04 -#define CommandTemperature 0x06 -#define CommandVoltage 0x08 -#define CommandBatteryStatus 0x0A -#define CommandCurrent 0x0C -#define CommandRemainingCapacity 0x10 -#define CommandFullChargeCapacity 0x12 -#define CommandAverageCurrent 0x14 -#define CommandTimeToEmpty 0x16 -#define CommandTimeToFull 0x18 -#define CommandStandbyCurrent 0x1A -#define CommandStandbyTimeToEmpty 0x1C -#define CommandMaxLoadCurrent 0x1E -#define CommandMaxLoadTimeToEmpty 0x20 -#define CommandRawCoulombCount 0x22 -#define CommandAveragePower 0x24 -#define CommandInternalTemperature 0x28 -#define CommandCycleCount 0x2A -#define CommandStateOfCharge 0x2C -#define CommandStateOfHealth 0x2E -#define CommandChargeVoltage 0x30 -#define CommandChargeCurrent 0x32 -#define CommandBTPDischargeSet 0x34 -#define CommandBTPChargeSet 0x36 -#define CommandOperationStatus 0x3A -#define CommandDesignCapacity 0x3C -#define CommandSelectSubclass 0x3E -#define CommandMACData 0x40 -#define CommandMACDataSum 0x60 -#define CommandMACDataLen 0x61 -#define CommandAnalogCount 0x79 -#define CommandRawCurrent 0x7A -#define CommandRawVoltage 0x7C -#define CommandRawIntTemp 0x7E +#define CommandControl (0x00u) +#define CommandAtRate (0x02u) +#define CommandAtRateTimeToEmpty (0x04u) +#define CommandTemperature (0x06u) +#define CommandVoltage (0x08u) +#define CommandBatteryStatus (0x0Au) +#define CommandCurrent (0x0Cu) +#define CommandRemainingCapacity (0x10u) +#define CommandFullChargeCapacity (0x12u) +#define CommandAverageCurrent (0x14u) +#define CommandTimeToEmpty (0x16u) +#define CommandTimeToFull (0x18u) +#define CommandStandbyCurrent (0x1Au) +#define CommandStandbyTimeToEmpty (0x1Cu) +#define CommandMaxLoadCurrent (0x1Eu) +#define CommandMaxLoadTimeToEmpty (0x20u) +#define CommandRawCoulombCount (0x22u) +#define CommandAveragePower (0x24u) +#define CommandInternalTemperature (0x28u) +#define CommandCycleCount (0x2Au) +#define CommandStateOfCharge (0x2Cu) +#define CommandStateOfHealth (0x2Eu) +#define CommandChargeVoltage (0x30u) +#define CommandChargeCurrent (0x32u) +#define CommandBTPDischargeSet (0x34u) +#define CommandBTPChargeSet (0x36u) +#define CommandOperationStatus (0x3Au) +#define CommandDesignCapacity (0x3Cu) +#define CommandSelectSubclass (0x3Eu) +#define CommandMACData (0x40u) +#define CommandMACDataSum (0x60u) +#define CommandMACDataLen (0x61u) +#define CommandAnalogCount (0x79u) +#define CommandRawCurrent (0x7Au) +#define CommandRawVoltage (0x7Cu) +#define CommandRawIntTemp (0x7Eu) -#define Control_CONTROL_STATUS 0x0000 -#define Control_DEVICE_NUMBER 0x0001 -#define Control_FW_VERSION 0x0002 -#define Control_BOARD_OFFSET 0x0009 -#define Control_CC_OFFSET 0x000A -#define Control_CC_OFFSET_SAVE 0x000B -#define Control_OCV_CMD 0x000C -#define Control_BAT_INSERT 0x000D -#define Control_BAT_REMOVE 0x000E -#define Control_SET_SNOOZE 0x0013 -#define Control_CLEAR_SNOOZE 0x0014 -#define Control_SET_PROFILE_1 0x0015 -#define Control_SET_PROFILE_2 0x0016 -#define Control_SET_PROFILE_3 0x0017 -#define Control_SET_PROFILE_4 0x0018 -#define Control_SET_PROFILE_5 0x0019 -#define Control_SET_PROFILE_6 0x001A -#define Control_CAL_TOGGLE 0x002D -#define Control_SEALED 0x0030 -#define Control_RESET 0x0041 -#define Control_EXIT_CAL 0x0080 -#define Control_ENTER_CAL 0x0081 -#define Control_ENTER_CFG_UPDATE 0x0090 -#define Control_EXIT_CFG_UPDATE_REINIT 0x0091 -#define Control_EXIT_CFG_UPDATE 0x0092 -#define Control_RETURN_TO_ROM 0x0F00 +#define Control_CONTROL_STATUS (0x0000u) +#define Control_DEVICE_NUMBER (0x0001u) +#define Control_FW_VERSION (0x0002u) +#define Control_HW_VERSION (0x0003u) +#define Control_BOARD_OFFSET (0x0009u) +#define Control_CC_OFFSET (0x000Au) +#define Control_CC_OFFSET_SAVE (0x000Bu) +#define Control_OCV_CMD (0x000Cu) +#define Control_BAT_INSERT (0x000Du) +#define Control_BAT_REMOVE (0x000Eu) +#define Control_SET_SNOOZE (0x0013u) +#define Control_CLEAR_SNOOZE (0x0014u) +#define Control_SET_PROFILE_1 (0x0015u) +#define Control_SET_PROFILE_2 (0x0016u) +#define Control_SET_PROFILE_3 (0x0017u) +#define Control_SET_PROFILE_4 (0x0018u) +#define Control_SET_PROFILE_5 (0x0019u) +#define Control_SET_PROFILE_6 (0x001Au) +#define Control_CAL_TOGGLE (0x002Du) +#define Control_SEALED (0x0030u) +#define Control_RESET (0x0041u) +#define Control_OERATION_STATUS (0x0054u) +#define Control_GAUGING_STATUS (0x0056u) +#define Control_EXIT_CAL (0x0080u) +#define Control_ENTER_CAL (0x0081u) +#define Control_ENTER_CFG_UPDATE (0x0090u) +#define Control_EXIT_CFG_UPDATE_REINIT (0x0091u) +#define Control_EXIT_CFG_UPDATE (0x0092u) +#define Control_RETURN_TO_ROM (0x0F00u) + +#define UnsealKey1 (0x0414u) +#define UnsealKey2 (0x3672u) + +#define FullAccessKey (0xffffu) diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 1a55278dcc..6097a8dc9c 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -47,7 +47,7 @@ def generate(env): PVSOPTIONS=[ "@.pvsoptions", "-j${PVSNCORES}", - "--disableLicenseExpirationCheck", + # "--disableLicenseExpirationCheck", # "--incremental", # kinda broken on PVS side ], PVSCONVOPTIONS=[ diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index ccbc521a69..37c6a8b1bb 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -73,18 +73,14 @@ void furi_hal_power_init(void) { // Find and init gauge size_t retry = 2; while(retry > 0) { - furi_hal_power.gauge_ok = bq27220_init(&furi_hal_i2c_handle_power); - if(furi_hal_power.gauge_ok) { - furi_hal_power.gauge_ok = bq27220_apply_data_memory( - &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); - } + furi_hal_power.gauge_ok = + bq27220_init(&furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); if(furi_hal_power.gauge_ok) { break; } else { - // Normal startup time is 250ms - // But if we try to access gauge at that stage it will become unresponsive - // 2 seconds timeout needed to restart communication - furi_delay_us(2020202); + // Gauge need some time to think about it's behavior + // We must wait, otherwise next init cycle will fail at unseal stage + furi_delay_us(4000000); } retry--; } @@ -110,8 +106,8 @@ void furi_hal_power_init(void) { bool furi_hal_power_gauge_is_ok(void) { bool ret = true; - BatteryStatus battery_status; - OperationStatus operation_status; + Bq27220BatteryStatus battery_status; + Bq27220OperationStatus operation_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -132,7 +128,7 @@ bool furi_hal_power_gauge_is_ok(void) { bool furi_hal_power_is_shutdown_requested(void) { bool ret = false; - BatteryStatus battery_status; + Bq27220BatteryStatus battery_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -593,8 +589,8 @@ void furi_hal_power_debug_get(PropertyValueCallback out, void* context) { PropertyValueContext property_context = { .key = key, .value = value, .out = out, .sep = '.', .last = false, .context = context}; - BatteryStatus battery_status; - OperationStatus operation_status; + Bq27220BatteryStatus battery_status; + Bq27220OperationStatus operation_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); From 0eaad8bf64f01a6f932647a9cda5475dd9ea1524 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 7 Oct 2024 04:21:31 +0900 Subject: [PATCH 121/236] [FL-3896] Split BadUSB into BadUSB and BadBLE (#3931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove BLE from BadUSB * Add the BadBLE app * Format images to 1-bit B/W * BadUsb: remove dead bits and pieces Co-authored-by: あく --- applications/main/bad_usb/application.fam | 2 +- applications/main/bad_usb/bad_usb_app.c | 8 - applications/main/bad_usb/bad_usb_app_i.h | 1 - .../main/bad_usb/helpers/bad_usb_hid.c | 156 +--- .../main/bad_usb/helpers/bad_usb_hid.h | 7 +- .../main/bad_usb/helpers/ducky_script.c | 4 +- .../main/bad_usb/helpers/ducky_script.h | 2 +- .../bad_usb/scenes/bad_usb_scene_config.c | 88 --- .../bad_usb/scenes/bad_usb_scene_config.h | 1 - .../main/bad_usb/scenes/bad_usb_scene_work.c | 4 +- .../main/bad_usb/views/bad_usb_view.c | 2 +- applications/system/bad_ble/application.fam | 12 + .../system/bad_ble/assets/Bad_BLE_48x22.png | Bin 0 -> 145 bytes applications/system/bad_ble/bad_ble_app.c | 196 +++++ applications/system/bad_ble/bad_ble_app.h | 11 + applications/system/bad_ble/bad_ble_app_i.h | 53 ++ .../system/bad_ble/helpers/bad_ble_hid.c | 157 ++++ .../system/bad_ble/helpers/bad_ble_hid.h | 34 + .../system/bad_ble/helpers/ducky_script.c | 716 ++++++++++++++++++ .../system/bad_ble/helpers/ducky_script.h | 55 ++ .../bad_ble/helpers/ducky_script_commands.c | 241 ++++++ .../system/bad_ble/helpers/ducky_script_i.h | 76 ++ .../bad_ble/helpers/ducky_script_keycodes.c | 133 ++++ applications/system/bad_ble/icon.png | Bin 0 -> 96 bytes .../system/bad_ble/scenes/bad_ble_scene.c | 30 + .../system/bad_ble/scenes/bad_ble_scene.h | 29 + .../bad_ble/scenes/bad_ble_scene_config.c | 59 ++ .../bad_ble/scenes/bad_ble_scene_config.h | 7 + .../scenes/bad_ble_scene_config_layout.c | 49 ++ .../scenes/bad_ble_scene_confirm_unpair.c | 53 ++ .../bad_ble/scenes/bad_ble_scene_error.c | 65 ++ .../scenes/bad_ble_scene_file_select.c | 46 ++ .../scenes/bad_ble_scene_unpair_done.c | 37 + .../bad_ble/scenes/bad_ble_scene_work.c | 65 ++ .../system/bad_ble/views/bad_ble_view.c | 284 +++++++ .../system/bad_ble/views/bad_ble_view.h | 26 + 36 files changed, 2444 insertions(+), 265 deletions(-) delete mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_config.c create mode 100644 applications/system/bad_ble/application.fam create mode 100644 applications/system/bad_ble/assets/Bad_BLE_48x22.png create mode 100644 applications/system/bad_ble/bad_ble_app.c create mode 100644 applications/system/bad_ble/bad_ble_app.h create mode 100644 applications/system/bad_ble/bad_ble_app_i.h create mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.c create mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.h create mode 100644 applications/system/bad_ble/helpers/ducky_script.c create mode 100644 applications/system/bad_ble/helpers/ducky_script.h create mode 100644 applications/system/bad_ble/helpers/ducky_script_commands.c create mode 100644 applications/system/bad_ble/helpers/ducky_script_i.h create mode 100644 applications/system/bad_ble/helpers/ducky_script_keycodes.c create mode 100644 applications/system/bad_ble/icon.png create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.h create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.h create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_error.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_file_select.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_work.c create mode 100644 applications/system/bad_ble/views/bad_ble_view.c create mode 100644 applications/system/bad_ble/views/bad_ble_view.h diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 8d3909fccc..9844e248df 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -7,7 +7,7 @@ App( icon="A_BadUsb_14", order=70, resources="resources", - fap_libs=["assets", "ble_profile"], + fap_libs=["assets"], fap_icon="icon.png", fap_category="USB", ) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 2d2d4be86c..1ee92bdf3f 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -35,7 +35,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { FuriString* temp_str = furi_string_alloc(); uint32_t version = 0; - uint32_t interface = 0; if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) { do { @@ -45,8 +44,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { break; if(!flipper_format_read_string(fff, "layout", temp_str)) break; - if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break; - if(interface > BadUsbHidInterfaceBle) break; state = true; } while(0); @@ -56,7 +53,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { if(state) { furi_string_set(app->keyboard_layout, temp_str); - app->interface = interface; Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo layout_file_info; @@ -68,7 +64,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { } } else { furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); - app->interface = BadUsbHidInterfaceUsb; } furi_string_free(temp_str); @@ -84,9 +79,6 @@ static void bad_usb_save_settings(BadUsbApp* app) { fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION)) break; if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; - uint32_t interface_id = app->interface; - if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1)) - break; } while(0); } diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index a4dd57d8b9..e63d0044c0 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -41,7 +41,6 @@ struct BadUsbApp { BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; - BadUsbHidInterface interface; FuriHalUsbInterface* usb_if_prev; }; diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index 5d7076314a..dcba7b5e93 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -1,12 +1,9 @@ #include "bad_usb_hid.h" #include -#include #include #define TAG "BadUSB HID" -#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" - void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) { furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); return NULL; @@ -72,155 +69,6 @@ static const BadUsbHidApi hid_api_usb = { .release_all = hid_usb_release_all, .get_led_state = hid_usb_get_led_state, }; - -typedef struct { - Bt* bt; - FuriHalBleProfileBase* profile; - HidStateCallback state_callback; - void* callback_context; - bool is_connected; -} BleHidInstance; - -static const BleProfileHidParams ble_hid_params = { - .device_name_prefix = "BadUSB", - .mac_xor = 0x0002, -}; - -static void hid_ble_connection_status_callback(BtStatus status, void* context) { - furi_assert(context); - BleHidInstance* ble_hid = context; - ble_hid->is_connected = (status == BtStatusConnected); - if(ble_hid->state_callback) { - ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); - } -} - -void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { - UNUSED(hid_cfg); - BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); - ble_hid->bt = furi_record_open(RECORD_BT); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - - ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); - furi_check(ble_hid->profile); - - furi_hal_bt_start_advertising(); - - bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); - - return ble_hid; -} - -void hid_ble_deinit(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - - bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(ble_hid->bt); - - furi_check(bt_profile_restore_default(ble_hid->bt)); - furi_record_close(RECORD_BT); - free(ble_hid); -} - -void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - ble_hid->state_callback = cb; - ble_hid->callback_context = context; -} - -bool hid_ble_is_connected(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_hid->is_connected; -} - -bool hid_ble_kb_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_press(ble_hid->profile, button); -} - -bool hid_ble_kb_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_release(ble_hid->profile, button); -} - -bool hid_ble_consumer_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_press(ble_hid->profile, button); -} - -bool hid_ble_consumer_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_release(ble_hid->profile, button); -} - -bool hid_ble_release_all(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - bool state = ble_profile_hid_kb_release_all(ble_hid->profile); - state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); - return state; -} - -uint8_t hid_ble_get_led_state(void* inst) { - UNUSED(inst); - FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); - return 0; -} - -static const BadUsbHidApi hid_api_ble = { - .init = hid_ble_init, - .deinit = hid_ble_deinit, - .set_state_callback = hid_ble_set_state_callback, - .is_connected = hid_ble_is_connected, - - .kb_press = hid_ble_kb_press, - .kb_release = hid_ble_kb_release, - .consumer_press = hid_ble_consumer_press, - .consumer_release = hid_ble_consumer_release, - .release_all = hid_ble_release_all, - .get_led_state = hid_ble_get_led_state, -}; - -const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) { - if(interface == BadUsbHidInterfaceUsb) { - return &hid_api_usb; - } else { - return &hid_api_ble; - } -} - -void bad_usb_hid_ble_remove_pairing(void) { - Bt* bt = furi_record_open(RECORD_BT); - bt_disconnect(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - furi_hal_bt_stop_advertising(); - - bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - bt_forget_bonded_devices(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(bt); - - furi_check(bt_profile_restore_default(bt)); - furi_record_close(RECORD_BT); +const BadUsbHidApi* bad_usb_hid_get_interface() { + return &hid_api_usb; } diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index 71d3a58e79..feaaacd541 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -7,11 +7,6 @@ extern "C" { #include #include -typedef enum { - BadUsbHidInterfaceUsb, - BadUsbHidInterfaceBle, -} BadUsbHidInterface; - typedef struct { void* (*init)(FuriHalUsbHidConfig* hid_cfg); void (*deinit)(void* inst); @@ -26,7 +21,7 @@ typedef struct { uint8_t (*get_led_state)(void* inst); } BadUsbHidApi; -const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface); +const BadUsbHidApi* bad_usb_hid_get_interface(); void bad_usb_hid_ble_remove_pairing(void); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index ccc3caa811..d730fdba4d 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -650,7 +650,7 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) { memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout))); } -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) { +BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_assert(file_path); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); @@ -660,7 +660,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface inte bad_usb->st.state = BadUsbStateInit; bad_usb->st.error[0] = '\0'; - bad_usb->hid = bad_usb_hid_get_interface(interface); + bad_usb->hid = bad_usb_hid_get_interface(); bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); furi_thread_start(bad_usb->thread); diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 9519623f60..43969d7b67 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -34,7 +34,7 @@ typedef struct { typedef struct BadUsbScript BadUsbScript; -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface); +BadUsbScript* bad_usb_script_open(FuriString* file_path); void bad_usb_script_close(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c deleted file mode 100644 index 1acf3acb15..0000000000 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "../bad_usb_app_i.h" - -enum SubmenuIndex { - ConfigIndexKeyboardLayout, - ConfigIndexInterface, - ConfigIndexBleUnpair, -}; - -const char* const interface_mode_text[2] = { - "USB", - "BLE", -}; - -void bad_usb_scene_config_select_callback(void* context, uint32_t index) { - BadUsbApp* bad_usb = context; - if(index != ConfigIndexInterface) { - view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index); - } -} - -void bad_usb_scene_config_interface_callback(VariableItem* item) { - BadUsbApp* bad_usb = variable_item_get_context(item); - furi_assert(bad_usb); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, interface_mode_text[index]); - bad_usb->interface = index; - - view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ConfigIndexInterface); -} - -static void draw_menu(BadUsbApp* bad_usb) { - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_reset(var_item_list); - - variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL); - - VariableItem* item = variable_item_list_add( - var_item_list, "Interface", 2, bad_usb_scene_config_interface_callback, bad_usb); - if(bad_usb->interface == BadUsbHidInterfaceUsb) { - variable_item_set_current_value_index(item, 0); - variable_item_set_current_value_text(item, interface_mode_text[0]); - } else { - variable_item_set_current_value_index(item, 1); - variable_item_set_current_value_text(item, interface_mode_text[1]); - variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL); - } -} - -void bad_usb_scene_config_on_enter(void* context) { - BadUsbApp* bad_usb = context; - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_set_enter_callback( - var_item_list, bad_usb_scene_config_select_callback, bad_usb); - draw_menu(bad_usb); - variable_item_list_set_selected_item(var_item_list, 0); - - view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig); -} - -bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) { - BadUsbApp* bad_usb = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == ConfigIndexKeyboardLayout) { - scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout); - } else if(event.event == ConfigIndexInterface) { - draw_menu(bad_usb); - } else if(event.event == ConfigIndexBleUnpair) { - bad_usb_hid_ble_remove_pairing(); - } else { - furi_crash("Unknown key type"); - } - } - - return consumed; -} - -void bad_usb_scene_config_on_exit(void* context) { - BadUsbApp* bad_usb = context; - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_reset(var_item_list); -} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h index 423aedc51b..e640bb556b 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.h +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.h @@ -1,5 +1,4 @@ ADD_SCENE(bad_usb, file_select, FileSelect) ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, error, Error) -ADD_SCENE(bad_usb, config, Config) ADD_SCENE(bad_usb, config_layout, ConfigLayout) diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 0a383f0295..0afc056b62 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -20,7 +20,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { bad_usb_script_close(app->bad_usb_script); app->bad_usb_script = NULL; - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); } consumed = true; } else if(event.event == InputKeyOk) { @@ -39,7 +39,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { void bad_usb_scene_work_on_enter(void* context) { BadUsbApp* app = context; - app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); + app->bad_usb_script = bad_usb_script_open(app->file_path); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); FuriString* file_name; diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 728f843487..7fb0b1434e 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -47,7 +47,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || (state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); - elements_button_left(canvas, "Config"); + elements_button_left(canvas, "Layout"); } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); if(!model->pause_wait) { diff --git a/applications/system/bad_ble/application.fam b/applications/system/bad_ble/application.fam new file mode 100644 index 0000000000..e00e6eefcc --- /dev/null +++ b/applications/system/bad_ble/application.fam @@ -0,0 +1,12 @@ +App( + appid="bad_ble", + name="Bad BLE", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_ble_app", + stack_size=2 * 1024, + icon="A_BadUsb_14", + fap_libs=["assets", "ble_profile"], + fap_icon="icon.png", + fap_icon_assets="assets", + fap_category="Bluetooth", +) diff --git a/applications/system/bad_ble/assets/Bad_BLE_48x22.png b/applications/system/bad_ble/assets/Bad_BLE_48x22.png new file mode 100644 index 0000000000000000000000000000000000000000..5f6fa6f4694972b23d9d0a219f404f16c18f6403 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^20$#v2qYNR^(cYp2u~Nskcv6JXAQX;3mdKI;Vst091lAb^rhX literal 0 HcmV?d00001 diff --git a/applications/system/bad_ble/bad_ble_app.c b/applications/system/bad_ble/bad_ble_app.c new file mode 100644 index 0000000000..f243371986 --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app.c @@ -0,0 +1,196 @@ +#include "bad_ble_app_i.h" +#include +#include +#include +#include +#include + +#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/.badble.settings" +#define BAD_BLE_SETTINGS_FILE_TYPE "Flipper BadBLE Settings File" +#define BAD_BLE_SETTINGS_VERSION 1 +#define BAD_BLE_SETTINGS_DEFAULT_LAYOUT BAD_BLE_APP_PATH_LAYOUT_FOLDER "/en-US.kl" + +static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_ble_app_back_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_ble_app_tick_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_ble_load_settings(BadBleApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + bool state = false; + + FuriString* temp_str = furi_string_alloc(); + uint32_t version = 0; + + if(flipper_format_file_open_existing(fff, BAD_BLE_SETTINGS_PATH)) { + do { + if(!flipper_format_read_header(fff, temp_str, &version)) break; + if((strcmp(furi_string_get_cstr(temp_str), BAD_BLE_SETTINGS_FILE_TYPE) != 0) || + (version != BAD_BLE_SETTINGS_VERSION)) + break; + + if(!flipper_format_read_string(fff, "layout", temp_str)) break; + + state = true; + } while(0); + } + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); + + if(state) { + furi_string_set(app->keyboard_layout, temp_str); + + Storage* fs_api = furi_record_open(RECORD_STORAGE); + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat( + fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); + furi_record_close(RECORD_STORAGE); + if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) { + furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); + } + } else { + furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); + } + + furi_string_free(temp_str); +} + +static void bad_ble_save_settings(BadBleApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_always(fff, BAD_BLE_SETTINGS_PATH)) { + do { + if(!flipper_format_write_header_cstr( + fff, BAD_BLE_SETTINGS_FILE_TYPE, BAD_BLE_SETTINGS_VERSION)) + break; + if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; + } while(0); + } + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); +} + +BadBleApp* bad_ble_app_alloc(char* arg) { + BadBleApp* app = malloc(sizeof(BadBleApp)); + + app->bad_ble_script = NULL; + + app->file_path = furi_string_alloc(); + app->keyboard_layout = furi_string_alloc(); + if(arg && strlen(arg)) { + furi_string_set(app->file_path, arg); + } + + bad_ble_load_settings(app); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->dialogs = furi_record_open(RECORD_DIALOGS); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_ble_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_ble_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_ble_app_back_event_callback); + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, BadBleAppViewPopup, popup_get_view(app->popup)); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + BadBleAppViewConfig, + variable_item_list_get_view(app->var_item_list)); + + app->bad_ble_view = bad_ble_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewWork, bad_ble_view_get_view(app->bad_ble_view)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + if(!furi_string_empty(app->file_path)) { + scene_manager_next_scene(app->scene_manager, BadBleSceneWork); + } else { + furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect); + } + + return app; +} + +void bad_ble_app_free(BadBleApp* app) { + furi_assert(app); + + if(app->bad_ble_script) { + bad_ble_script_close(app->bad_ble_script); + app->bad_ble_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork); + bad_ble_view_free(app->bad_ble_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewPopup); + popup_free(app->popup); + + // Config menu + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig); + variable_item_list_free(app->var_item_list); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + + bad_ble_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + free(app); +} + +int32_t bad_ble_app(void* p) { + BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p); + + view_dispatcher_run(bad_ble_app->view_dispatcher); + + bad_ble_app_free(bad_ble_app); + return 0; +} diff --git a/applications/system/bad_ble/bad_ble_app.h b/applications/system/bad_ble/bad_ble_app.h new file mode 100644 index 0000000000..11954836e5 --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BadBleApp BadBleApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/bad_ble_app_i.h b/applications/system/bad_ble/bad_ble_app_i.h new file mode 100644 index 0000000000..d1f739bebb --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app_i.h @@ -0,0 +1,53 @@ +#pragma once + +#include "bad_ble_app.h" +#include "scenes/bad_ble_scene.h" +#include "helpers/ducky_script.h" +#include "helpers/bad_ble_hid.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/bad_ble_view.h" + +#define BAD_BLE_APP_BASE_FOLDER EXT_PATH("badusb") +#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/assets/layouts" +#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt" +#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl" + +typedef enum { + BadBleAppErrorNoFiles, + BadBleAppErrorCloseRpc, +} BadBleAppError; + +struct BadBleApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + Popup* popup; + VariableItemList* var_item_list; + + BadBleAppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadBle* bad_ble_view; + BadBleScript* bad_ble_script; + + BadBleHidInterface interface; +}; + +typedef enum { + BadBleAppViewWidget, + BadBleAppViewPopup, + BadBleAppViewWork, + BadBleAppViewConfig, +} BadBleAppView; diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.c b/applications/system/bad_ble/helpers/bad_ble_hid.c new file mode 100644 index 0000000000..c34b3c6461 --- /dev/null +++ b/applications/system/bad_ble/helpers/bad_ble_hid.c @@ -0,0 +1,157 @@ +#include "bad_ble_hid.h" +#include +#include +#include + +#define TAG "BadBLE HID" + +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" + +typedef struct { + Bt* bt; + FuriHalBleProfileBase* profile; + HidStateCallback state_callback; + void* callback_context; + bool is_connected; +} BleHidInstance; + +static const BleProfileHidParams ble_hid_params = { + .device_name_prefix = "BadBLE", + .mac_xor = 0x0002, +}; + +static void hid_ble_connection_status_callback(BtStatus status, void* context) { + furi_assert(context); + BleHidInstance* ble_hid = context; + ble_hid->is_connected = (status == BtStatusConnected); + if(ble_hid->state_callback) { + ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); + } +} + +void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { + UNUSED(hid_cfg); + BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); + ble_hid->bt = furi_record_open(RECORD_BT); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); + furi_check(ble_hid->profile); + + furi_hal_bt_start_advertising(); + + bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); + + return ble_hid; +} + +void hid_ble_deinit(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + + bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(ble_hid->bt); + + furi_check(bt_profile_restore_default(ble_hid->bt)); + furi_record_close(RECORD_BT); + free(ble_hid); +} + +void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + ble_hid->state_callback = cb; + ble_hid->callback_context = context; +} + +bool hid_ble_is_connected(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_hid->is_connected; +} + +bool hid_ble_kb_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_press(ble_hid->profile, button); +} + +bool hid_ble_kb_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_release(ble_hid->profile, button); +} + +bool hid_ble_consumer_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_press(ble_hid->profile, button); +} + +bool hid_ble_consumer_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_release(ble_hid->profile, button); +} + +bool hid_ble_release_all(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + bool state = ble_profile_hid_kb_release_all(ble_hid->profile); + state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); + return state; +} + +uint8_t hid_ble_get_led_state(void* inst) { + UNUSED(inst); + FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); + return 0; +} + +static const BadBleHidApi hid_api_ble = { + .init = hid_ble_init, + .deinit = hid_ble_deinit, + .set_state_callback = hid_ble_set_state_callback, + .is_connected = hid_ble_is_connected, + + .kb_press = hid_ble_kb_press, + .kb_release = hid_ble_kb_release, + .consumer_press = hid_ble_consumer_press, + .consumer_release = hid_ble_consumer_release, + .release_all = hid_ble_release_all, + .get_led_state = hid_ble_get_led_state, +}; + +const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface) { + UNUSED(interface); + return &hid_api_ble; +} + +void bad_ble_hid_ble_remove_pairing(void) { + Bt* bt = furi_record_open(RECORD_BT); + bt_disconnect(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + bt_forget_bonded_devices(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(bt); + + furi_check(bt_profile_restore_default(bt)); + furi_record_close(RECORD_BT); +} diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.h b/applications/system/bad_ble/helpers/bad_ble_hid.h new file mode 100644 index 0000000000..b06385d6dd --- /dev/null +++ b/applications/system/bad_ble/helpers/bad_ble_hid.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef enum { + BadBleHidInterfaceBle, +} BadBleHidInterface; + +typedef struct { + void* (*init)(FuriHalUsbHidConfig* hid_cfg); + void (*deinit)(void* inst); + void (*set_state_callback)(void* inst, HidStateCallback cb, void* context); + bool (*is_connected)(void* inst); + + bool (*kb_press)(void* inst, uint16_t button); + bool (*kb_release)(void* inst, uint16_t button); + bool (*consumer_press)(void* inst, uint16_t button); + bool (*consumer_release)(void* inst, uint16_t button); + bool (*release_all)(void* inst); + uint8_t (*get_led_state)(void* inst); +} BadBleHidApi; + +const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface); + +void bad_ble_hid_ble_remove_pairing(void); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script.c b/applications/system/bad_ble/helpers/ducky_script.c new file mode 100644 index 0000000000..a903fbdc4d --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script.c @@ -0,0 +1,716 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" +#include + +#define TAG "BadBle" + +#define WORKER_TAG TAG "Worker" + +#define BADUSB_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + +typedef enum { + WorkerEvtStartStop = (1 << 0), + WorkerEvtPauseResume = (1 << 1), + WorkerEvtEnd = (1 << 2), + WorkerEvtConnect = (1 << 3), + WorkerEvtDisconnect = (1 << 4), +} WorkerEvtFlags; + +static const char ducky_cmd_id[] = {"ID"}; + +static const uint8_t numpad_keys[10] = { + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, +}; + +uint32_t ducky_get_command_len(const char* line) { + uint32_t len = strlen(line); + for(uint32_t i = 0; i < len; i++) { + if(line[i] == ' ') return i; + } + return 0; +} + +bool ducky_is_line_end(const char chr) { + return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'); +} + +uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) { + uint16_t keycode = ducky_get_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; + } + + if((accept_chars) && (strlen(param) > 0)) { + return BADUSB_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF; + } + return 0; +} + +bool ducky_get_number(const char* param, uint32_t* val) { + uint32_t value = 0; + if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) { + *val = value; + return true; + } + return false; +} + +void ducky_numlock_on(BadBleScript* bad_ble) { + if((bad_ble->hid->get_led_state(bad_ble->hid_inst) & HID_KB_LED_NUM) == 0) { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); + } +} + +bool ducky_numpad_press(BadBleScript* bad_ble, const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + bad_ble->hid->kb_press(bad_ble->hid_inst, key); + bad_ble->hid->kb_release(bad_ble->hid_inst, key); + + return true; +} + +bool ducky_altchar(BadBleScript* bad_ble, const char* charcode) { + uint8_t i = 0; + bool state = false; + + bad_ble->hid->kb_press(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(bad_ble, charcode[i]); + if(state == false) break; + i++; + } + + bad_ble->hid->kb_release(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); + return state; +} + +bool ducky_altstring(BadBleScript* bad_ble, const char* param) { + uint32_t i = 0; + bool state = false; + + while(param[i] != '\0') { + if((param[i] < ' ') || (param[i] > '~')) { + i++; + continue; // Skip non-printable chars + } + + char temp_str[4]; + snprintf(temp_str, 4, "%u", param[i]); + + state = ducky_altchar(bad_ble, temp_str); + if(state == false) break; + i++; + } + return state; +} + +int32_t ducky_error(BadBleScript* bad_ble, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(bad_ble->st.error, sizeof(bad_ble->st.error), text, args); + + va_end(args); + return SCRIPT_STATE_ERROR; +} + +bool ducky_string(BadBleScript* bad_ble, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + if(param[i] != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); + bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); + } + } else { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + } + i++; + } + bad_ble->stringdelay = 0; + return true; +} + +static bool ducky_string_next(BadBleScript* bad_ble) { + if(bad_ble->string_print_pos >= furi_string_size(bad_ble->string_print)) { + return true; + } + + char print_char = furi_string_get_char(bad_ble->string_print, bad_ble->string_print_pos); + + if(print_char != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, print_char); + if(keycode != HID_KEYBOARD_NONE) { + bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); + bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); + } + } else { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + } + + bad_ble->string_print_pos++; + + return false; +} + +static int32_t ducky_parse_line(BadBleScript* bad_ble, FuriString* line) { + uint32_t line_len = furi_string_size(line); + const char* line_tmp = furi_string_get_cstr(line); + + if(line_len == 0) { + return SCRIPT_STATE_NEXT_LINE; // Skip empty lines + } + FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + + // Ducky Lang Functions + int32_t cmd_result = ducky_execute_cmd(bad_ble, line_tmp); + if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { + return cmd_result; + } + + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_ble, "No keycode defined for %s", line_tmp); + } + if((key & 0xFF00) != 0) { + // It's a modifier key + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + key |= ducky_get_keycode(bad_ble, line_tmp, true); + } + bad_ble->hid->kb_press(bad_ble->hid_inst, key); + bad_ble->hid->kb_release(bad_ble->hid_inst, key); + return 0; +} + +static bool ducky_set_usb_id(BadBleScript* bad_ble, const char* line) { + if(sscanf(line, "%lX:%lX", &bad_ble->hid_cfg.vid, &bad_ble->hid_cfg.pid) == 2) { + bad_ble->hid_cfg.manuf[0] = '\0'; + bad_ble->hid_cfg.product[0] = '\0'; + + uint8_t id_len = ducky_get_command_len(line); + if(!ducky_is_line_end(line[id_len + 1])) { + sscanf( + &line[id_len + 1], + "%31[^\r\n:]:%31[^\r\n]", + bad_ble->hid_cfg.manuf, + bad_ble->hid_cfg.product); + } + FURI_LOG_D( + WORKER_TAG, + "set id: %04lX:%04lX mfr:%s product:%s", + bad_ble->hid_cfg.vid, + bad_ble->hid_cfg.pid, + bad_ble->hid_cfg.manuf, + bad_ble->hid_cfg.product); + return true; + } + return false; +} + +static void bad_ble_hid_state_callback(bool state, void* context) { + furi_assert(context); + BadBleScript* bad_ble = context; + + if(state == true) { + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect); + } else { + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect); + } +} + +static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) { + uint8_t ret = 0; + uint32_t line_len = 0; + + furi_string_reset(bad_ble->line); + + do { + ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + for(uint16_t i = 0; i < ret; i++) { + if(bad_ble->file_buf[i] == '\n' && line_len > 0) { + bad_ble->st.line_nb++; + line_len = 0; + } else { + if(bad_ble->st.line_nb == 0) { // Save first line + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + line_len++; + } + } + if(storage_file_eof(script_file)) { + if(line_len > 0) { + bad_ble->st.line_nb++; + break; + } + } + } while(ret > 0); + + const char* line_tmp = furi_string_get_cstr(bad_ble->line); + bool id_set = false; // Looking for ID command at first line + if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { + id_set = ducky_set_usb_id(bad_ble, &line_tmp[strlen(ducky_cmd_id) + 1]); + } + + if(id_set) { + bad_ble->hid_inst = bad_ble->hid->init(&bad_ble->hid_cfg); + } else { + bad_ble->hid_inst = bad_ble->hid->init(NULL); + } + bad_ble->hid->set_state_callback(bad_ble->hid_inst, bad_ble_hid_state_callback, bad_ble); + + storage_file_seek(script_file, 0, true); + furi_string_reset(bad_ble->line); + + return true; +} + +static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) { + int32_t delay_val = 0; + + if(bad_ble->repeat_cnt > 0) { + bad_ble->repeat_cnt--; + delay_val = ducky_parse_line(bad_ble, bad_ble->line_prev); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; + } else if(delay_val < 0) { // Script error + bad_ble->st.error_line = bad_ble->st.line_cur - 1; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur - 1U); + return SCRIPT_STATE_ERROR; + } else { + return delay_val + bad_ble->defdelay; + } + } + + furi_string_set(bad_ble->line_prev, bad_ble->line); + furi_string_reset(bad_ble->line); + + while(1) { + if(bad_ble->buf_len == 0) { + bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + if(storage_file_eof(script_file)) { + if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) { + bad_ble->file_buf[bad_ble->buf_len] = '\n'; + bad_ble->buf_len++; + bad_ble->file_end = true; + } + } + + bad_ble->buf_start = 0; + if(bad_ble->buf_len == 0) return SCRIPT_STATE_END; + } + for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) { + if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) { + bad_ble->st.line_cur++; + bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1); + bad_ble->buf_start = i + 1; + furi_string_trim(bad_ble->line); + delay_val = ducky_parse_line(bad_ble, bad_ble->line); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; + } else if(delay_val < 0) { + bad_ble->st.error_line = bad_ble->st.line_cur; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur); + return SCRIPT_STATE_ERROR; + } else { + return delay_val + bad_ble->defdelay; + } + } else { + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + } + bad_ble->buf_len = 0; + if(bad_ble->file_end) return SCRIPT_STATE_END; + } + + return 0; +} + +static uint32_t bad_ble_flags_get(uint32_t flags_mask, uint32_t timeout) { + uint32_t flags = furi_thread_flags_get(); + furi_check((flags & FuriFlagError) == 0); + if(flags == 0) { + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout)); + } else { + uint32_t state = furi_thread_flags_clear(flags); + furi_check((state & FuriFlagError) == 0); + } + return flags; +} + +static int32_t bad_ble_worker(void* context) { + BadBleScript* bad_ble = context; + + BadBleWorkerState worker_state = BadBleStateInit; + BadBleWorkerState pause_state = BadBleStateRunning; + int32_t delay_val = 0; + + FURI_LOG_I(WORKER_TAG, "Init"); + File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bad_ble->line = furi_string_alloc(); + bad_ble->line_prev = furi_string_alloc(); + bad_ble->string_print = furi_string_alloc(); + + while(1) { + if(worker_state == BadBleStateInit) { // State: initialization + if(storage_file_open( + script_file, + furi_string_get_cstr(bad_ble->file_path), + FSAM_READ, + FSOM_OPEN_EXISTING)) { + if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) { + if(bad_ble->hid->is_connected(bad_ble->hid_inst)) { + worker_state = BadBleStateIdle; // Ready to run + } else { + worker_state = BadBleStateNotConnected; // USB not connected + } + } else { + worker_state = BadBleStateScriptError; // Script preload error + } + } else { + FURI_LOG_E(WORKER_TAG, "File open error"); + worker_state = BadBleStateFileError; // File open error + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateNotConnected) { // State: USB not connected + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { + worker_state = BadBleStateIdle; // Ready to run + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateWillRun; // Will run when USB is connected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateIdle) { // State: ready to start + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { // Start executing script + dolphin_deed(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->stringdelay = 0; + bad_ble->defstringdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->key_hold_nb = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateWillRun) { // State: start on connection + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + dolphin_deed(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->stringdelay = 0; + bad_ble->defstringdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + // extra time for PC to recognize Flipper as keyboard + flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriFlagWaitAny | FuriFlagNoClear, + 1500); + if(flags == (unsigned)FuriFlagErrorTimeout) { + // If nothing happened - start script execution + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; + furi_thread_flags_clear(WorkerEvtStartStop); + } + } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution + worker_state = BadBleStateNotConnected; + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateRunning) { // State: running + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriFlagWaitAny, + delay_cur); + + delay_val -= delay_cur; + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadBleStateRunning; + worker_state = BadBleStatePaused; // Pause + } + bad_ble->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + if(delay_val > 0) { + bad_ble->st.delay_remain--; + continue; + } + bad_ble->st.state = BadBleStateRunning; + delay_val = ducky_script_execute_next(bad_ble, script_file); + if(delay_val == SCRIPT_STATE_ERROR) { // Script error + delay_val = 0; + worker_state = BadBleStateScriptError; + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(delay_val == SCRIPT_STATE_END) { // End of script + delay_val = 0; + worker_state = BadBleStateIdle; + bad_ble->st.state = BadBleStateDone; + bad_ble->hid->release_all(bad_ble->hid_inst); + continue; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays + delay_val = bad_ble->defdelay; + bad_ble->string_print_pos = 0; + worker_state = BadBleStateStringDelay; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input + worker_state = BadBleStateWaitForBtn; + bad_ble->st.state = BadBleStateWaitForBtn; // Show long delays + } else if(delay_val > 1000) { + bad_ble->st.state = BadBleStateDelay; // Show long delays + bad_ble->st.delay_remain = delay_val / 1000; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if(worker_state == BadBleStateWaitForBtn) { // State: Wait for button Press + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriWaitForever); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + delay_val = 0; + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } + bad_ble->st.state = worker_state; + continue; + } + } else if(worker_state == BadBleStatePaused) { // State: Paused + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriWaitForever); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + if(pause_state == BadBleStateRunning) { + if(delay_val > 0) { + bad_ble->st.state = BadBleStateDelay; + bad_ble->st.delay_remain = delay_val / 1000; + } else { + bad_ble->st.state = BadBleStateRunning; + delay_val = 0; + } + worker_state = BadBleStateRunning; // Resume + } else if(pause_state == BadBleStateStringDelay) { + bad_ble->st.state = BadBleStateRunning; + worker_state = BadBleStateStringDelay; // Resume + } + } + continue; + } + } else if(worker_state == BadBleStateStringDelay) { // State: print string with delays + uint32_t delay = (bad_ble->stringdelay == 0) ? bad_ble->defstringdelay : + bad_ble->stringdelay; + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + delay); + + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadBleStateStringDelay; + worker_state = BadBleStatePaused; // Pause + } + bad_ble->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + bool string_end = ducky_string_next(bad_ble); + if(string_end) { + bad_ble->stringdelay = 0; + worker_state = BadBleStateRunning; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if( + (worker_state == BadBleStateFileError) || + (worker_state == BadBleStateScriptError)) { // State: error + uint32_t flags = + bad_ble_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command + + if(flags & WorkerEvtEnd) { + break; + } + } + } + + bad_ble->hid->set_state_callback(bad_ble->hid_inst, NULL, NULL); + bad_ble->hid->deinit(bad_ble->hid_inst); + + storage_file_close(script_file); + storage_file_free(script_file); + furi_string_free(bad_ble->line); + furi_string_free(bad_ble->line_prev); + furi_string_free(bad_ble->string_print); + + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) { + furi_assert(bad_ble); + memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout)); + memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout))); +} + +BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface) { + furi_assert(file_path); + + BadBleScript* bad_ble = malloc(sizeof(BadBleScript)); + bad_ble->file_path = furi_string_alloc(); + furi_string_set(bad_ble->file_path, file_path); + bad_ble_script_set_default_keyboard_layout(bad_ble); + + bad_ble->st.state = BadBleStateInit; + bad_ble->st.error[0] = '\0'; + bad_ble->hid = bad_ble_hid_get_interface(interface); + + bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble); + furi_thread_start(bad_ble->thread); + return bad_ble; +} //-V773 + +void bad_ble_script_close(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd); + furi_thread_join(bad_ble->thread); + furi_thread_free(bad_ble->thread); + furi_string_free(bad_ble->file_path); + free(bad_ble); +} + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) { + furi_assert(bad_ble); + + if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) { + // do not update keyboard layout while a script is running + return; + } + + File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(!furi_string_empty(layout_path)) { //-V1051 + if(storage_file_open( + layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + uint16_t layout[128]; + if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { + memcpy(bad_ble->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_ble_script_set_default_keyboard_layout(bad_ble); + } + storage_file_free(layout_file); +} + +void bad_ble_script_start_stop(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtStartStop); +} + +void bad_ble_script_pause_resume(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtPauseResume); +} + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) { + furi_assert(bad_ble); + return &(bad_ble->st); +} diff --git a/applications/system/bad_ble/helpers/ducky_script.h b/applications/system/bad_ble/helpers/ducky_script.h new file mode 100644 index 0000000000..044cae8256 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "bad_ble_hid.h" + +typedef enum { + BadBleStateInit, + BadBleStateNotConnected, + BadBleStateIdle, + BadBleStateWillRun, + BadBleStateRunning, + BadBleStateDelay, + BadBleStateStringDelay, + BadBleStateWaitForBtn, + BadBleStatePaused, + BadBleStateDone, + BadBleStateScriptError, + BadBleStateFileError, +} BadBleWorkerState; + +typedef struct { + BadBleWorkerState state; + size_t line_cur; + size_t line_nb; + uint32_t delay_remain; + size_t error_line; + char error[64]; +} BadBleState; + +typedef struct BadBleScript BadBleScript; + +BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface); + +void bad_ble_script_close(BadBleScript* bad_ble); + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path); + +void bad_ble_script_start(BadBleScript* bad_ble); + +void bad_ble_script_stop(BadBleScript* bad_ble); + +void bad_ble_script_start_stop(BadBleScript* bad_ble); + +void bad_ble_script_pause_resume(BadBleScript* bad_ble); + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_commands.c b/applications/system/bad_ble/helpers/ducky_script_commands.c new file mode 100644 index 0000000000..f70c5eba40 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_commands.c @@ -0,0 +1,241 @@ +#include +#include "ducky_script.h" +#include "ducky_script_i.h" + +typedef int32_t (*DuckyCmdCallback)(BadBleScript* bad_usb, const char* line, int32_t param); + +typedef struct { + char* name; + DuckyCmdCallback callback; + int32_t param; +} DuckyCmd; + +static int32_t ducky_fnc_delay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint32_t delay_val = 0; + bool state = ducky_get_number(line, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + + return ducky_error(bad_usb, "Invalid number %s", line); +} + +static int32_t ducky_fnc_defdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_strdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->stringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_defstrdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defstringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_string(BadBleScript* bad_usb, const char* line, int32_t param) { + line = &line[ducky_get_command_len(line) + 1]; + furi_string_set_str(bad_usb->string_print, line); + if(param == 1) { + furi_string_cat(bad_usb->string_print, "\n"); + } + + if(bad_usb->stringdelay == 0 && + bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately + bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); + if(!state) { + return ducky_error(bad_usb, "Invalid string %s", line); + } + } else { // stringdelay is set - run command in thread to keep handling external events + return SCRIPT_STATE_STRING_START; + } + + return 0; +} + +static int32_t ducky_fnc_repeat(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->repeat_cnt); + if((!state) || (bad_usb->repeat_cnt == 0)) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_sysrq(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + bad_usb->hid->release_all(bad_usb->hid_inst); + return 0; +} + +static int32_t ducky_fnc_altchar(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_usb); + bool state = ducky_altchar(bad_usb, line); + if(!state) { + return ducky_error(bad_usb, "Invalid altchar %s", line); + } + return 0; +} + +static int32_t ducky_fnc_altstring(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_usb); + bool state = ducky_altstring(bad_usb, line); + if(!state) { + return ducky_error(bad_usb, "Invalid altstring %s", line); + } + return 0; +} + +static int32_t ducky_fnc_hold(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + bad_usb->key_hold_nb++; + if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { + return ducky_error(bad_usb, "Too many keys are hold"); + } + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_release(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + if(bad_usb->key_hold_nb == 0) { + return ducky_error(bad_usb, "No keys are hold"); + } + bad_usb->key_hold_nb--; + bad_usb->hid->kb_release(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_media(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_media_keycode_by_name(line); + if(key == HID_CONSUMER_UNASSIGNED) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + bad_usb->hid->consumer_press(bad_usb->hid_inst, key); + bad_usb->hid->consumer_release(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_globe(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + + bad_usb->hid->consumer_press(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + bad_usb->hid->kb_release(bad_usb->hid_inst, key); + bad_usb->hid->consumer_release(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); + return 0; +} + +static int32_t ducky_fnc_waitforbutton(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + UNUSED(bad_usb); + UNUSED(line); + + return SCRIPT_STATE_WAIT_FOR_BTN; +} + +static const DuckyCmd ducky_commands[] = { + {"REM", NULL, -1}, + {"ID", NULL, -1}, + {"DELAY", ducky_fnc_delay, -1}, + {"STRING", ducky_fnc_string, 0}, + {"STRINGLN", ducky_fnc_string, 1}, + {"DEFAULT_DELAY", ducky_fnc_defdelay, -1}, + {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, + {"STRINGDELAY", ducky_fnc_strdelay, -1}, + {"STRING_DELAY", ducky_fnc_strdelay, -1}, + {"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1}, + {"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -1}, + {"REPEAT", ducky_fnc_repeat, -1}, + {"SYSRQ", ducky_fnc_sysrq, -1}, + {"ALTCHAR", ducky_fnc_altchar, -1}, + {"ALTSTRING", ducky_fnc_altstring, -1}, + {"ALTCODE", ducky_fnc_altstring, -1}, + {"HOLD", ducky_fnc_hold, -1}, + {"RELEASE", ducky_fnc_release, -1}, + {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, + {"MEDIA", ducky_fnc_media, -1}, + {"GLOBE", ducky_fnc_globe, -1}, +}; + +#define TAG "BadBle" + +#define WORKER_TAG TAG "Worker" + +int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line) { + size_t cmd_word_len = strcspn(line, " "); + for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { + size_t cmd_compare_len = strlen(ducky_commands[i].name); + + if(cmd_compare_len != cmd_word_len) { + continue; + } + + if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) { + if(ducky_commands[i].callback == NULL) { + return 0; + } else { + return (ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param); + } + } + } + + return SCRIPT_STATE_CMD_UNKNOWN; +} diff --git a/applications/system/bad_ble/helpers/ducky_script_i.h b/applications/system/bad_ble/helpers/ducky_script_i.h new file mode 100644 index 0000000000..a5581d2065 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_i.h @@ -0,0 +1,76 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ducky_script.h" +#include "bad_ble_hid.h" + +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) +#define SCRIPT_STATE_CMD_UNKNOWN (-4) +#define SCRIPT_STATE_STRING_START (-5) +#define SCRIPT_STATE_WAIT_FOR_BTN (-6) + +#define FILE_BUFFER_LEN 16 + +struct BadBleScript { + FuriHalUsbHidConfig hid_cfg; + const BadBleHidApi* hid; + void* hid_inst; + FuriThread* thread; + BadBleState st; + + FuriString* file_path; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + + uint32_t defdelay; + uint32_t stringdelay; + uint32_t defstringdelay; + uint16_t layout[128]; + + FuriString* line; + FuriString* line_prev; + uint32_t repeat_cnt; + uint8_t key_hold_nb; + + FuriString* string_print; + size_t string_print_pos; +}; + +uint16_t ducky_get_keycode(BadBleScript* bad_usb, const char* param, bool accept_chars); + +uint32_t ducky_get_command_len(const char* line); + +bool ducky_is_line_end(const char chr); + +uint16_t ducky_get_keycode_by_name(const char* param); + +uint16_t ducky_get_media_keycode_by_name(const char* param); + +bool ducky_get_number(const char* param, uint32_t* val); + +void ducky_numlock_on(BadBleScript* bad_usb); + +bool ducky_numpad_press(BadBleScript* bad_usb, const char num); + +bool ducky_altchar(BadBleScript* bad_usb, const char* charcode); + +bool ducky_altstring(BadBleScript* bad_usb, const char* param); + +bool ducky_string(BadBleScript* bad_usb, const char* param); + +int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line); + +int32_t ducky_error(BadBleScript* bad_usb, const char* text, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_keycodes.c b/applications/system/bad_ble/helpers/ducky_script_keycodes.c new file mode 100644 index 0000000000..290618c131 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_keycodes.c @@ -0,0 +1,133 @@ +#include +#include "ducky_script_i.h" + +typedef struct { + char* name; + uint16_t keycode; +} DuckyKey; + +static const DuckyKey ducky_keys[] = { + {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, + {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, + {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, + {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, + {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, + {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, + + {"CTRL", KEY_MOD_LEFT_CTRL}, + {"CONTROL", KEY_MOD_LEFT_CTRL}, + {"SHIFT", KEY_MOD_LEFT_SHIFT}, + {"ALT", KEY_MOD_LEFT_ALT}, + {"GUI", KEY_MOD_LEFT_GUI}, + {"WINDOWS", KEY_MOD_LEFT_GUI}, + + {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UPARROW", HID_KEYBOARD_UP_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, + + {"ENTER", HID_KEYBOARD_RETURN}, + {"BREAK", HID_KEYBOARD_PAUSE}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, + {"BACKSPACE", HID_KEYBOARD_DELETE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"ESCAPE", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + {"APP", HID_KEYBOARD_APPLICATION}, + + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, + {"F13", HID_KEYBOARD_F13}, + {"F14", HID_KEYBOARD_F14}, + {"F15", HID_KEYBOARD_F15}, + {"F16", HID_KEYBOARD_F16}, + {"F17", HID_KEYBOARD_F17}, + {"F18", HID_KEYBOARD_F18}, + {"F19", HID_KEYBOARD_F19}, + {"F20", HID_KEYBOARD_F20}, + {"F21", HID_KEYBOARD_F21}, + {"F22", HID_KEYBOARD_F22}, + {"F23", HID_KEYBOARD_F23}, + {"F24", HID_KEYBOARD_F24}, +}; + +static const DuckyKey ducky_media_keys[] = { + {"POWER", HID_CONSUMER_POWER}, + {"REBOOT", HID_CONSUMER_RESET}, + {"SLEEP", HID_CONSUMER_SLEEP}, + {"LOGOFF", HID_CONSUMER_AL_LOGOFF}, + + {"EXIT", HID_CONSUMER_AC_EXIT}, + {"HOME", HID_CONSUMER_AC_HOME}, + {"BACK", HID_CONSUMER_AC_BACK}, + {"FORWARD", HID_CONSUMER_AC_FORWARD}, + {"REFRESH", HID_CONSUMER_AC_REFRESH}, + + {"SNAPSHOT", HID_CONSUMER_SNAPSHOT}, + + {"PLAY", HID_CONSUMER_PLAY}, + {"PAUSE", HID_CONSUMER_PAUSE}, + {"PLAY_PAUSE", HID_CONSUMER_PLAY_PAUSE}, + {"NEXT_TRACK", HID_CONSUMER_SCAN_NEXT_TRACK}, + {"PREV_TRACK", HID_CONSUMER_SCAN_PREVIOUS_TRACK}, + {"STOP", HID_CONSUMER_STOP}, + {"EJECT", HID_CONSUMER_EJECT}, + + {"MUTE", HID_CONSUMER_MUTE}, + {"VOLUME_UP", HID_CONSUMER_VOLUME_INCREMENT}, + {"VOLUME_DOWN", HID_CONSUMER_VOLUME_DECREMENT}, + + {"FN", HID_CONSUMER_FN_GLOBE}, + {"BRIGHT_UP", HID_CONSUMER_BRIGHTNESS_INCREMENT}, + {"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT}, +}; + +uint16_t ducky_get_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { + size_t key_cmd_len = strlen(ducky_keys[i].name); + if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_keys[i].keycode; + } + } + + return HID_KEYBOARD_NONE; +} + +uint16_t ducky_get_media_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_media_keys); i++) { + size_t key_cmd_len = strlen(ducky_media_keys[i].name); + if((strncmp(param, ducky_media_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_media_keys[i].keycode; + } + } + + return HID_CONSUMER_UNASSIGNED; +} diff --git a/applications/system/bad_ble/icon.png b/applications/system/bad_ble/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..27355f8dbab9f62f03c3114bd345117b72703df2 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsse8IOhE&W+{!>5Ur5*F)|L$Vj tulr2n@!{d|IW8GdR-3Ztdxz&lMuxeII5)+8P-F*b^>p=fS?83{1OR(_90~vc literal 0 HcmV?d00001 diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.c b/applications/system/bad_ble/scenes/bad_ble_scene.c new file mode 100644 index 0000000000..351bb1e794 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene.c @@ -0,0 +1,30 @@ +#include "bad_ble_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_ble_scene_on_enter_handlers[])(void*) = { +#include "bad_ble_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 bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_ble_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 bad_ble_scene_on_exit_handlers[])(void* context) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_ble_scene_handlers = { + .on_enter_handlers = bad_ble_scene_on_enter_handlers, + .on_event_handlers = bad_ble_scene_on_event_handlers, + .on_exit_handlers = bad_ble_scene_on_exit_handlers, + .scene_num = BadBleSceneNum, +}; diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.h b/applications/system/bad_ble/scenes/bad_ble_scene.h new file mode 100644 index 0000000000..25b19fc4b5 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadBleScene##id, +typedef enum { +#include "bad_ble_scene_config.h" + BadBleSceneNum, +} BadBleScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_ble_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_ble_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 "bad_ble_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 "bad_ble_scene_config.h" +#undef ADD_SCENE diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.c b/applications/system/bad_ble/scenes/bad_ble_scene_config.c new file mode 100644 index 0000000000..1f64f19039 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config.c @@ -0,0 +1,59 @@ +#include "../bad_ble_app_i.h" + +enum SubmenuIndex { + ConfigIndexKeyboardLayout, + ConfigIndexBleUnpair, +}; + +void bad_ble_scene_config_select_callback(void* context, uint32_t index) { + BadBleApp* bad_ble = context; + + view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index); +} + +static void draw_menu(BadBleApp* bad_ble) { + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_reset(var_item_list); + + variable_item_list_add(var_item_list, "Keyboard Layout (Global)", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Unpair Device", 0, NULL, NULL); +} + +void bad_ble_scene_config_on_enter(void* context) { + BadBleApp* bad_ble = context; + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_set_enter_callback( + var_item_list, bad_ble_scene_config_select_callback, bad_ble); + draw_menu(bad_ble); + variable_item_list_set_selected_item(var_item_list, 0); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig); +} + +bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == ConfigIndexKeyboardLayout) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout); + } else if(event.event == ConfigIndexBleUnpair) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfirmUnpair); + } else { + furi_crash("Unknown key type"); + } + } + + return consumed; +} + +void bad_ble_scene_config_on_exit(void* context) { + BadBleApp* bad_ble = context; + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.h b/applications/system/bad_ble/scenes/bad_ble_scene_config.h new file mode 100644 index 0000000000..5675fca59b --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config.h @@ -0,0 +1,7 @@ +ADD_SCENE(bad_ble, file_select, FileSelect) +ADD_SCENE(bad_ble, work, Work) +ADD_SCENE(bad_ble, error, Error) +ADD_SCENE(bad_ble, config, Config) +ADD_SCENE(bad_ble, config_layout, ConfigLayout) +ADD_SCENE(bad_ble, confirm_unpair, ConfirmUnpair) +ADD_SCENE(bad_ble, unpair_done, UnpairDone) diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c new file mode 100644 index 0000000000..594525dd7b --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c @@ -0,0 +1,49 @@ +#include "../bad_ble_app_i.h" +#include + +static bool bad_ble_layout_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_ble->keyboard_layout)) { + furi_string_set(predefined_path, bad_ble->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + browser_options.base_path = BAD_BLE_APP_PATH_LAYOUT_FOLDER; + browser_options.skip_assets = false; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_ble_scene_config_layout_on_enter(void* context) { + BadBleApp* bad_ble = context; + + if(bad_ble_layout_select(bad_ble)) { + scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneWork); + } else { + scene_manager_previous_scene(bad_ble->scene_manager); + } +} + +bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_config_layout_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c b/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c new file mode 100644 index 0000000000..63f1e92cf2 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c @@ -0,0 +1,53 @@ +#include "../bad_ble_app_i.h" + +void bad_ble_scene_confirm_unpair_widget_callback( + GuiButtonType type, + InputType input_type, + void* context) { + UNUSED(input_type); + SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type}; + bad_ble_scene_confirm_unpair_on_event(context, event); +} + +void bad_ble_scene_confirm_unpair_on_enter(void* context) { + BadBleApp* bad_ble = context; + Widget* widget = bad_ble->widget; + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Unpair", + bad_ble_scene_confirm_unpair_widget_callback, + context); + + widget_add_text_box_element( + widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget); +} + +bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + SceneManager* scene_manager = bad_ble->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone); + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(scene_manager); + } + } + + return consumed; +} + +void bad_ble_scene_confirm_unpair_on_exit(void* context) { + BadBleApp* bad_ble = context; + Widget* widget = bad_ble->widget; + + widget_reset(widget); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_error.c b/applications/system/bad_ble/scenes/bad_ble_scene_error.c new file mode 100644 index 0000000000..c9c2b12da2 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_error.c @@ -0,0 +1,65 @@ +#include "../bad_ble_app_i.h" + +typedef enum { + BadBleCustomEventErrorBack, +} BadBleCustomEvent; + +static void + bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + BadBleApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack); + } +} + +void bad_ble_scene_error_on_enter(void* context) { + BadBleApp* app = context; + + if(app->error == BadBleAppErrorNoFiles) { + widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); + widget_add_string_multiline_element( + app->widget, + 81, + 4, + AlignCenter, + AlignTop, + FontSecondary, + "No SD card or\napp data found.\nThis app will not\nwork without\nrequired files."); + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app); + } else if(app->error == BadBleAppErrorCloseRpc) { + widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); + widget_add_string_multiline_element( + app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nIs Active!"); + widget_add_string_multiline_element( + app->widget, + 3, + 30, + AlignLeft, + AlignTop, + FontSecondary, + "Disconnect from\nPC or phone to\nuse this function."); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWidget); +} + +bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBleCustomEventErrorBack) { + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } + } + return consumed; +} + +void bad_ble_scene_error_on_exit(void* context) { + BadBleApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c new file mode 100644 index 0000000000..2a182a874d --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c @@ -0,0 +1,46 @@ +#include "../bad_ble_app_i.h" +#include +#include + +static bool bad_ble_file_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_BLE_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options); + + return res; +} + +void bad_ble_scene_file_select_on_enter(void* context) { + BadBleApp* bad_ble = context; + + if(bad_ble->bad_ble_script) { + bad_ble_script_close(bad_ble->bad_ble_script); + bad_ble->bad_ble_script = NULL; + } + + if(bad_ble_file_select(bad_ble)) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork); + } else { + view_dispatcher_stop(bad_ble->view_dispatcher); + } +} + +bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_file_select_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c new file mode 100644 index 0000000000..4c1fe3366b --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c @@ -0,0 +1,37 @@ +#include "../bad_ble_app_i.h" + +static void bad_ble_scene_unpair_done_popup_callback(void* context) { + BadBleApp* bad_ble = context; + scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneConfig); +} + +void bad_ble_scene_unpair_done_on_enter(void* context) { + BadBleApp* bad_ble = context; + Popup* popup = bad_ble->popup; + + popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58); + popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom); + popup_set_callback(popup, bad_ble_scene_unpair_done_popup_callback); + popup_set_context(popup, bad_ble); + popup_set_timeout(popup, 1000); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewPopup); +} + +bool bad_ble_scene_unpair_done_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + UNUSED(bad_ble); + UNUSED(event); + + bool consumed = false; + + return consumed; +} + +void bad_ble_scene_unpair_done_on_exit(void* context) { + BadBleApp* bad_ble = context; + Popup* popup = bad_ble->popup; + + popup_reset(popup); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_work.c b/applications/system/bad_ble/scenes/bad_ble_scene_work.c new file mode 100644 index 0000000000..ff71edc3c2 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_work.c @@ -0,0 +1,65 @@ +#include "../helpers/ducky_script.h" +#include "../bad_ble_app_i.h" +#include "../views/bad_ble_view.h" +#include +#include "toolbox/path.h" + +void bad_ble_scene_work_button_callback(InputKey key, void* context) { + furi_assert(context); + BadBleApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, key); +} + +bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InputKeyLeft) { + if(bad_ble_view_is_idle_state(app->bad_ble_view)) { + bad_ble_script_close(app->bad_ble_script); + app->bad_ble_script = NULL; + + scene_manager_next_scene(app->scene_manager, BadBleSceneConfig); + } + consumed = true; + } else if(event.event == InputKeyOk) { + bad_ble_script_start_stop(app->bad_ble_script); + consumed = true; + } else if(event.event == InputKeyRight) { + bad_ble_script_pause_resume(app->bad_ble_script); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + } + return consumed; +} + +void bad_ble_scene_work_on_enter(void* context) { + BadBleApp* app = context; + + app->bad_ble_script = bad_ble_script_open(app->file_path, app->interface); + bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout); + + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + bad_ble_view_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name)); + furi_string_free(file_name); + + FuriString* layout; + layout = furi_string_alloc(); + path_extract_filename(app->keyboard_layout, layout, true); + bad_ble_view_set_layout(app->bad_ble_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + + bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + + bad_ble_view_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork); +} + +void bad_ble_scene_work_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/system/bad_ble/views/bad_ble_view.c b/applications/system/bad_ble/views/bad_ble_view.c new file mode 100644 index 0000000000..28f935733e --- /dev/null +++ b/applications/system/bad_ble/views/bad_ble_view.c @@ -0,0 +1,284 @@ +#include "bad_ble_view.h" +#include "../helpers/ducky_script.h" +#include +#include +#include +#include "bad_ble_icons.h" + +#define MAX_NAME_LEN 64 + +struct BadBle { + View* view; + BadBleButtonCallback callback; + void* context; +}; + +typedef struct { + char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; + BadBleState state; + bool pause_wait; + uint8_t anim_frame; +} BadBleModel; + +static void bad_ble_draw_callback(Canvas* canvas, void* _model) { + BadBleModel* model = _model; + + FuriString* disp_str; + disp_str = furi_string_alloc_set(model->file_name); + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); + + if(strlen(model->layout) == 0) { + furi_string_set(disp_str, "(default)"); + } else { + furi_string_printf(disp_str, "(%s)", model->layout); + } + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_draw_str( + canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); + + furi_string_reset(disp_str); + + canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22); + + BadBleWorkerState state = model->state.state; + + if((state == BadBleStateIdle) || (state == BadBleStateDone) || + (state == BadBleStateNotConnected)) { + elements_button_center(canvas, "Run"); + elements_button_left(canvas, "Config"); + } else if((state == BadBleStateRunning) || (state == BadBleStateDelay)) { + elements_button_center(canvas, "Stop"); + if(!model->pause_wait) { + elements_button_right(canvas, "Pause"); + } + } else if(state == BadBleStatePaused) { + elements_button_center(canvas, "End"); + elements_button_right(canvas, "Resume"); + } else if(state == BadBleStateWaitForBtn) { + elements_button_center(canvas, "Press to continue"); + } else if(state == BadBleStateWillRun) { + elements_button_center(canvas, "Cancel"); + } + + if(state == BadBleStateNotConnected) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device"); + } else if(state == BadBleStateWillRun) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); + } else if(state == BadBleStateFileError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); + } else if(state == BadBleStateScriptError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); + canvas_set_font(canvas, FontSecondary); + furi_string_printf(disp_str, "line %zu", model->state.error_line); + canvas_draw_str_aligned( + canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + + furi_string_set_str(disp_str, model->state.error); + elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); + canvas_draw_str_aligned( + canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else if(state == BadBleStateIdle) { + canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateRunning) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateDone) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateDelay) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); + canvas_draw_str_aligned( + canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else if((state == BadBleStatePaused) || (state == BadBleStateWaitForBtn)) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); + furi_string_reset(disp_str); + } else { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + } + + furi_string_free(disp_str); +} + +static bool bad_ble_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BadBle* bad_ble = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } else if(event->key == InputKeyOk) { + with_view_model( + bad_ble->view, BadBleModel * model, { model->pause_wait = false; }, true); + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } else if(event->key == InputKeyRight) { + with_view_model( + bad_ble->view, + BadBleModel * model, + { + if((model->state.state == BadBleStateRunning) || + (model->state.state == BadBleStateDelay)) { + model->pause_wait = true; + } + }, + true); + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } + } + + return consumed; +} + +BadBle* bad_ble_view_alloc(void) { + BadBle* bad_ble = malloc(sizeof(BadBle)); + + bad_ble->view = view_alloc(); + view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel)); + view_set_context(bad_ble->view, bad_ble); + view_set_draw_callback(bad_ble->view, bad_ble_draw_callback); + view_set_input_callback(bad_ble->view, bad_ble_input_callback); + + return bad_ble; +} + +void bad_ble_view_free(BadBle* bad_ble) { + furi_assert(bad_ble); + view_free(bad_ble->view); + free(bad_ble); +} + +View* bad_ble_view_get_view(BadBle* bad_ble) { + furi_assert(bad_ble); + return bad_ble->view; +} + +void bad_ble_view_set_button_callback( + BadBle* bad_ble, + BadBleButtonCallback callback, + void* context) { + furi_assert(bad_ble); + furi_assert(callback); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + UNUSED(model); + bad_ble->callback = callback; + bad_ble->context = context; + }, + true); +} + +void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name) { + furi_assert(name); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->file_name, name, MAX_NAME_LEN); }, + true); +} + +void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout) { + furi_assert(layout); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->layout, layout, MAX_NAME_LEN); }, + true); +} + +void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st) { + furi_assert(st); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + memcpy(&(model->state), st, sizeof(BadBleState)); + model->anim_frame ^= 1; + if(model->state.state == BadBleStatePaused) { + model->pause_wait = false; + } + }, + true); +} + +bool bad_ble_view_is_idle_state(BadBle* bad_ble) { + bool is_idle = false; + with_view_model( + bad_ble->view, + BadBleModel * model, + { + if((model->state.state == BadBleStateIdle) || + (model->state.state == BadBleStateDone) || + (model->state.state == BadBleStateNotConnected)) { + is_idle = true; + } + }, + false); + return is_idle; +} diff --git a/applications/system/bad_ble/views/bad_ble_view.h b/applications/system/bad_ble/views/bad_ble_view.h new file mode 100644 index 0000000000..e26488818e --- /dev/null +++ b/applications/system/bad_ble/views/bad_ble_view.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "../helpers/ducky_script.h" + +typedef struct BadBle BadBle; +typedef void (*BadBleButtonCallback)(InputKey key, void* context); + +BadBle* bad_ble_view_alloc(void); + +void bad_ble_view_free(BadBle* bad_ble); + +View* bad_ble_view_get_view(BadBle* bad_ble); + +void bad_ble_view_set_button_callback( + BadBle* bad_ble, + BadBleButtonCallback callback, + void* context); + +void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name); + +void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout); + +void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st); + +bool bad_ble_view_is_idle_state(BadBle* bad_ble); From a905c1492d5af92c38c41732f6383aa036c84431 Mon Sep 17 00:00:00 2001 From: noproto Date: Sun, 6 Oct 2024 16:19:26 -0400 Subject: [PATCH 122/236] Handle invalid key candidates --- .../protocols/mf_classic/mf_classic_poller.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index 1a2e43deec..e8f660b161 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -10,6 +10,7 @@ // TODO: Store target key in CUID dictionary // TODO: Fix rare nested_target_key 64 bug // TODO: Dead code for malloc returning NULL? +// TODO: Auth1 static encrypted exists (rare) #define MF_CLASSIC_MAX_BUFF_SIZE (64) @@ -606,6 +607,7 @@ NfcCommand mf_classic_poller_handler_analyze_backdoor(MfClassicPoller* instance) } NfcCommand mf_classic_poller_handler_backdoor_read_sector(MfClassicPoller* instance) { + // TODO: Reauth not needed NfcCommand command = NfcCommandContinue; MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; MfClassicError error = MfClassicErrorNone; @@ -1854,6 +1856,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } else if(dict_attack_ctx->prng_type == MfClassicPrngTypeNoTag) { FURI_LOG_E(TAG, "No tag detected"); // Free nonce array + // TODO: Consider using .count here if(dict_attack_ctx->nested_nonce.nonces) { free(dict_attack_ctx->nested_nonce.nonces); dict_attack_ctx->nested_nonce.nonces = NULL; @@ -1864,6 +1867,7 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance } if(dict_attack_ctx->nested_nonce.nonces) { // Free nonce array + // TODO: Consider using .count here free(dict_attack_ctx->nested_nonce.nonces); dict_attack_ctx->nested_nonce.nonces = NULL; dict_attack_ctx->nested_nonce.count = 0; @@ -1877,15 +1881,19 @@ NfcCommand mf_classic_poller_handler_nested_controller(MfClassicPoller* instance (instance->sectors_total * 2) : (instance->sectors_total * 16); if(dict_attack_ctx->nested_phase == MfClassicNestedPhaseDictAttackResume) { - if(!(mf_classic_nested_is_target_key_found(instance, true))) { + if(!(mf_classic_nested_is_target_key_found(instance, true)) && + (dict_attack_ctx->nested_nonce.count > 0)) { instance->state = MfClassicPollerStateNestedDictAttack; return command; } else { dict_attack_ctx->auth_passed = true; - furi_assert(dict_attack_ctx->nested_nonce.nonces); - free(dict_attack_ctx->nested_nonce.nonces); - dict_attack_ctx->nested_nonce.nonces = NULL; - dict_attack_ctx->nested_nonce.count = 0; + if(dict_attack_ctx->nested_nonce.count > 0) { + // Free nonce array + furi_assert(dict_attack_ctx->nested_nonce.nonces); + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } dict_attack_ctx->nested_phase = MfClassicNestedPhaseDictAttack; } } From 86b8c8d8592325655c79dd3b5f9c2e34016f136a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 7 Oct 2024 04:03:07 +0300 Subject: [PATCH 123/236] LED universal remote DB by amec0e other things by me --- .../resources/infrared/assets/leds.ir | 2938 +++++++++++++++++ .../infrared/scenes/infrared_scene_config.h | 1 + .../scenes/infrared_scene_universal.c | 11 + .../scenes/infrared_scene_universal_leds.c | 133 + assets/icons/Infrared/blue_19x20.png | Bin 0 -> 202 bytes assets/icons/Infrared/blue_hover_19x20.png | Bin 0 -> 186 bytes .../icons/Infrared/brightness_text_40x5.png | Bin 0 -> 182 bytes assets/icons/Infrared/color_text_24x5.png | Bin 0 -> 152 bytes assets/icons/Infrared/green_19x20.png | Bin 0 -> 214 bytes assets/icons/Infrared/green_hover_19x20.png | Bin 0 -> 198 bytes assets/icons/Infrared/minus_19x20.png | Bin 0 -> 168 bytes assets/icons/Infrared/minus_hover_19x20.png | Bin 0 -> 151 bytes assets/icons/Infrared/on_text_9x5.png | Bin 0 -> 121 bytes assets/icons/Infrared/plus_19x20.png | Bin 0 -> 175 bytes assets/icons/Infrared/plus_hover_19x20.png | Bin 0 -> 161 bytes assets/icons/Infrared/red_19x20.png | Bin 0 -> 202 bytes assets/icons/Infrared/red_hover_19x20.png | Bin 0 -> 196 bytes assets/icons/Infrared/white_19x20.png | Bin 0 -> 210 bytes assets/icons/Infrared/white_hover_19x20.png | Bin 0 -> 198 bytes 19 files changed, 3083 insertions(+) create mode 100644 applications/main/infrared/resources/infrared/assets/leds.ir create mode 100644 applications/main/infrared/scenes/infrared_scene_universal_leds.c create mode 100644 assets/icons/Infrared/blue_19x20.png create mode 100644 assets/icons/Infrared/blue_hover_19x20.png create mode 100644 assets/icons/Infrared/brightness_text_40x5.png create mode 100644 assets/icons/Infrared/color_text_24x5.png create mode 100644 assets/icons/Infrared/green_19x20.png create mode 100644 assets/icons/Infrared/green_hover_19x20.png create mode 100644 assets/icons/Infrared/minus_19x20.png create mode 100644 assets/icons/Infrared/minus_hover_19x20.png create mode 100644 assets/icons/Infrared/on_text_9x5.png create mode 100644 assets/icons/Infrared/plus_19x20.png create mode 100644 assets/icons/Infrared/plus_hover_19x20.png create mode 100644 assets/icons/Infrared/red_19x20.png create mode 100644 assets/icons/Infrared/red_hover_19x20.png create mode 100644 assets/icons/Infrared/white_19x20.png create mode 100644 assets/icons/Infrared/white_hover_19x20.png diff --git a/applications/main/infrared/resources/infrared/assets/leds.ir b/applications/main/infrared/resources/infrared/assets/leds.ir new file mode 100644 index 0000000000..7778356bf7 --- /dev/null +++ b/applications/main/infrared/resources/infrared/assets/leds.ir @@ -0,0 +1,2938 @@ +Filetype: IR library file +Version: 1 +# Last Updated 5th Oct, 2024 +# Last Checked 5th Oct, 2024 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 47 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9249 4403 683 479 659 478 659 479 657 481 656 483 659 488 650 488 650 488 649 1588 649 1589 649 1611 629 1612 626 511 629 1612 627 1612 626 1612 626 1589 649 1611 626 512 626 512 625 512 626 512 626 512 626 512 626 512 626 512 626 1612 626 1612 626 1613 627 1612 626 1612 626 1612 626 40625 9269 2159 651 95392 9285 2158 652 95392 9275 2158 652 95396 9266 2158 651 95395 9242 2158 649 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9219 4427 633 504 658 479 657 479 656 481 654 483 653 486 651 486 651 485 651 1586 651 1586 651 1587 650 1586 650 486 651 1587 650 1588 649 1588 649 487 650 1587 651 487 649 487 650 487 649 488 649 488 649 488 649 1611 626 488 650 1611 626 1612 626 1612 625 1611 626 1611 626 1611 626 40628 9247 2183 626 95429 9236 2183 626 95411 9236 2159 649 95438 9224 2158 650 95435 9216 2158 650 95432 9229 2159 650 95437 9225 2159 650 95447 9218 2159 651 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9227 4429 632 505 656 479 656 480 655 482 654 484 652 488 649 488 649 487 650 1612 626 1612 627 1612 627 1612 627 512 626 1612 626 1612 626 1612 627 512 627 512 627 1613 626 512 625 512 626 512 625 512 625 512 626 1613 625 1613 625 512 626 1614 626 1613 627 1613 626 1614 625 1613 624 40639 9235 2184 625 95445 9231 2184 625 95422 9250 2159 650 95420 9259 2159 651 95415 9248 2159 650 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9233 4403 685 479 658 479 657 480 656 482 654 485 651 486 651 486 651 486 651 1588 651 1587 652 1587 651 1587 651 487 650 1588 650 1588 650 1588 649 1588 649 487 649 1588 650 488 649 488 649 488 649 488 649 487 649 488 649 1589 648 488 649 1589 648 1589 649 1589 649 1590 648 1590 648 40649 9223 2160 649 95425 9235 2160 649 95430 9241 2159 650 95435 9236 2160 649 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9213 4427 633 504 633 504 658 479 658 481 655 486 650 511 626 512 626 512 625 1613 625 1613 626 1613 626 1613 626 512 626 1613 625 1613 625 1612 626 511 627 1612 626 1612 627 512 626 512 626 512 626 511 627 512 626 1612 628 512 627 489 649 1588 651 1589 649 1589 649 1589 650 1588 651 40647 9223 2160 650 95428 9248 2160 650 95434 9234 2160 649 95437 9240 2160 650 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9224 4425 632 503 632 504 656 479 656 481 653 484 651 510 625 511 625 511 625 1612 624 1612 624 1613 625 1613 626 513 624 1615 620 1642 597 1641 574 1665 598 1642 596 1642 571 565 571 565 571 565 571 565 571 565 571 565 571 565 571 565 571 1666 570 1666 571 1641 596 1641 596 1641 596 40681 9202 2182 624 95465 9213 2159 650 95436 9244 2159 650 95454 9225 2159 650 95459 9233 2160 650 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9231 4403 685 479 658 479 657 480 656 481 655 484 652 486 651 486 651 486 651 1587 650 1588 650 1587 650 1587 651 486 651 1587 650 1587 650 1587 650 487 649 487 650 487 650 487 650 487 650 488 649 487 649 487 649 1588 649 1588 649 1588 649 1588 649 1588 649 1588 649 1589 648 1589 648 40654 9203 2159 649 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9237 4405 684 480 658 479 658 480 656 482 654 485 652 487 650 487 649 487 650 1588 648 1588 649 1588 649 1588 648 488 648 1588 649 1588 648 1589 648 1589 648 488 648 488 648 488 648 488 648 489 647 488 648 488 647 488 647 1589 647 1590 647 1590 647 1590 647 1614 623 1614 624 1614 624 40649 9227 2161 649 95434 9243 2160 650 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9029 4463 592 540 598 534 594 539 599 533 595 538 600 532 596 537 601 531 597 1642 593 1647 598 1640 595 1643 592 1647 598 1640 595 1644 601 1637 598 534 594 539 599 1639 596 1642 593 1646 599 532 596 1643 592 540 598 1641 594 1644 601 531 649 431 707 477 599 1641 594 538 600 1638 597 39704 9026 2222 598 95884 9034 2217 603 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5D 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 58 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 59 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 44 00 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 03 FC 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 02 FD 00 00 +# +name: White +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 04 FB 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 06 F9 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 07 F8 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 08 B7 00 00 +command: 00 FF 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 08 B7 00 00 +command: 02 FD 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 08 B7 00 00 +command: 10 EF 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 08 B7 00 00 +command: 12 ED 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 04 FB 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 05 FA 00 00 +# +name: Red +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 09 F6 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0A F5 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0B F4 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 01 EF 00 00 +command: 00 FF 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 01 EF 00 00 +command: 05 FA 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 01 EF 00 00 +command: 04 FB 00 00 +# +name: White +type: parsed +protocol: NECext +address: 01 EF 00 00 +command: 02 FD 00 00 +# +name: White +type: parsed +protocol: NECext +address: 01 EF 00 00 +command: 44 BB 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 00 FF 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 01 FE 00 00 +# +name: Red +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 04 FB 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 05 FA 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 06 F9 00 00 +# +name: White +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 07 F8 00 00 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9227 4492 622 521 603 542 623 522 632 512 632 513 631 514 630 514 630 515 629 1628 630 1629 629 1630 628 1633 604 545 630 1630 628 1631 627 1631 627 518 626 1632 626 519 625 520 624 520 624 521 623 521 603 542 623 1634 624 521 623 1634 624 1633 625 1633 625 1632 605 1652 626 1631 596 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9234 4488 626 519 625 520 634 510 634 511 633 512 632 513 631 515 629 516 628 1631 627 1633 635 1625 633 1628 630 518 636 1624 634 1627 631 1631 637 511 633 515 629 519 635 514 630 519 635 513 631 516 638 512 632 1627 631 1631 637 1624 634 1626 632 1628 630 1629 629 1633 625 1634 634 41137 9266 2204 631 96573 9235 2204 631 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9349 4519 636 519 635 521 633 522 632 523 611 544 641 514 640 517 606 549 605 1663 636 1631 637 1629 639 1627 631 522 632 1633 635 1629 629 1631 606 1653 625 520 624 521 633 512 632 513 631 514 630 516 628 516 628 518 626 1632 626 1633 625 1634 634 1625 633 1626 632 1626 632 1627 631 41160 9200 2214 631 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9223 4493 632 512 632 512 632 512 632 513 631 513 631 513 631 514 630 514 630 1629 629 1629 629 1630 628 1631 627 518 626 1632 626 1633 625 1634 624 1635 633 1625 633 1626 632 513 631 513 631 513 631 512 632 512 632 512 632 512 632 512 622 1635 623 1634 624 1634 624 1633 625 1632 626 41178 9189 2219 627 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 15 EA 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 44 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9242 4488 624 520 625 520 625 521 650 497 648 525 620 527 619 527 619 527 619 1640 619 1640 618 1640 618 1640 618 1640 618 1640 618 1640 618 1640 618 1640 618 527 619 527 619 527 619 527 619 528 618 528 618 528 618 528 618 1640 618 1641 617 1641 617 1641 618 1641 617 1641 618 1641 617 40000 9266 2216 619 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9272 4489 625 520 651 496 649 525 620 526 619 528 617 529 617 528 618 529 617 1642 617 1642 617 1642 616 1642 617 1642 617 1642 617 1642 616 1642 617 529 617 529 617 529 617 529 617 529 617 529 617 529 617 529 617 1643 616 1642 617 1642 616 1643 616 1643 616 1643 616 1643 616 1643 616 40017 9262 2219 618 96577 9262 2218 618 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9239 4490 626 519 652 495 651 496 649 524 621 527 618 529 617 529 617 528 618 1642 618 1642 618 1642 618 1642 618 1643 617 1642 618 1642 618 1642 618 528 618 528 618 1642 618 1642 617 529 617 528 618 528 618 528 618 1642 617 1643 617 529 617 529 617 1642 618 1643 617 1643 617 1643 616 40036 9262 2218 618 96635 9259 2221 617 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9226 4494 624 521 624 522 648 498 647 526 618 528 618 528 618 528 618 528 618 1642 618 1642 617 1642 617 1642 617 1642 617 1642 618 1642 617 1642 617 1642 617 528 617 528 618 1642 617 528 618 528 617 528 618 528 618 528 617 1642 617 1642 618 528 618 1642 618 1642 617 1643 617 1642 618 40047 9252 2220 618 96654 9252 2221 617 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9272 4494 627 521 652 498 649 526 621 527 620 529 619 530 618 530 618 530 618 1644 618 1644 618 1644 618 1644 617 1644 617 1644 617 1644 617 1644 617 530 617 1644 617 530 617 1644 617 530 617 530 617 530 617 530 617 1644 617 530 617 1644 617 530 617 1644 617 1644 617 1645 616 1644 617 40058 9263 2222 617 96673 9264 2222 617 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9300 4491 654 495 653 496 651 498 648 526 620 528 620 529 619 529 619 529 619 1643 619 1643 619 1643 619 1643 619 1643 619 1643 619 1643 619 1643 619 529 619 1643 619 1643 619 529 619 1643 619 529 619 529 619 529 619 1644 618 529 619 529 619 1644 618 530 618 1644 618 1644 618 1644 618 40055 9278 2219 619 96665 9277 2219 619 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9245 4492 605 542 605 1676 605 542 605 1675 604 542 604 542 604 542 604 542 604 1675 604 542 629 517 628 519 626 1654 624 1655 624 1655 624 1655 623 1655 623 521 624 522 623 1655 623 522 623 521 624 521 623 522 623 521 624 1655 622 1655 623 522 622 1655 621 1655 622 1655 622 1655 622 40679 9209 2205 621 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9230 4483 659 487 658 1620 605 541 604 1674 604 541 604 540 605 540 605 540 605 1673 605 541 628 518 626 545 599 1680 598 1680 598 1680 598 1680 598 547 597 1680 598 547 597 1681 596 548 596 548 596 548 572 573 596 1682 571 574 595 1683 569 574 569 1732 544 1733 543 1733 543 1734 543 40712 9182 2224 597 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9202 4490 602 541 603 1675 602 541 603 1674 602 541 602 541 602 542 602 541 602 1674 602 541 602 542 626 518 625 1652 624 1654 623 1653 623 1653 623 1654 623 1653 623 520 623 1654 623 520 623 520 623 520 623 520 623 520 623 520 623 1654 622 520 623 1654 622 1653 623 1653 623 1653 623 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9236 4438 652 517 627 1624 654 516 627 1649 603 541 602 541 602 540 627 516 627 1649 626 517 624 520 623 521 622 1654 622 1654 622 1654 622 1654 622 521 621 1654 622 521 621 1654 622 1654 622 521 622 521 621 522 621 1654 621 521 621 1654 621 521 621 521 621 1655 620 1655 620 1655 620 40677 9178 2202 621 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9226 4462 654 516 628 1624 655 515 629 1625 627 541 603 541 603 541 628 516 628 1649 627 518 624 521 623 521 623 1654 622 1654 622 1654 622 1654 622 521 622 521 622 1654 623 1654 622 521 623 521 622 521 623 521 622 1654 623 1654 622 521 622 521 622 1655 622 1655 621 1655 621 1655 621 40686 9195 2203 621 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 0A F1 00 00 +command: 0E F1 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 47 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 12 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 0A 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1B 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1F 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 00 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 00 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 11 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0E 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 04 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 06 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9229 4485 610 539 605 541 614 534 610 536 660 487 606 540 604 543 612 535 609 1671 608 1673 606 1676 613 1668 611 1671 608 1673 606 1676 613 1669 610 540 615 535 609 539 605 543 612 537 607 542 613 1668 611 538 668 1613 614 1668 611 1671 608 1675 614 1667 612 1671 608 542 613 1668 611 39754 9217 2249 607 96357 9217 2246 610 96379 9206 2250 606 96376 9218 2245 611 96353 9201 2241 667 96293 9209 2237 620 96344 9200 2244 613 96343 9202 2247 610 96346 9199 2248 609 96343 9200 2247 609 96343 9221 2239 618 96344 9219 2243 614 96358 9228 2238 618 96345 9230 2241 616 96360 9215 2248 609 96370 9226 2242 615 96351 9222 2248 608 96359 9225 2241 616 96360 9215 2248 609 96374 9220 2242 615 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9200 4490 615 534 610 535 609 536 608 537 607 538 606 539 605 539 605 540 615 1662 617 1663 616 1663 616 1664 615 1665 614 1665 614 1666 613 1667 612 537 607 541 614 1663 616 1664 615 1666 613 536 608 1670 609 540 615 1665 614 1667 612 539 616 531 614 535 609 1668 611 540 615 1663 616 39753 9198 2242 615 96361 9194 2248 609 96367 9198 2243 614 96356 9199 2244 613 96361 9205 2237 620 96353 9203 2243 614 96356 9199 2243 614 96357 9200 2243 614 96358 9199 2245 612 96361 9195 2251 606 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9207 4489 616 531 613 532 613 534 611 535 609 535 609 536 608 537 607 538 617 1661 618 1661 618 1662 617 1662 617 1663 616 1664 615 1664 615 1665 614 1667 612 536 608 1671 618 1662 617 1663 616 533 611 1669 610 567 588 533 612 1668 611 566 589 532 612 535 609 1671 618 559 585 1667 612 39757 9205 2250 607 96504 9197 2246 611 96551 9202 2242 615 96318 9199 2243 614 96305 9171 2245 612 96307 9189 2244 613 96329 9188 2249 608 96627 9199 2248 609 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9222 4483 612 564 580 566 578 567 577 568 576 569 586 560 584 561 583 562 582 1666 613 1666 613 1667 612 1668 611 1669 610 1670 609 1671 608 1672 617 561 583 563 581 565 579 1670 609 1671 618 560 584 1665 614 564 580 1669 610 1671 618 1663 616 563 581 567 577 1672 617 561 583 1666 613 39760 9190 2248 609 96373 9190 2248 609 96376 9198 2242 615 96367 9198 2245 612 96366 9199 2241 616 96360 9194 2246 611 96367 9197 2244 613 96366 9190 2251 616 96362 9194 2248 609 96369 9197 2244 613 96365 9192 2249 608 96370 9199 2242 615 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9236 4456 639 507 637 508 606 540 604 541 603 542 603 543 601 543 601 544 611 1668 611 1667 612 1667 612 1667 612 1668 611 1667 612 1668 611 1668 663 1618 609 537 607 539 605 1674 605 1675 604 543 612 1668 611 536 608 539 605 1675 614 1666 613 535 609 538 606 1673 616 533 611 1667 612 39741 9202 2239 618 96334 9195 2244 613 96331 9198 2241 616 96330 9198 2244 613 96333 9196 2248 609 96334 9205 2240 617 96330 9200 2238 619 96325 9193 2247 610 96338 9200 2241 616 96339 9209 2248 609 96401 9199 2243 614 96340 9209 2240 617 96351 9208 2249 608 96360 9210 2250 669 96302 9205 2243 614 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 41 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 55 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5C 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 54 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 55 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 49 00 00 00 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 995 1000 995 998 997 997 988 1006 989 1032 963 1004 991 1029 966 8899 994 999 996 998 997 997 988 1006 989 1005 990 1003 992 1028 967 2986 963 1005 990 1030 965 1002 993 1001 994 2954 964 1031 964 2931 997 1055 961 1033 962 2933 995 999 996 2926 992 1002 993 1027 968 1026 969 1021 995 96857 993 1000 995 999 996 998 987 1007 988 1032 963 1005 990 1003 992 8900 993 1001 994 1026 969 1024 961 1033 962 1032 963 1031 964 1030 965 2961 988 1033 962 1031 964 1030 965 1029 966 2929 989 1032 963 2933 995 1056 970 1024 961 2961 967 1027 968 2927 991 1030 965 1029 966 1028 967 1049 967 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 990 1003 992 1029 966 1027 968 1026 969 1025 970 1024 961 1033 962 8903 990 1004 991 1029 966 1028 967 1027 968 1026 969 1024 961 1034 961 2991 968 1000 995 1025 970 1024 960 1033 962 1032 963 2959 969 999 996 2957 992 2956 972 1023 962 2960 968 1000 995 1025 970 997 988 1033 962 1054 962 98791 996 1024 971 1023 962 1032 963 1031 964 1030 965 1029 966 1028 967 8898 994 999 996 1024 971 997 987 1032 963 1031 964 1030 965 1029 966 2987 962 1032 963 1031 964 1030 965 1029 966 1028 967 2929 988 1005 990 2963 996 2926 991 1002 993 2956 962 1032 963 1031 964 1003 992 1029 966 1050 966 98787 992 1002 993 1027 968 1026 969 1025 970 1024 961 1033 962 1032 963 8902 991 1029 966 1002 993 1027 968 1026 969 1024 971 1023 962 1032 963 2990 969 999 996 1024 961 1033 962 1032 963 1031 964 2931 997 998 997 2955 994 2955 962 1005 990 2958 970 1025 970 1023 961 1006 989 1032 963 1053 963 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 7157 4072 642 1432 617 1428 621 1425 614 1432 617 1428 621 1425 1383 666 1383 658 981 1065 984 1074 8180 4067 647 1425 614 1432 617 1429 620 1425 614 1432 617 1427 1381 667 1382 663 987 1058 981 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8182 4067 647 1425 614 1433 616 1428 621 1424 1384 665 1374 672 619 1426 613 1428 990 1057 982 1076 8178 4066 648 1426 613 1432 617 1428 621 1425 1382 664 1375 672 619 1425 614 1428 980 1065 984 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8153 4070 644 1428 621 1423 1385 664 1375 673 618 1425 614 1431 618 1428 621 1419 989 1059 980 1077 8177 4068 646 1426 613 1433 1375 672 1377 671 620 1422 617 1429 620 1425 614 1428 980 1067 982 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4076 4497 331 539 335 583 291 532 331 539 335 584 290 581 293 532 331 541 333 1648 329 1651 336 1690 287 1696 291 1642 335 1648 339 1644 333 1649 338 536 338 1644 333 1650 337 537 337 536 338 537 337 536 338 537 337 1647 330 587 287 537 337 1694 293 1640 337 1692 285 1648 339 1690 287 59795 13271 2259 280 98396 13269 2276 273 98426 13292 2256 272 98428 13268 2283 277 98424 13273 2276 273 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 174 8463 294 580 262 609 265 626 279 578 264 626 279 563 259 592 172 1052 264 1731 256 1715 293 1694 345 1652 252 1734 253 1721 266 1721 266 3719 178 39 179 6228 281 4075 285 1710 298 1687 279 1708 279 1646 341 1705 272 59603 196 12564 180 183 181 2173 278 98492 247 10659 1351 122 174 339 290 2280 259 98501 250 10985 177 115 587 119 177 291 177 115 176 2295 249 98530 250 10658 2279 2284 255 98517 254 10758 1159 357 361 119 193 2281 248 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4082 4497 331 539 335 535 339 530 333 539 335 535 339 532 331 540 334 538 336 1609 378 1605 372 1609 378 1609 378 1607 380 1609 378 1634 343 1603 374 1639 338 534 340 1641 336 537 337 536 338 536 338 533 341 533 341 531 343 1636 341 499 343 1664 344 1635 342 1636 362 1618 338 1642 335 59711 13365 2274 275 98192 13430 2281 278 98226 13355 2284 276 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4070 4492 336 532 331 539 335 536 338 531 332 537 337 535 339 534 340 531 332 1648 339 1641 336 1645 332 1648 339 1641 336 1646 331 1653 334 1647 340 1643 334 539 335 539 335 1648 339 534 340 535 339 535 339 534 340 531 332 1649 338 1646 331 538 336 1646 331 1649 338 1642 335 1645 332 59768 13221 2279 280 98376 13242 2198 330 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4115 4504 324 582 281 538 336 535 339 530 333 537 337 505 369 502 372 531 332 1648 339 1643 334 1646 331 1650 337 1643 334 1649 338 1641 315 1689 179 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 201 8424 275 576 298 572 291 599 296 558 285 603 271 599 275 580 294 596 278 1684 293 3677 277 1711 276 1691 286 1640 337 1639 348 1635 342 546 338 1698 279 585 299 1636 341 545 339 537 347 541 353 509 344 1635 404 536 348 1638 349 539 376 1638 328 79436 203 3212 773 4525 282 548 357 506 357 530 354 542 342 506 409 515 411 587 276 545 349 1677 289 1690 266 1709 268 1710 267 1714 263 1717 291 1693 263 1718 269 607 267 1722 265 613 172 1814 261 616 268 607 267 612 262 615 259 1721 297 579 263 1720 267 606 268 1715 272 1714 263 1723 264 1720 267 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 175 8455 280 593 301 565 288 585 278 583 270 597 297 576 298 563 181 1065 303 5651 280 3674 301 1698 279 7614 346 532 342 1710 277 539 345 535 339 539 345 584 279 598 276 2584 308 567 275 3701 294 1688 278 1709 278 59644 176 11236 179 1178 174 4633 263 97280 248 10633 4435 2254 253 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0E 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 10 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 12 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9296 4514 603 547 607 544 610 541 613 538 606 545 609 542 612 538 606 1657 610 1653 604 1658 609 1654 613 1649 608 1655 612 1650 607 1656 611 540 604 547 607 1655 612 539 605 546 608 1654 613 537 607 544 610 541 613 1649 608 543 611 1651 606 1657 610 540 604 1659 608 1654 613 1650 607 40011 9303 2233 606 96496 9310 2232 607 96508 9306 2235 604 96514 9303 2234 605 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9362 4515 612 541 613 540 614 538 606 547 607 545 609 544 610 542 612 1652 615 1648 609 1682 585 1679 577 1685 582 1682 585 1679 588 1676 580 544 610 542 612 1651 606 547 607 1655 612 541 613 539 615 538 616 540 614 1651 616 538 606 1659 608 546 608 1656 611 1654 613 1651 606 1659 608 40015 9283 2235 604 96515 9276 2238 611 96503 9299 2232 607 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9417 4514 613 545 609 548 616 541 613 543 611 546 608 548 616 541 613 1657 620 1677 590 1651 616 1653 614 1654 613 1655 612 1655 612 1655 612 544 610 1656 611 1684 583 545 609 1657 610 1684 583 546 608 548 616 540 614 546 608 547 607 1659 618 539 615 542 612 1655 612 1683 584 1657 610 40015 9385 2240 620 96496 9356 2233 616 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9421 4508 619 541 613 545 609 547 607 549 615 539 615 540 614 540 614 1652 615 1654 613 1681 586 1681 586 1680 587 1678 589 1677 579 1685 582 545 609 1654 613 1677 580 1682 585 1678 579 1656 611 539 605 546 608 542 602 548 606 543 611 538 606 544 610 540 604 1656 611 1650 607 1682 585 40013 9274 2239 610 96511 9281 2234 605 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9419 4510 617 544 620 539 615 545 619 540 614 546 618 539 615 541 613 1654 613 1653 613 1652 615 1650 606 1657 610 1654 613 1650 606 1659 608 546 608 547 617 538 616 541 613 544 610 547 617 538 616 541 613 544 610 1659 618 1650 617 1650 617 1649 607 1658 609 1656 611 1656 611 1655 612 39870 9281 2236 613 96484 9266 2234 605 96493 9287 2236 613 96178 9362 2238 611 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 59 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9226 4477 611 532 608 531 609 529 600 539 601 537 603 536 604 534 606 532 608 1632 606 1635 603 1639 609 1631 607 532 608 1633 605 1636 602 1639 609 529 600 1640 608 530 599 539 601 537 603 535 605 533 607 531 598 1641 607 531 609 1631 607 1634 604 1637 601 1640 608 1632 606 1635 603 40894 9165 2219 599 95821 9175 2215 603 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9200 4477 611 556 573 565 575 535 605 532 597 539 601 535 605 531 598 538 602 1636 602 1638 600 1640 598 1641 607 556 573 1639 599 1640 608 1631 607 557 572 1639 599 1641 607 556 573 536 604 533 607 529 600 536 604 1635 602 561 579 531 598 1641 607 1632 606 1633 605 1635 603 1636 602 40899 9139 2210 608 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 48 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9752 4512 678 519 629 574 636 569 631 571 629 572 628 572 628 571 629 573 627 1689 629 1683 625 1685 623 1686 622 1687 621 1688 630 1678 619 1686 622 1683 625 1681 627 567 623 575 625 571 619 576 624 572 628 1679 629 567 623 573 627 1681 627 1677 620 1686 622 1680 617 1681 616 571 619 39161 9413 2260 622 96457 9493 2267 615 96491 9491 2260 622 99522 2889 75674 9757 4519 630 573 627 578 632 572 628 578 632 573 627 578 632 572 628 575 635 1675 622 1676 621 1677 610 1687 621 1677 610 1687 621 1678 609 1688 620 1683 614 1687 631 568 621 575 625 577 633 568 632 569 631 1682 626 573 627 573 627 1684 624 1687 631 1677 631 1678 630 1680 628 569 631 39165 9531 2265 617 97240 9673 2263 629 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9526 4512 627 568 622 572 628 567 623 571 619 575 625 568 622 570 620 571 619 1683 625 1678 619 1682 626 1676 621 1681 627 1675 623 1679 619 1683 625 565 625 1674 623 596 593 595 595 594 596 594 596 593 586 1685 623 1677 621 596 594 1677 621 1679 618 1681 616 1682 615 1683 614 600 590 39182 9434 2264 618 96516 9414 2260 612 96510 9460 2258 614 96482 9561 2269 634 96516 9856 2270 633 96479 9862 2271 642 96516 9898 2271 642 96486 9876 2273 691 96432 9879 2265 638 96485 9866 2273 640 96480 9858 2266 637 96530 9891 2271 642 96726 9890 2271 642 96496 9884 2271 642 96493 9886 2265 638 96502 9888 2267 646 96494 9886 2267 646 96494 9898 2268 645 96500 9911 2271 642 96513 9921 2266 647 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9512 4513 626 592 587 599 591 596 594 592 587 598 592 594 585 600 590 595 584 1684 624 1676 622 1677 621 1675 612 1682 616 1679 608 1684 614 1680 607 601 589 564 615 564 615 565 614 565 614 565 614 593 586 1676 611 1681 606 1686 612 1680 607 1685 612 1680 607 1685 613 1681 616 567 612 39181 9353 2262 610 96521 9346 2265 607 96503 9407 2263 609 96555 9468 2267 615 96789 10035 2284 639 98340 9868 2281 642 96680 10006 2271 642 96640 10007 2278 645 96746 10013 2277 646 96773 10027 2281 642 96844 10049 2275 648 96842 10063 2279 644 96641 9494 2270 622 96592 9459 2262 620 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 10190 4555 656 579 652 583 648 587 654 581 650 584 647 586 655 578 653 581 649 1701 658 1695 654 1699 650 1702 647 1704 655 1693 645 1703 656 1694 655 1698 651 586 655 583 658 581 650 589 652 584 657 579 652 1700 649 586 655 1699 650 1704 655 1698 651 1703 656 1699 650 1705 654 585 656 39849 10069 2284 650 54703 10223 4549 651 586 655 584 657 581 660 581 660 581 660 581 660 581 660 581 660 1699 660 1699 660 1701 658 1704 655 1706 653 1708 651 1708 661 1699 660 1701 658 584 657 585 656 587 654 588 653 588 653 588 653 1706 653 588 653 1705 654 1705 654 1705 654 1703 656 1701 658 1705 654 586 655 63890 10114 4552 649 588 653 584 657 580 651 586 655 582 649 588 653 583 658 579 651 1705 654 1706 653 1708 651 1706 653 1708 651 1709 650 1711 648 1710 649 1714 655 583 648 587 654 581 650 585 645 589 652 585 707 1652 656 583 658 1701 658 1701 658 1699 660 1699 650 1708 651 1707 652 586 655 39618 10155 2286 658 97127 10219 2288 656 97523 9965 2290 644 96905 10101 2281 653 97091 10070 2285 648 97100 10070 2293 651 97024 10167 2284 650 97065 10218 2285 659 97001 10200 2289 655 96997 10141 2284 650 97121 9976 2292 641 97312 10070 2289 644 97238 10094 2289 644 97251 10195 2288 656 97168 10270 2227 655 96954 10206 2282 651 96927 10190 2289 655 96922 10159 2283 651 96890 10159 2285 649 96941 10230 2283 651 96897 10119 2281 652 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9643 4513 626 570 630 567 633 566 634 567 633 597 603 601 599 577 633 574 626 1680 617 1684 613 1687 631 1683 635 1681 627 1679 618 1681 627 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 2277 135 6297 131 648 31215 9397 13890 9345 4513 616 566 613 566 613 567 612 568 611 569 610 569 610 570 609 572 607 1682 615 1680 618 1676 611 1678 609 1682 615 1675 612 1679 608 1682 615 1676 611 568 611 1676 611 570 609 571 608 574 615 565 614 1675 612 566 613 1675 612 566 613 1678 609 1682 615 1676 611 1679 608 572 607 60519 9399 4514 604 572 607 569 610 566 613 564 605 572 607 569 610 567 612 564 605 1681 606 1684 613 1674 613 1675 448 1852 610 1678 609 1681 647 1646 621 1678 609 573 606 1682 615 565 501 682 610 569 610 569 610 1680 607 573 606 1683 614 565 583 1705 418 84 177 1618 613 1678 619 1681 616 566 613 39179 621 499 3912 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 7235 513 882 81 483 106 335 4464 624 568 642 550 619 570 609 573 616 567 622 568 622 568 621 571 629 1675 622 1679 629 1679 629 1685 623 1684 624 1676 621 1679 629 1678 619 576 624 1677 620 1682 615 573 616 570 619 569 621 567 612 1682 615 1681 668 520 618 565 624 1678 619 1676 621 1683 625 1679 618 572 607 39193 9276 2260 612 96574 9516 2265 617 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9300 4515 603 574 605 573 606 571 608 570 609 567 602 574 605 571 608 567 602 1687 610 1678 609 1680 607 1682 605 1685 612 1678 609 1680 607 1682 605 1684 613 1677 610 1680 607 571 608 569 610 567 612 565 614 1676 611 567 612 564 605 572 607 1681 606 1683 604 1684 603 1686 612 564 605 39200 9291 2258 604 96551 9262 2259 613 96529 9264 2259 613 96522 9284 2257 605 96540 9285 2264 608 96536 9286 2266 606 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 96 00 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1F E0 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1E E1 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1C E3 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1D E2 00 00 +# +name: Red +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 00 FF 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 01 FE 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 02 FD 00 00 +# +name: White +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 03 FC 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 09 F6 00 00 +# +name: Red +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 08 F7 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 09 F6 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0A F5 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 06 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 05 00 00 00 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9054 4465 597 566 571 537 600 535 602 532 595 567 570 538 599 535 602 533 594 1649 595 1649 595 1649 595 1649 678 1566 594 1650 594 1650 594 1650 594 1651 603 559 568 541 596 1646 598 564 573 536 601 534 593 568 569 539 598 1644 600 1643 601 561 576 1640 604 1640 604 1641 603 1640 604 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9046 4474 599 564 573 535 602 533 594 541 596 539 598 537 600 535 602 532 595 1649 595 1649 678 1567 604 1641 603 1642 602 1643 601 1644 600 1645 599 564 573 536 601 534 593 1651 603 559 568 541 596 539 598 537 600 1643 601 1645 599 1645 599 564 573 1644 600 1645 599 1647 597 1648 596 39826 9048 2232 596 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0A 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0B 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0E 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9594 4446 638 543 632 541 634 547 638 547 628 549 626 547 628 542 623 548 627 1642 626 1644 624 1638 620 1639 619 540 615 1645 613 1641 617 1640 608 1646 612 1644 614 538 606 544 611 540 615 535 609 541 614 540 615 539 616 538 616 1641 617 1641 617 1641 617 1644 614 1648 610 1649 619 40424 9654 2224 632 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9529 4476 618 574 601 565 600 534 621 536 619 537 618 540 615 542 613 542 613 1643 615 1642 616 1644 614 1643 615 541 614 1642 616 1639 619 1635 623 566 588 1642 626 562 593 566 588 572 593 569 586 572 593 566 589 1644 624 568 607 1639 619 1649 630 1641 627 1641 617 1644 624 1640 628 40776 9446 2224 611 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9417 4469 615 538 617 534 610 540 615 537 607 543 612 539 616 535 609 541 614 1639 609 1670 588 1664 584 1668 580 545 610 1670 588 1637 611 1640 608 543 612 540 615 538 606 546 609 541 614 538 617 536 608 545 610 1670 588 1665 583 1643 615 1641 617 1635 613 1638 610 1670 588 1637 611 40788 9415 2218 618 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9403 4480 614 543 612 545 609 545 609 544 610 544 611 543 612 541 614 539 616 1637 611 1643 615 1666 582 1672 586 537 607 1646 612 1641 617 1636 612 1641 607 543 612 538 606 544 611 539 616 535 609 541 614 536 608 542 613 1641 607 1645 613 1640 608 1645 613 1639 609 1644 614 1639 609 40849 9390 2218 607 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9408 4476 618 560 584 567 587 563 581 569 586 565 590 562 582 568 587 565 590 1637 611 1643 615 1639 609 1646 612 566 588 1638 610 1644 614 1641 617 535 609 570 585 1642 616 536 608 543 612 540 615 537 618 534 610 1644 614 1641 617 563 581 1645 613 1642 616 1639 609 1647 611 1644 614 40816 9393 2219 616 95827 9399 2216 609 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9397 4479 615 538 617 536 608 544 611 541 613 538 616 536 608 543 612 539 616 1638 610 1644 614 1640 608 1646 612 539 615 1638 609 1647 611 1672 586 1670 588 536 608 1645 613 539 616 537 607 544 610 541 614 539 616 536 608 1645 613 540 614 1639 609 1646 612 1642 616 1637 611 1644 614 40776 9391 2225 610 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9392 4479 615 535 609 540 615 536 608 542 612 537 607 543 612 539 616 535 609 1644 614 1639 609 1644 614 1639 609 541 614 1640 608 1645 613 1640 608 542 613 1640 608 1645 613 536 608 541 614 536 608 542 613 537 607 1646 612 536 608 541 614 1640 608 1645 613 1639 609 1644 614 1639 609 40856 9373 2226 609 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 10023 4480 634 546 629 547 628 543 622 547 628 545 630 538 627 538 616 543 622 1639 619 1642 626 1638 620 1640 618 568 586 1644 624 1639 629 1642 647 1644 645 1648 630 1646 632 569 596 573 602 567 598 571 594 567 587 540 614 540 614 540 625 1637 621 1644 624 1640 618 1646 622 1643 625 40664 9594 2221 624 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9229 4346 717 457 636 513 634 515 633 516 633 516 633 517 632 517 632 520 632 1630 632 1630 632 1630 631 1630 632 1630 631 1630 632 1630 631 1634 631 1630 632 517 632 517 632 1630 631 517 632 517 631 517 632 521 631 518 631 1630 631 1630 631 518 630 1631 630 1631 630 1631 630 1631 630 39733 9131 2189 631 96229 9149 2187 633 96282 9165 2184 634 95966 9160 2186 634 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 47 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 05 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 04 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 02 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 06 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 03 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5A 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9255 4431 642 552 614 1630 613 555 610 558 608 561 606 561 606 561 606 561 606 1639 606 561 606 1639 605 1639 605 1639 605 1639 605 1639 605 1639 605 561 605 561 605 562 604 562 604 561 605 562 604 562 604 562 604 1640 604 1640 604 1641 602 1641 602 1641 602 1641 602 1641 602 1665 578 39411 9181 2208 603 95896 9203 2207 604 +# +name: Power_off +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 02 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 18 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1A 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1D 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 4D 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1B 00 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 00 FF 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 02 FD 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 12 ED 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 13 EC 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9823 4498 642 544 635 551 638 548 641 545 634 551 638 548 641 545 634 551 638 1654 642 1651 635 1658 638 1656 640 1653 643 1651 635 1659 637 1656 640 1654 642 546 643 544 635 552 637 549 640 547 642 544 635 552 637 549 640 1652 644 1649 637 1656 640 1653 643 1650 635 1658 638 1655 641 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9772 4493 637 543 636 545 634 546 633 548 631 550 629 551 638 543 636 544 635 1652 633 1654 631 1656 629 1657 639 1649 636 1651 635 1653 633 1654 631 551 638 543 636 545 634 547 632 550 629 552 637 544 635 546 633 1654 631 1656 629 1658 638 1650 635 1652 633 1654 631 1656 629 1658 638 39896 9765 2236 629 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9795 4466 663 516 663 517 662 519 659 520 659 521 658 523 666 514 665 516 663 1624 661 1626 659 1627 658 1628 657 1630 666 1621 664 1623 662 1624 661 520 659 1628 668 1620 665 515 663 1624 692 488 660 521 658 523 666 1620 665 516 663 518 660 1625 660 521 658 1629 656 1630 666 1621 633 39898 9789 2210 655 +# +name: Power_on +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 01 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 09 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0A 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 05 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 04 00 00 00 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9139 4357 677 451 677 1567 702 426 702 1543 702 427 701 450 677 451 676 453 674 1570 674 454 674 455 673 455 673 1572 673 1571 673 1571 673 1571 673 1571 673 455 673 455 673 1572 673 455 673 455 673 455 673 455 673 455 673 1572 673 1572 672 456 672 1572 673 1572 672 1572 673 1572 673 40605 9112 2134 673 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9152 4345 707 420 708 1536 733 394 734 1510 734 395 706 421 707 422 705 423 704 1564 679 448 679 449 678 450 677 1567 677 1568 676 1569 675 1569 675 452 676 1569 675 453 675 1569 675 453 675 452 676 453 675 453 675 1569 675 453 675 1569 675 453 675 1570 674 1569 675 1570 674 1570 674 40596 9119 2125 676 95865 9137 2131 674 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9124 4375 679 449 679 1566 679 449 679 1567 678 451 677 475 676 451 678 450 678 1568 676 452 676 452 676 453 675 1570 675 1570 675 1570 675 1570 675 1570 675 1570 675 453 675 1570 674 453 675 453 675 453 675 453 675 453 675 453 675 1570 675 454 674 1570 675 1570 675 1571 674 1571 674 40606 9122 2126 676 95916 9118 2129 676 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9127 4377 679 449 680 1567 678 475 653 1592 654 475 653 475 653 476 677 451 677 1569 677 452 676 453 676 453 676 1570 676 1571 675 1571 675 1571 675 453 676 1571 675 453 675 1571 675 1571 675 453 675 453 675 453 676 1571 675 454 675 1571 675 454 675 454 675 1571 675 1571 675 1571 675 40549 9125 2129 676 95891 9120 2129 675 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9158 4347 734 393 735 1512 733 395 708 1538 707 422 706 424 705 447 680 448 680 1567 678 450 678 451 677 452 676 1570 676 1570 676 1570 676 1570 676 453 676 1570 676 1570 676 1570 676 453 676 453 676 453 676 453 676 1571 675 453 676 453 676 453 676 1570 676 1571 675 1571 675 1571 675 40827 9128 2126 676 96171 9122 2129 675 96129 9146 2126 676 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9219 4556 604 549 609 545 602 551 607 546 601 551 607 546 601 552 606 547 600 1675 610 1693 581 1667 607 1669 605 548 610 1666 608 1668 606 1669 606 547 611 543 604 548 610 543 604 548 610 544 603 549 609 544 603 1672 603 1673 602 1674 601 1676 609 1666 608 1668 606 1669 605 1671 603 41545 9245 2262 602 97493 9216 2260 604 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9246 4559 610 543 604 549 608 545 602 551 607 547 600 553 605 549 608 545 602 1674 610 1693 581 1669 605 1672 602 551 607 1670 604 1673 601 1676 608 545 602 1674 610 543 604 550 607 546 601 552 606 575 583 544 603 1673 601 552 606 1671 603 1674 610 1666 608 1669 605 1672 602 1674 610 41549 9245 2265 609 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9250 4555 604 550 607 546 601 552 606 548 609 544 603 550 607 545 602 551 606 1669 605 1671 603 1674 600 1676 608 545 602 1674 600 1676 608 1668 606 1670 604 1672 602 551 606 547 600 553 604 576 581 545 602 551 606 546 601 552 606 1670 604 1673 601 1676 608 1668 606 1670 604 1672 602 41552 9251 2257 606 97494 9238 2260 604 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9217 4542 605 543 604 545 602 547 600 549 598 551 606 542 605 544 603 546 601 1667 596 1670 604 1665 598 1669 605 544 603 1665 598 1669 605 1663 600 548 599 549 598 1670 603 545 602 547 600 548 599 549 608 541 606 1661 602 1666 607 541 606 1661 602 1666 597 1670 604 1664 599 1668 606 41407 9212 2254 598 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9207 4546 601 548 599 550 607 541 606 541 606 542 605 545 602 549 608 542 605 1665 608 1663 600 1670 603 1667 606 543 604 1667 607 1664 599 1671 602 1668 606 545 602 1668 605 545 602 548 609 541 606 545 602 548 609 541 606 1665 608 542 605 1666 607 1664 609 1662 601 1670 603 1668 605 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9213 4539 608 541 606 543 604 544 603 546 601 548 599 551 606 543 604 545 602 1667 606 1663 600 1669 604 1665 609 541 606 1663 600 1669 605 1665 609 568 579 1664 599 1671 603 574 573 550 607 543 604 546 601 549 608 1661 602 575 572 578 579 1664 599 1671 603 1668 605 1665 608 1662 601 41472 9215 2289 636 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9209 4543 604 545 602 547 600 549 608 541 606 542 605 544 655 494 601 548 599 1669 604 1664 599 1669 604 1664 599 550 607 1661 602 1667 606 1662 601 1668 605 1663 600 1669 604 545 654 495 600 549 608 541 606 543 604 545 602 547 600 550 607 1662 601 1669 604 1665 608 1662 601 1669 604 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9221 4546 610 540 607 543 604 547 610 541 606 544 603 548 609 541 606 545 602 1669 604 1668 605 1665 608 1663 610 541 606 1665 608 1663 610 1661 602 549 608 541 606 545 602 1669 604 546 601 549 608 543 604 546 601 1671 602 1669 604 1668 605 545 602 1670 603 1669 604 1668 605 1668 605 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9224 4550 606 544 603 549 608 543 604 547 600 578 579 572 575 576 581 543 604 1695 578 1667 606 1667 606 1667 606 545 602 1672 601 1672 601 1673 600 1673 600 551 606 573 574 1673 600 552 605 547 600 579 578 574 573 579 578 1668 605 1669 604 548 609 1665 608 1667 606 1668 605 1670 603 41529 9231 2262 610 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9212 4542 604 546 601 549 608 542 605 545 602 548 599 551 606 543 604 544 603 1666 607 1662 601 1667 606 1663 600 548 599 1668 605 1662 601 1668 605 545 602 1668 605 545 602 1669 604 546 601 549 608 543 655 494 601 1671 602 548 599 1672 601 549 608 1662 601 1670 603 1667 606 1662 601 41456 9222 2252 610 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9255 4560 607 548 661 494 601 554 603 552 605 550 607 548 609 546 611 544 665 1616 605 1675 608 1671 602 1678 605 549 608 1672 601 1679 604 1677 606 548 609 1671 602 1678 605 550 607 1673 610 545 602 553 604 552 605 1675 608 548 609 546 611 1671 602 553 604 1678 605 1676 607 1674 609 41616 9301 2210 611 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9253 4409 699 471 699 471 699 496 673 497 672 498 671 499 670 501 668 502 667 1590 667 1590 667 1591 666 1590 667 1590 667 1591 666 1590 667 1590 667 1590 667 1590 667 1590 667 504 666 504 666 504 666 1590 666 504 666 504 666 504 666 504 666 1591 665 1591 666 1591 666 503 666 1591 665 39687 9245 2161 667 +# +name: Power_on +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 01 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 00 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 14 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 06 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 08 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 0E 00 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 01 FE 00 00 +command: 12 ED 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 01 FE 00 00 +command: 01 FE 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 01 FE 00 00 +command: 03 FC 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 01 FE 00 00 +command: 06 F9 00 00 +# +name: Red +type: parsed +protocol: NECext +address: 01 FE 00 00 +command: 07 F8 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 01 FE 00 00 +command: 08 F7 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 01 FE 00 00 +command: 09 F6 00 00 +# +name: White +type: parsed +protocol: NECext +address: 01 FE 00 00 +command: 19 E6 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 44 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 58 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 59 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1D 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1E 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 01 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 01 00 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 84 E0 00 00 +command: 20 DF 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 84 E0 00 00 +command: 73 8C 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 84 E0 00 00 +command: 74 8B 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 08 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: A0 B7 00 00 +command: E9 16 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: A0 B7 00 00 +command: AB 54 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: A0 B7 00 00 +command: BC 43 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9036 4605 624 1656 621 524 619 1661 616 528 615 1665 622 523 620 1660 617 527 616 528 615 1665 622 523 620 1660 617 528 615 1665 622 522 621 1659 618 1663 624 521 622 1658 619 525 618 526 617 1663 624 520 623 1657 620 524 619 1662 615 529 625 1657 620 1660 617 527 617 1664 623 520 1206 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9032 4609 620 1660 617 527 616 1664 623 521 622 1658 619 525 618 1661 616 529 614 529 625 1655 622 522 621 1659 618 526 617 1663 624 520 623 1656 621 1660 617 527 616 1664 623 1657 620 524 619 1661 616 528 615 528 615 529 614 1665 622 522 621 523 620 1659 618 526 617 1663 624 1656 1203 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9033 4610 619 1662 615 529 624 1656 621 524 619 1661 616 528 615 1666 621 523 620 524 619 1662 615 529 624 1656 621 524 619 1661 616 529 625 1656 621 1659 618 1662 625 519 624 1656 621 523 620 524 619 1660 617 527 616 527 616 527 616 1665 622 522 621 1659 618 1662 615 529 624 1656 1203 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9841 4491 606 569 608 570 605 570 606 568 611 568 607 567 606 570 604 569 605 1678 606 1678 606 1677 607 1679 605 569 607 1678 607 1677 607 1677 608 1678 607 1678 607 568 608 570 606 568 609 568 609 568 610 569 609 568 637 545 612 1681 622 1688 628 1691 626 1691 629 1690 629 1689 630 41172 10270 2240 628 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9882 4490 613 570 614 569 614 569 613 570 612 568 614 570 614 570 613 568 614 1679 611 1677 613 1678 614 1679 612 569 613 1676 613 1676 613 1678 608 568 610 1678 608 570 609 571 608 567 610 568 611 569 612 568 613 1676 614 569 611 1678 606 1679 604 1678 605 1678 605 1679 604 1678 606 41190 9747 2228 603 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9819 4494 610 570 607 566 612 567 610 567 610 568 609 568 609 568 609 569 607 1677 609 1677 608 1677 608 1677 607 568 608 1677 608 1678 606 1676 609 568 608 569 607 569 606 568 608 567 609 569 607 568 607 570 603 1677 606 1678 606 1677 604 1677 605 1679 603 1677 605 1680 602 1678 604 41202 9733 2226 608 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9843 4498 609 568 612 568 612 568 612 570 609 569 610 569 610 569 609 569 609 1677 610 1678 609 1678 609 1677 610 567 611 1677 609 1677 609 1678 608 1678 608 568 610 567 610 569 608 568 609 570 607 568 609 569 608 568 609 1677 609 1677 609 1678 608 1678 608 1678 607 1679 607 1677 608 41183 9760 2227 607 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9764 4493 605 570 606 570 605 570 604 568 606 569 605 568 605 568 606 568 607 1678 603 1677 606 1677 606 1676 606 569 604 1679 604 1680 601 1678 603 569 604 570 602 1678 602 568 603 570 601 567 604 568 603 568 603 1679 601 1679 602 569 602 1679 601 1679 601 1680 600 1677 603 1678 602 41178 9691 2227 603 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9686 4529 601 572 600 570 601 571 601 569 603 570 602 572 599 571 600 571 601 1680 601 1680 602 1680 602 1680 601 570 601 1679 602 1679 602 1681 600 1682 598 569 602 1680 601 569 602 569 601 570 601 570 600 571 599 571 599 1680 600 569 601 1680 600 1679 601 1680 600 1680 600 1681 599 41234 9610 2257 572 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9727 4500 605 569 604 569 602 568 603 570 601 568 602 570 600 569 601 570 600 1680 600 1681 599 1680 600 1680 600 568 601 1679 601 1680 599 1679 600 569 599 1682 597 1679 600 569 600 569 599 570 598 569 599 569 599 1679 600 568 600 569 599 1678 601 1680 598 1680 598 1681 597 1680 598 41212 9652 2227 601 96458 9662 2231 599 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9694 4504 600 568 603 568 603 570 601 569 602 568 604 570 602 568 604 570 602 1679 601 1680 599 1680 600 1678 605 569 605 1680 600 1681 600 1682 598 1679 602 1679 601 1680 600 570 601 568 604 569 601 567 602 569 600 569 601 568 602 569 601 1678 602 1680 600 1681 600 1679 605 1681 601 41241 9662 2228 603 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4808 4505 592 542 625 539 628 1648 624 539 628 1648 624 540 627 1649 623 541 626 540 627 1650 622 542 625 542 625 1652 631 1646 626 1650 622 543 634 534 633 1645 627 567 600 540 627 541 626 543 635 534 633 536 631 537 630 539 628 541 626 1653 630 536 631 537 630 1649 623 1680 603 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4810 4469 628 1671 601 535 622 543 624 541 626 1675 597 540 627 1674 598 539 628 538 629 1672 600 538 629 537 630 1672 600 1673 599 1674 598 540 627 539 628 1675 597 540 627 541 626 541 626 541 626 541 626 541 626 1677 605 533 634 1669 603 535 632 536 631 536 631 1672 600 1646 626 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4802 4475 622 538 629 1672 600 534 623 1652 620 1653 619 543 624 1650 622 541 626 539 628 1647 625 538 629 536 631 1644 628 1645 627 1646 626 537 630 536 631 1644 628 535 632 534 633 533 634 533 634 532 625 542 625 542 625 1651 621 1653 630 1644 628 536 631 535 632 1645 627 1646 626 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4758 4470 616 538 619 1649 623 532 625 1644 618 1648 624 532 624 1644 618 539 628 1640 622 1645 616 540 627 532 625 1644 617 539 628 532 624 535 622 539 628 532 624 1645 616 542 625 535 622 538 629 531 626 535 622 1649 623 535 622 539 628 532 625 1646 626 1642 620 1649 623 534 622 +# +name: Power_on +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 00 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 08 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 42 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 4A 00 00 00 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9205 4358 644 516 645 516 645 516 674 488 672 490 670 491 669 515 645 516 644 1591 642 1592 641 1594 639 1596 638 1596 638 1597 637 1597 637 1597 637 523 638 523 638 523 637 1597 637 523 637 524 637 523 638 523 637 1597 637 1598 636 1598 636 524 637 1598 636 1623 611 1623 611 1623 611 39226 9175 2164 637 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9304 4355 649 516 648 516 677 488 677 488 675 489 674 491 672 515 647 517 647 1592 644 1594 643 1596 642 1597 641 1597 641 1597 642 1597 643 1598 643 526 639 524 644 1598 642 1598 642 1598 641 524 641 525 641 525 640 1601 640 1599 639 525 640 525 639 526 638 1600 638 1600 638 1600 637 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 52 00 00 00 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9264 4384 673 493 670 493 669 496 668 519 644 497 666 519 644 520 643 522 642 1596 641 1596 640 1596 641 1596 640 1596 641 1597 641 1597 640 1596 641 523 641 1596 640 523 639 1596 640 523 639 523 640 1596 640 523 640 1597 639 523 639 1597 639 523 640 1597 640 1597 639 523 640 1597 639 39232 9209 2161 640 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 01 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 00 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 14 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 02 FE 00 00 +command: 01 FE 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 02 FE 00 00 +command: 02 FD 00 00 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 2597 769 839 789 449 705 504 605 737 626 208 7936 553 921 142 1941 360 4248 292 1886 393 1885 392 6502 162 4156 393 851 266 1886 446 8685 384 1891 119 4415 333 3088 336 1843 388 +# +name: White +type: parsed +protocol: NECext +address: 02 FE 00 00 +command: 16 E9 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 02 FE 00 00 +command: 08 F7 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 02 FE 00 00 +command: 15 EA 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 02 FE 00 00 +command: 09 F6 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 02 FE 00 00 +command: 10 EF 00 00 +# +name: Red +type: parsed +protocol: NECext +address: 02 FE 00 00 +command: 0E F1 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9483 4405 679 504 651 479 675 481 646 534 619 511 643 515 612 542 612 516 640 1645 612 1645 613 1645 640 1645 613 1645 613 1646 641 1645 613 1646 612 1646 640 516 612 543 612 1646 612 542 613 516 640 517 611 543 613 517 641 1646 613 1646 612 543 612 1646 612 1646 639 1646 611 1646 612 39729 9472 2213 612 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9464 4377 678 503 647 478 673 480 644 510 640 512 640 514 611 541 613 515 640 1644 612 1645 611 1645 639 1645 612 1645 611 1645 639 1645 611 1645 610 1645 638 515 612 1645 639 1645 610 515 639 515 611 541 611 516 639 516 611 1645 639 515 613 542 612 1645 610 1645 640 1646 609 1645 611 39756 9455 2214 610 95791 9460 2213 610 95751 9476 2213 611 95752 9459 2213 610 95750 9460 2213 610 95752 9459 2214 610 95754 9459 2214 610 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9422 4406 676 503 647 478 672 480 642 509 639 487 663 514 609 541 610 515 637 1645 609 1645 609 1645 637 1645 609 1645 609 1645 637 1645 609 1645 609 1645 637 515 610 1645 637 515 610 1645 637 515 609 542 609 516 636 515 609 1646 636 515 609 1646 636 516 609 1646 636 1646 608 1646 609 39767 9422 2214 609 95831 9437 2242 584 96076 9454 2243 584 95795 9397 2216 607 95791 9395 2216 608 95765 9421 2215 608 95763 9421 2215 608 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9426 4414 644 507 646 480 673 481 644 535 615 511 641 514 611 541 611 515 639 1644 611 1645 610 1645 638 1645 610 1645 610 1645 638 1645 610 1645 610 1645 638 516 610 1646 637 1645 610 516 637 516 610 1646 637 516 609 543 609 1646 609 543 609 517 637 1646 609 1646 609 543 609 1646 609 39738 9424 2243 583 95777 9441 2219 609 95775 9442 2219 608 95778 9440 2219 609 95778 9434 2220 608 95776 9434 2219 608 95778 9435 2219 608 95776 9435 2243 584 95780 9436 2215 611 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9437 4377 677 502 648 478 673 479 644 508 642 510 640 513 612 541 612 514 640 1644 612 1644 611 1644 640 1644 612 1644 612 1644 640 1645 612 1644 612 541 611 515 638 515 610 541 611 515 638 515 610 541 611 515 638 1644 611 1644 611 1645 639 1644 611 1645 610 1644 638 1644 610 1645 610 39762 9422 2212 610 95744 9446 2212 610 95743 9447 2212 610 95747 9456 2212 610 95746 9472 2212 609 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 04 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 08 00 00 00 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9144 4440 608 530 503 664 421 689 580 559 608 529 582 587 547 535 635 529 609 1615 609 1671 607 1722 395 1802 554 1668 610 1642 611 1694 527 1670 582 556 608 1642 609 1641 611 1640 633 506 581 586 580 532 607 555 582 1616 633 587 552 531 607 556 554 1697 555 1670 609 1669 551 1699 582 39942 9039 2322 500 96415 9122 2187 636 96369 9143 2189 635 96411 9093 2188 637 96406 9009 2269 608 96435 9118 2214 449 96570 9065 2186 635 96402 9066 2214 637 96436 9064 2188 608 96410 9123 2182 641 96365 9154 2156 669 96360 9153 2180 644 96370 9153 2184 641 96367 9150 2184 641 96374 9151 2184 640 96357 9149 2184 640 96358 9151 2184 641 96362 9150 2183 641 96359 9150 2184 641 96371 9151 2183 641 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 01 00 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: 00 CF 00 00 +command: 40 BF 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 00 CF 00 00 +command: 59 A6 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 CF 00 00 +command: 45 BA 00 00 +# +name: Red +type: parsed +protocol: NECext +address: 00 CF 00 00 +command: 58 A7 00 00 +# +name: White +type: parsed +protocol: NECext +address: 00 CF 00 00 +command: 44 BB 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 CF 00 00 +command: 5C A3 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 00 CF 00 00 +command: 5D A2 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 05 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 04 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 06 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 08 00 00 00 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8984 4436 676 448 676 450 674 452 672 450 674 448 676 448 676 475 649 449 676 1572 675 1599 648 1575 673 1570 677 1571 676 1573 675 1572 675 1570 677 1571 676 447 677 447 677 1569 678 449 675 448 676 448 676 448 676 447 677 1572 675 1571 676 447 677 1570 677 1598 648 1572 675 1572 675 39202 8955 2242 674 95722 8955 2240 602 95786 8979 2213 603 95785 8979 2213 603 95779 8980 2213 603 95774 8982 2214 602 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0B 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9351 4464 620 559 589 560 588 562 586 562 586 562 586 559 579 538 610 532 606 1660 605 1660 605 1660 605 1659 606 1659 606 1659 606 1659 606 1659 606 1659 606 533 604 1660 605 536 601 538 610 530 608 1658 607 533 604 536 601 1665 610 530 607 1659 606 1660 605 1662 603 540 608 1660 605 39530 9255 2232 607 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9289 4463 611 538 610 536 612 532 605 540 608 539 609 537 611 534 614 533 615 1656 609 1662 613 1659 606 1665 611 1663 613 1661 614 1658 617 1656 609 1663 613 1660 615 1657 608 540 608 539 609 538 610 1662 613 535 613 535 613 533 615 531 617 1654 611 1661 615 1659 616 532 616 1656 609 39564 9275 2238 612 +# +name: Power_on +type: parsed +protocol: NECext +address: 7E 7E 00 00 +command: 05 FA 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 7E 7E 00 00 +command: 04 FB 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 7E 7E 00 00 +command: 1A E5 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 7E 7E 00 00 +command: 1B E4 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 7E 7E 00 00 +command: 1C E3 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 7E 7E 00 00 +command: 1D E2 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9424 4467 658 525 626 527 624 529 623 532 622 532 622 532 621 532 621 532 622 1647 621 1647 621 1647 621 1647 620 1647 620 1646 621 1647 620 1646 621 1646 621 532 621 1647 620 532 621 532 621 532 620 1647 620 532 620 532 620 1647 619 532 620 1647 619 1647 619 1647 619 533 619 1647 619 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9442 4435 684 497 654 500 651 502 649 530 623 531 622 531 622 531 622 531 622 1645 622 1645 622 1645 622 1646 621 1645 622 1645 622 1646 621 1646 621 1646 621 1645 621 1645 621 531 621 531 621 531 621 1646 620 531 621 531 621 531 621 532 620 1646 620 1646 620 1646 620 532 620 1646 620 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9426 4435 684 497 655 500 651 502 650 530 623 531 622 531 622 531 622 531 622 1646 621 1645 622 1645 622 1646 622 1646 622 1645 622 1646 622 1645 622 1645 621 531 621 1645 621 1645 621 531 621 531 621 531 621 531 621 531 620 1646 620 531 621 531 621 1646 620 1646 620 1646 620 1646 620 40069 9348 2217 620 96668 9368 2219 619 96685 9344 2222 618 96691 9338 2221 619 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9439 4437 684 497 655 500 652 502 650 530 624 531 623 531 623 531 623 531 623 1645 623 1645 623 1646 623 1646 623 1646 624 1646 621 1638 624 1646 623 532 622 532 621 1647 620 1648 620 533 621 533 620 533 622 533 622 1648 621 1648 621 534 621 534 620 1649 619 1649 619 1648 621 1648 622 39503 9376 2219 621 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9510 4462 663 499 657 526 629 529 628 531 627 531 627 532 626 531 627 532 626 1646 626 1646 626 1647 626 1646 626 1647 625 1646 626 1647 625 1647 625 532 625 1647 625 1646 626 1647 625 1646 625 532 625 1647 624 532 625 1647 624 532 625 532 625 532 625 532 625 1647 624 532 625 1647 624 113910 9453 4502 623 532 624 532 624 532 624 532 624 532 624 532 624 532 624 532 624 1647 624 1647 624 1647 623 1647 623 1648 623 1647 624 1647 624 1647 623 533 623 1648 622 1648 622 1647 623 1648 623 533 622 1648 622 533 623 1647 623 533 622 533 622 533 622 533 622 1648 622 533 622 1648 622 40080 9404 2219 622 96717 9393 2248 594 96679 9387 2225 619 +# +name: Power_on +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 02 FD 00 00 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9199 4420 675 462 677 463 675 464 675 461 677 463 674 464 676 463 674 464 676 1573 677 1573 675 1575 649 1599 703 436 676 1547 701 1576 675 1572 675 1574 675 1573 703 435 676 463 677 463 674 465 674 464 675 462 676 462 676 464 675 1573 677 1571 677 1573 674 1573 676 1575 675 1572 649 41029 9226 2116 677 96318 9199 2142 675 96321 9208 2143 677 96315 9204 2141 675 96321 9203 2143 676 96317 9210 2140 677 96314 9205 2141 676 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9196 4413 680 483 680 457 680 457 680 458 678 460 677 461 676 462 676 462 676 1573 676 1573 676 1573 676 1573 676 463 675 1574 675 1574 675 1575 674 464 674 488 650 488 650 488 650 488 650 489 649 489 649 488 650 1599 650 1600 649 1600 649 1600 648 1601 648 1601 648 1601 648 1601 648 41005 9197 2172 649 96318 9198 2172 649 96322 9196 2146 674 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9203 4419 676 461 678 462 703 436 674 464 700 437 678 461 677 462 676 461 677 1574 676 1573 677 1574 675 1573 676 462 675 1576 675 1576 673 1577 674 1577 672 463 673 467 701 463 647 467 676 487 649 464 674 461 678 465 673 1576 672 1578 673 1575 676 1574 676 1573 676 1575 675 1573 676 41040 9166 2200 648 96372 9202 2172 649 96351 9211 2143 678 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9168 4442 654 483 654 483 680 458 679 459 678 461 676 487 650 488 650 488 649 1599 650 1599 649 1599 650 1599 650 488 649 1599 650 1599 649 1599 650 488 650 489 649 1599 650 488 649 489 649 488 649 489 649 489 649 1599 649 1599 649 489 649 1600 648 1599 649 1599 649 1600 649 1599 649 41008 9188 2172 649 96336 9189 2172 649 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9327 4434 654 482 655 483 653 483 680 458 679 459 678 463 674 488 649 488 650 1599 648 1599 648 1599 648 1599 648 489 648 1599 648 1599 648 1599 650 1599 648 489 648 1600 648 489 648 489 648 490 648 489 648 489 648 489 648 1600 647 490 647 1600 647 1600 647 1600 647 1600 648 1600 648 40983 9183 2171 648 96240 9276 2144 679 96193 9354 2145 678 96222 9298 2144 679 96215 9214 2142 675 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9223 4384 709 455 682 455 682 456 681 457 679 458 679 460 677 461 677 461 677 1572 677 1572 676 1573 676 1573 676 462 676 1572 676 1573 676 1572 676 462 676 1572 676 1572 677 462 676 461 677 462 676 462 676 462 677 1573 677 462 677 462 677 1573 677 1573 676 1573 676 1573 676 1573 676 40987 9201 2144 675 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9350 4358 716 455 687 456 686 457 685 458 683 460 681 462 680 464 679 464 679 1575 679 1575 679 1575 679 1575 679 464 679 1575 679 1575 679 1575 679 1575 679 1575 679 1575 680 464 679 464 679 464 679 464 679 464 679 464 680 464 679 464 679 1576 679 1575 680 1576 679 1576 678 1576 679 41014 9273 2146 679 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1F 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8921 4420 588 528 590 525 583 533 585 1647 590 526 592 523 585 531 587 529 589 1643 584 1648 589 1644 583 532 586 1647 590 1642 585 1648 589 1643 584 10687 8917 4422 586 529 589 526 592 524 584 1648 589 526 592 523 585 531 587 528 590 1642 585 1647 590 1642 585 531 587 1645 592 1640 587 1645 592 1640 587 10683 8921 4418 590 526 582 533 585 530 588 1644 583 533 585 530 588 527 591 524 584 1648 589 1643 584 1648 589 526 592 1640 587 1645 592 1640 587 1645 592 10677 8916 4422 586 530 588 527 591 524 584 1648 589 526 592 523 585 531 587 528 590 1642 585 1647 590 1642 585 530 588 1644 583 1649 588 1644 583 1649 588 10681 8912 4426 593 524 584 531 587 528 590 1642 585 531 587 528 590 525 583 532 586 1646 591 1641 586 1646 591 524 584 1648 589 1643 584 1648 589 1644 583 10686 8917 4421 587 529 589 526 592 523 585 1647 590 526 592 523 585 531 587 528 590 1641 586 1647 590 1642 585 531 587 1645 592 1640 587 1645 592 1641 586 10684 8920 4419 589 527 591 524 584 532 638 1594 592 524 584 532 586 529 589 526 592 1640 587 1646 591 1641 586 530 588 1644 583 1649 588 1645 592 1640 587 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8921 4420 588 1644 593 1639 588 1645 592 1640 587 530 588 527 591 524 584 532 586 529 589 526 592 524 584 531 587 1643 594 1639 588 1644 593 1639 588 10681 8923 4418 590 1642 585 1647 590 1642 585 1647 590 527 591 524 584 532 586 529 589 526 592 523 585 531 587 554 564 1640 587 1645 592 1640 587 1645 592 10676 8917 4422 586 1645 592 1640 587 1645 592 1640 587 530 588 554 564 551 557 532 586 529 589 526 592 550 558 557 561 1643 594 1638 589 1643 594 1638 589 10679 8914 4424 594 1638 589 1643 594 1638 589 1643 594 523 585 557 561 554 564 551 557 559 559 556 562 553 565 524 584 1647 590 1642 585 1647 590 1642 585 10684 8920 4419 589 1643 594 1639 588 1644 593 1639 588 529 589 553 565 524 584 557 561 528 590 552 566 549 559 556 562 1642 585 1647 590 1642 585 1648 589 10679 8915 4424 595 1638 589 1643 594 1638 589 1643 594 523 585 531 587 528 590 525 593 523 585 556 562 528 590 525 583 1647 590 1642 585 1648 589 1643 594 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9333 4399 630 568 600 568 600 568 599 568 601 568 600 568 599 568 600 568 599 1648 599 1648 599 1648 599 1648 599 1648 599 1647 600 1647 601 1647 601 567 600 567 600 566 601 566 600 566 601 566 600 566 600 567 599 1647 599 1647 599 1648 598 1648 598 1648 598 1648 598 1649 623 1623 623 39481 9180 2217 600 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9282 4455 661 509 660 509 660 509 630 539 658 511 660 510 660 511 659 512 658 1593 658 1619 632 1619 631 1620 630 1621 630 1622 628 1623 628 1623 627 544 626 1624 627 544 626 544 626 544 626 544 627 544 626 544 626 1625 626 544 626 1625 626 1625 626 1625 626 1625 626 1625 626 1625 626 39490 9262 2193 627 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9263 4433 655 538 630 539 629 540 628 541 628 542 627 543 625 543 625 544 625 1625 625 1625 625 1625 626 1625 626 1626 625 1626 624 1625 625 1625 624 545 624 1626 623 1625 624 1626 624 544 624 545 623 545 624 544 625 1626 625 545 625 545 625 545 626 1626 626 1626 625 1626 625 1626 625 39509 9254 2196 625 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 153 6793 9234 4462 625 543 626 543 626 543 625 544 625 543 625 544 624 544 625 544 625 1624 625 1625 625 1625 626 1625 626 1625 626 1624 627 1623 628 1597 653 514 654 514 652 515 651 539 627 1621 626 541 624 542 624 543 623 1624 622 1625 621 1625 621 1625 621 544 621 1625 621 1625 621 1625 621 39509 9167 2193 622 96159 9198 2192 624 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9267 4435 654 515 654 517 654 541 630 541 629 542 628 543 626 544 627 545 628 1626 624 1626 624 1626 625 1627 625 1626 626 1626 626 1626 624 1627 624 546 627 1627 627 546 629 546 624 1628 629 546 631 546 629 546 629 1626 626 545 625 1627 624 1627 626 546 626 1627 626 1627 625 1627 625 39559 9259 2199 627 +# +name: Power_off +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1E 00 00 00 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9202 4471 619 520 619 520 619 521 618 521 619 521 618 521 618 521 618 1630 618 1630 618 1630 618 1630 618 1631 617 1631 618 1631 617 1631 618 522 617 522 617 1631 617 522 618 1631 617 522 618 522 617 522 618 522 618 1631 618 522 618 1631 617 522 618 1631 618 1631 617 1631 617 1631 617 39887 9176 2201 619 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9204 4467 622 517 622 518 621 518 621 519 620 520 619 521 619 521 619 1630 618 1631 618 1630 618 1631 617 1631 617 1630 618 1630 618 1630 618 522 617 1630 618 1631 617 1631 617 1631 617 1630 618 522 617 522 617 522 617 522 617 522 617 522 617 522 617 522 617 1631 617 1631 617 1631 617 39886 9173 2200 619 96195 9199 2200 619 95933 9207 2201 619 96190 9197 2201 619 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 +# +name: White +type: parsed +protocol: NECext +address: A0 B7 00 00 +command: EF 10 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1263 422 1262 422 437 1264 1264 422 437 1266 438 1265 439 1267 437 1265 439 1266 438 1265 1265 421 438 7907 1264 448 1213 444 438 1267 1240 444 465 1237 439 1267 437 1266 438 1266 438 1267 437 1266 1241 445 465 7879 1240 445 1239 444 465 1238 1240 446 463 1238 466 1238 466 1237 467 1238 466 1237 467 1239 1240 444 464 7880 1241 443 1241 444 464 1240 1240 443 465 1240 464 1239 465 1240 464 1240 464 1239 465 1239 1240 444 464 7881 1239 444 1240 444 464 1239 1240 446 463 1239 466 1238 466 1238 466 1237 467 1239 465 1238 1240 445 465 7879 1240 444 1240 446 464 1237 1241 445 465 1237 466 1238 438 1265 439 1266 438 1266 438 1265 1241 444 466 7877 1263 421 1241 443 466 1237 1241 443 438 1266 438 1265 439 1266 438 1266 438 1269 435 1266 1263 422 437 7905 1263 420 1263 420 439 1265 1263 422 437 1265 438 1265 439 1265 439 1264 440 1265 439 1265 1264 422 437 7905 1263 422 1262 421 438 1266 1263 421 438 1265 438 1265 439 1265 439 1266 438 1266 438 1266 1263 421 438 149854 1264 420 1264 420 439 1265 1264 421 438 1266 438 1264 440 1266 438 1265 439 1265 439 1266 1263 422 437 7906 1264 420 1241 446 435 1265 1241 444 437 1265 439 1265 439 1267 437 1265 439 1266 438 1266 1241 445 465 7879 1241 445 1239 444 466 1236 1242 444 466 1238 437 1267 437 1265 439 1266 439 1266 438 1267 1240 443 467 7880 1242 444 1241 444 466 1238 1240 445 466 1237 439 1266 439 1267 437 1268 437 1266 439 1294 1213 444 467 7879 1242 445 1239 446 464 1238 1241 446 464 1239 466 1237 467 1239 466 1239 465 1239 465 1237 1242 444 465 7882 1240 444 1240 444 465 1240 1239 445 464 1239 465 1238 466 1240 465 1239 465 1239 465 1239 1241 444 464 7880 1241 446 1238 444 464 1239 1241 445 464 1240 464 1240 464 1239 465 1239 465 1238 466 1239 1239 445 464 7879 1239 444 1240 445 464 1236 1242 444 465 1239 465 1236 467 1238 464 1238 466 1238 465 1239 1240 445 465 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1340 341 1341 342 491 1210 1342 342 492 1211 492 1210 493 1209 494 1210 1340 342 492 1212 491 1211 492 7813 1291 393 1339 343 490 1212 1290 393 490 1212 491 1211 492 1213 490 1210 1292 392 491 1210 493 1213 490 7815 1291 393 1339 342 492 1211 1291 391 492 1211 492 1212 491 1211 492 1212 1291 393 489 1213 490 1213 490 7818 1292 393 1290 394 487 1215 1290 393 488 1214 490 1215 488 1213 490 1213 1292 393 488 1214 490 1216 487 7819 1291 394 1289 393 440 1262 1291 393 463 1240 464 1238 465 1240 486 1215 1291 393 440 1262 464 1238 465 7844 1290 393 1290 393 440 1262 1291 393 440 1262 464 1240 463 1239 440 1264 1289 393 440 1264 439 1263 440 7870 1289 394 1289 394 439 1262 1291 394 439 1264 462 1241 438 1262 441 1263 1290 395 438 1262 441 1266 437 7867 1291 393 1290 393 440 1263 1289 394 439 1265 438 1264 439 1263 440 1264 1289 394 439 1264 439 1262 441 7868 1290 393 1290 395 438 1264 1289 394 439 1263 440 1264 462 1240 440 1265 1288 394 439 1263 440 1262 441 7868 1290 394 1289 394 439 1263 1290 394 439 1265 438 1262 441 1264 439 1263 1290 393 440 1262 464 1239 464 7844 1291 394 1289 393 440 1263 1290 394 439 1263 463 1240 464 1239 464 1238 1292 393 440 1265 438 1263 464 7845 1291 393 1290 393 440 1264 1289 393 440 1262 464 1239 465 1240 464 1239 1291 394 439 1263 440 1264 439 7869 1291 393 1290 395 438 1263 1290 394 439 1264 439 1264 439 1264 439 1263 1290 394 439 1263 440 1265 438 7871 1288 394 1289 393 440 1263 1290 394 439 1262 441 1263 440 1263 440 1265 1288 394 439 1262 440 1264 439 7869 1289 394 1289 393 440 1262 1291 393 440 1263 440 1263 440 1262 465 1240 1290 393 440 1263 440 1265 461 7844 1291 393 1290 394 439 1263 1290 395 438 1263 464 1239 464 1240 463 1240 1289 394 462 1240 463 1240 463 7844 1290 393 1290 394 439 1264 1289 393 440 1262 465 1239 464 1239 464 1238 1291 393 463 1241 462 1239 464 7844 1290 393 1290 393 463 1240 1289 394 463 1240 463 1238 465 1239 464 1237 1292 393 464 1240 463 1238 465 7844 1289 392 1291 394 462 1239 1290 393 464 1237 466 1238 465 1239 464 1239 1289 392 465 1239 465 1238 465 7844 1290 393 1290 393 464 1240 1289 393 463 1239 465 1240 463 1239 464 1239 1290 393 463 1239 464 1239 464 7844 1289 393 1290 393 440 1263 1290 394 439 1262 440 1264 439 1263 440 1263 1290 393 440 1263 439 1263 440 7867 1290 394 1289 394 439 1263 1290 395 438 1262 441 1264 439 1263 440 1265 1287 396 437 1263 439 1263 440 7868 1289 393 1290 395 438 1264 1288 395 438 1264 438 1263 440 1264 439 1264 1288 395 439 1264 439 1264 439 7869 1289 395 1263 420 438 1264 1264 420 438 1264 439 1264 439 1264 439 1266 1262 420 439 1263 440 1264 439 7869 1264 419 1264 420 438 1264 1263 420 438 1264 439 1264 439 1264 439 1264 1263 420 438 1264 439 1264 439 7868 1264 421 1262 422 436 1264 1263 421 438 1266 437 1265 438 1264 439 1265 1262 421 438 1264 439 1264 439 7868 1264 420 1263 420 438 1264 1263 421 437 1264 439 1263 440 1264 439 1264 1264 420 438 1264 439 1263 440 7869 1263 420 1262 420 438 1264 1263 419 439 1263 440 1264 439 1263 440 1264 1263 420 438 1264 439 1264 439 7868 1288 396 1262 420 438 1263 1289 395 439 1264 439 1264 439 1264 439 1264 1288 395 439 1263 440 1264 439 7868 1289 396 1287 394 439 1264 1288 396 437 1263 440 1263 440 1264 439 1264 1288 395 438 1265 438 1263 440 +# +name: Power_off +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 01 FE 00 00 +# +name: White +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 06 F9 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 03 FC 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0C F3 00 00 +# +name: Green +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0D F2 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0E F1 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 10 EF 00 00 +# +name: White +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0B F4 00 00 +# +name: Blue +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0F F0 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9408 4556 587 545 618 576 587 576 587 576 586 546 617 576 587 576 588 577 587 1691 586 1691 588 1662 619 1691 589 1691 589 1691 589 1692 589 1691 590 1691 590 577 588 1691 591 577 591 576 592 577 592 1691 591 577 592 546 623 1663 617 549 617 1692 592 1691 592 1691 592 576 592 1691 592 40116 9493 2274 589 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9386 4558 583 577 582 578 582 550 609 577 606 554 605 554 604 553 606 553 605 1668 605 1669 604 1669 604 1669 604 1668 605 1639 634 1639 634 1667 606 1668 605 1667 606 1667 606 523 635 551 607 552 606 1667 606 552 606 552 606 551 607 551 607 1667 606 1667 606 1667 606 552 606 1638 635 40149 9392 2249 607 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9426 4531 610 549 611 549 611 520 639 548 611 522 638 549 611 549 610 549 610 1664 611 1664 611 1664 612 1664 611 1638 638 1665 612 1665 613 1665 611 549 611 521 639 1665 610 549 610 549 610 549 610 1639 635 549 610 1665 609 1637 638 549 610 1665 609 1665 610 1665 609 549 610 1665 609 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9410 4536 607 552 608 551 608 552 607 552 608 522 637 552 607 552 607 552 607 1668 607 1668 607 1639 636 1668 607 1668 607 1668 607 1668 606 1641 634 552 607 525 633 524 635 552 606 522 636 552 607 1640 634 552 606 1642 633 1668 606 1641 633 1641 633 1639 635 1668 606 552 607 1668 606 40168 9388 2251 606 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9384 4536 605 522 636 552 606 553 605 552 606 552 606 552 606 553 605 552 606 1668 606 1669 605 1668 605 1668 606 1668 606 1668 606 1668 605 1669 604 1668 606 1668 606 553 605 552 606 552 606 552 606 1668 606 552 606 524 634 552 606 1639 635 1668 606 1668 606 1668 606 552 606 1668 606 40131 9422 2222 635 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9304 4649 557 599 558 600 557 600 558 557 600 600 558 557 601 600 557 601 557 1717 557 1716 557 1715 557 1717 556 1717 486 1787 486 1787 486 1787 486 1787 486 671 487 670 488 1787 486 670 488 670 488 631 527 644 514 585 573 1700 573 1786 487 669 489 1761 513 1704 570 1700 574 1758 515 40252 9354 2219 633 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9431 4532 611 549 611 549 612 521 640 521 640 549 612 549 612 549 612 549 612 1638 640 1664 617 1639 640 1665 614 1665 614 1664 613 1637 640 1664 614 522 640 1637 640 1664 613 548 613 1665 612 523 639 548 613 549 612 1664 612 548 613 548 613 1638 638 549 612 1664 613 1665 612 1665 611 40154 9410 2250 608 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9466 4532 612 551 610 550 611 550 611 550 611 550 611 524 636 551 610 550 610 1639 637 1666 610 1667 609 1639 637 1668 607 1668 607 1669 606 1668 607 553 606 553 607 1670 582 1693 582 577 583 577 582 577 583 577 583 1693 582 1693 583 549 611 577 583 1692 583 1663 612 1693 583 1693 584 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 12 ED 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4563 4450 602 521 604 519 606 1630 599 1635 594 1641 598 524 601 1634 595 533 602 1633 596 1638 601 521 604 522 592 528 597 525 600 523 602 526 599 523 602 1631 598 1637 602 520 605 521 593 527 598 524 601 527 598 1635 594 529 596 1665 574 522 603 523 601 1629 600 1635 594 529 596 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4560 4451 601 1635 594 1641 598 523 601 522 603 1631 598 525 600 1635 594 533 602 1633 596 1639 600 522 603 521 593 528 597 525 600 522 603 525 600 522 603 1630 599 1636 603 519 595 529 596 526 599 523 602 525 600 523 602 520 594 1638 601 1634 595 1640 599 523 602 1632 597 527 598 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4563 4450 602 521 604 1631 598 525 599 1635 594 1641 598 524 600 1634 595 532 603 1632 597 1637 602 521 593 529 596 1639 600 522 603 519 595 533 602 520 594 1639 600 1634 595 527 598 525 600 524 600 521 593 534 601 1631 598 1636 603 519 595 528 597 1637 602 1632 597 1637 602 521 603 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 4564 4477 575 547 567 1668 571 524 601 1635 594 1641 598 524 601 1634 595 533 602 1633 596 1639 600 522 603 519 595 527 598 525 600 523 602 526 599 523 602 1632 597 1665 574 522 603 519 595 527 598 525 600 528 597 1637 602 1633 596 527 598 524 601 522 603 1632 597 1638 601 522 603 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9036 4462 554 548 553 499 578 548 555 1629 578 547 553 523 556 548 554 522 556 1653 495 1713 569 1634 553 548 554 1651 555 1652 554 1651 496 1710 554 10590 9005 4460 553 546 555 521 554 520 582 1651 554 547 552 523 555 522 579 522 555 1652 495 1712 567 1636 553 547 555 1651 554 1626 580 1651 496 1686 578 10589 8996 4460 553 548 553 522 553 547 554 1652 553 523 576 524 554 547 554 522 555 1652 495 1712 566 1614 575 525 577 1627 578 1651 554 1651 549 1657 553 10589 8991 4460 553 548 552 522 553 548 553 1652 553 548 551 524 553 548 553 523 554 1653 495 1713 567 1636 551 549 553 1652 553 1652 553 1652 553 1653 552 10589 8986 4463 550 549 552 524 551 550 552 1653 552 549 550 526 551 550 551 524 552 1655 494 1712 517 1687 549 550 552 1654 551 1654 551 1628 576 1655 550 11774 8981 4464 550 526 575 551 493 581 551 1654 550 551 548 502 575 551 550 526 520 1686 548 1657 517 1711 549 526 550 1655 549 1655 519 1687 548 1656 548 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9043 4464 551 1653 552 1654 550 1655 549 1655 552 550 552 550 550 526 553 550 553 524 552 550 553 524 552 550 553 1654 552 1653 552 1653 552 1654 551 10553 9022 4463 550 1653 551 1630 574 1654 548 1656 551 522 580 549 551 525 553 523 579 524 551 550 552 524 551 550 552 1654 551 1653 551 1653 551 1654 551 10563 9015 4437 576 1652 552 1653 550 1654 494 1710 550 549 552 549 550 526 552 549 553 524 551 550 552 523 552 550 552 1653 552 1628 576 1653 551 1630 574 10589 8986 4462 551 1653 551 1653 550 1655 493 1710 550 549 552 549 550 526 552 549 553 524 551 550 552 524 551 550 552 1653 552 1653 551 1653 551 1654 550 10588 8983 4462 551 1653 551 1653 550 1655 493 1710 550 549 552 549 550 525 553 549 552 524 551 550 552 524 551 550 552 1653 551 1653 551 1653 551 1653 551 10587 8981 4462 550 1653 551 1654 549 1654 494 1710 550 549 552 550 549 526 552 549 552 501 573 550 552 524 551 550 552 1654 550 1654 550 1653 551 1654 550 10567 9001 4463 549 1653 551 1654 549 1655 547 1656 549 550 551 550 548 527 550 526 576 524 550 551 551 525 549 551 551 1654 550 1654 550 1654 550 1655 549 11772 8975 4463 550 1655 493 1709 549 1655 548 1656 570 529 550 551 547 527 550 550 551 525 549 551 550 551 549 526 550 1654 550 1654 519 1686 549 1655 549 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8959 4447 556 543 556 492 581 1647 494 604 557 1645 557 1619 583 1646 556 1646 556 1645 567 1635 556 543 556 1619 583 493 581 543 557 543 557 491 583 10569 8962 4426 578 542 557 492 582 1647 494 604 557 1645 557 1645 557 1618 584 1645 557 1619 522 1707 556 543 557 1646 556 517 557 543 557 543 556 518 556 10571 8961 4449 556 543 556 517 557 1648 494 604 556 1646 556 1646 556 1646 556 1646 556 1646 495 1707 555 543 556 1647 555 517 556 544 556 543 556 518 556 10572 8960 4452 554 544 555 519 555 1650 493 604 555 1648 554 1647 555 1648 554 1647 555 1622 521 1707 555 544 556 1648 554 519 554 519 581 544 555 519 555 10584 8958 4452 555 544 555 495 579 1650 493 604 555 1647 555 1647 555 1648 555 1647 555 1647 495 1707 555 520 579 1624 578 519 555 545 555 544 555 519 555 11766 8957 4453 555 518 581 545 551 1627 493 604 555 1647 555 1648 555 1647 555 1647 575 1628 494 1707 555 520 579 1647 555 544 555 519 555 545 554 519 555 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8960 4423 580 542 557 1621 581 517 574 1654 553 1623 568 1660 557 1646 556 1645 556 1647 556 494 579 1646 556 543 556 517 557 543 557 543 556 517 557 10574 8959 4426 579 520 579 1649 553 522 567 1659 547 1630 494 1734 552 1650 552 1649 552 1651 552 521 552 1624 579 547 552 522 552 547 554 546 553 521 553 10580 8955 4427 581 546 553 1649 553 502 587 1660 547 1608 516 1733 553 1650 552 1650 551 1651 552 499 574 1623 579 521 578 521 553 547 553 546 553 520 554 10579 8956 4454 553 546 553 1649 553 523 565 1661 547 1631 493 1733 553 1624 578 1649 552 1651 553 521 552 1650 553 546 553 522 552 523 578 547 552 499 575 10580 8956 4455 552 547 552 1651 551 525 566 1660 545 1631 494 1734 551 1651 551 1651 550 1652 551 522 551 1651 551 548 551 523 551 548 552 548 551 522 552 11769 8955 4456 551 548 552 1651 551 525 516 1710 546 1657 551 1651 551 1652 550 1651 551 1651 551 523 550 1652 576 523 551 523 576 523 551 549 551 522 552 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8961 4448 555 1647 555 1647 554 546 555 1647 555 1647 554 1648 554 1648 553 1649 550 549 554 519 549 1654 551 548 554 518 525 576 552 548 554 519 552 10578 8963 4449 555 1647 555 1647 555 544 557 1646 556 1646 556 1646 556 1645 557 1646 556 543 557 517 557 1645 558 543 557 494 580 542 558 543 557 490 584 10583 8961 4452 553 1650 552 1651 551 548 552 1650 552 1651 552 1650 552 1650 552 1651 551 548 552 522 551 1651 552 548 551 523 551 549 551 548 552 523 551 11769 8953 4458 551 1653 493 1708 550 549 551 1652 550 1651 551 1652 550 1630 570 1654 550 521 579 523 550 1652 576 523 551 549 551 523 551 549 551 523 551 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8944 4460 549 1627 575 523 576 525 549 525 574 1653 549 1629 573 1652 550 1625 577 526 574 1652 549 1653 549 1652 550 525 548 551 549 551 549 524 549 10577 8947 4436 573 1624 578 551 548 528 547 525 573 1629 573 1655 547 1628 573 1654 548 553 547 1654 547 1655 547 1656 523 550 523 576 524 576 524 549 524 10603 8923 4459 549 1678 524 575 525 550 523 550 550 1677 525 1678 524 1677 525 1677 525 575 525 1676 525 1677 525 1677 525 549 524 575 525 574 526 548 525 10602 8925 4484 525 1676 526 573 527 548 524 547 554 1676 526 1676 526 1676 526 1676 526 574 526 1675 527 1676 526 1675 527 548 525 573 527 573 527 546 527 11793 8927 4482 526 1676 526 546 554 547 525 574 527 1675 527 1675 527 1649 553 1652 571 551 527 1675 527 1676 526 1675 526 572 528 546 527 547 553 522 551 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8969 4417 584 1644 556 1621 579 1644 556 516 583 1616 584 542 554 490 585 542 557 516 557 542 556 516 495 1732 551 521 557 1644 557 1616 523 1706 556 10571 8945 4418 583 1645 556 1619 582 1644 556 542 557 1645 556 542 555 518 557 543 556 517 556 543 556 493 518 1732 552 521 556 1620 581 1645 494 1706 556 10572 8948 4446 556 1644 557 1645 556 1645 556 542 557 1644 557 543 553 519 556 542 557 517 556 542 557 517 495 1732 551 522 556 1620 581 1645 494 1706 556 10573 8947 4446 557 1645 556 1645 556 1645 556 543 556 1645 556 543 553 519 557 542 557 517 557 542 556 518 494 1733 550 522 556 1645 556 1645 495 1707 556 10562 8946 4421 581 1644 557 1645 556 1624 577 517 582 1644 557 542 554 519 556 517 581 517 556 542 556 517 495 1732 551 522 556 1645 556 1645 495 1706 556 11754 8947 4446 555 1645 568 1633 555 1624 577 542 557 1645 556 543 553 519 556 543 556 517 556 515 584 543 555 1645 552 521 556 1646 572 1628 494 1707 555 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 8956 4453 552 522 578 1648 553 1625 577 1623 578 1623 578 546 551 523 553 547 552 1624 577 521 553 547 553 547 547 527 553 1649 553 1649 552 1650 552 10576 8958 4454 553 547 553 1624 578 1623 579 1649 552 1650 552 547 550 523 553 548 551 1650 552 521 553 523 577 547 546 527 553 1650 552 1650 551 1650 552 10578 8959 4432 575 548 552 1650 551 1650 552 1650 552 1651 551 548 549 524 552 548 551 1651 551 522 552 548 552 548 545 528 552 1624 578 1651 551 1625 577 11762 8957 4457 551 548 552 1651 551 1651 551 1650 552 1627 575 548 549 525 551 548 552 1652 550 549 493 581 551 525 568 529 552 1651 519 1685 549 1652 550 +# +name: Power_on +type: parsed +protocol: NECext +address: 04 AC 00 00 +command: 4A B5 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 04 AC 00 00 +command: 15 EA 00 00 +# +name: White +type: parsed +protocol: NECext +address: 04 AC 00 00 +command: 55 AA 00 00 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9266 4443 660 533 632 537 656 510 628 537 627 536 628 536 627 537 625 538 624 1665 623 1668 621 1670 620 1670 620 1671 619 1670 618 1671 617 1670 618 544 617 544 617 544 618 1671 617 1671 617 544 617 1671 617 544 617 1671 617 1671 617 1671 617 544 617 544 617 1671 617 544 617 1671 615 39236 9118 2204 588 95940 9137 2203 588 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9251 4440 662 532 629 535 652 509 626 535 626 535 626 535 626 536 625 537 623 1663 623 1665 621 1667 619 1668 619 1668 618 1668 618 1669 618 1668 618 1668 618 542 618 1668 618 1668 618 1668 618 542 618 1669 617 542 618 542 618 1669 617 542 617 542 617 543 616 1669 616 542 617 1668 616 39216 9090 2201 589 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0A 00 00 00 +# +name: Power_off +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1D E2 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9222 4483 610 556 612 1638 612 556 611 557 636 532 635 534 633 536 632 562 606 1645 605 563 605 1645 605 1645 605 1645 605 1645 605 1645 605 1645 605 563 605 563 605 563 605 563 605 563 605 563 605 563 605 563 605 1645 606 1645 605 1645 605 1645 605 1645 605 1645 605 1645 605 1645 605 39540 9217 2218 605 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9238 4486 611 557 612 1639 612 557 637 533 635 535 633 562 606 563 606 564 605 1646 606 564 605 1646 605 1646 606 1646 605 1646 605 1647 605 1646 605 564 605 1647 604 564 605 564 605 564 605 564 605 564 605 564 605 1646 606 564 605 1646 605 1646 605 1646 605 1647 605 1646 605 1647 604 39549 9227 2222 605 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9236 4487 610 558 610 1639 638 531 637 532 635 534 633 538 630 564 604 565 604 1647 604 565 604 1647 604 1647 604 1647 604 1647 605 1647 604 1647 605 565 604 1647 605 565 604 1648 604 1647 605 565 604 565 605 565 604 1647 604 565 604 1648 604 565 604 565 604 1647 604 1648 603 1648 604 39553 9224 2223 605 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9247 4484 613 557 613 1639 613 556 638 532 637 534 635 535 634 562 607 563 607 1645 608 563 608 1646 608 1646 607 1646 606 1646 607 1646 606 1646 606 563 607 563 606 564 606 1646 606 1646 606 564 605 564 606 564 606 1646 606 1646 605 1646 605 564 605 564 605 1646 605 1646 605 1646 606 39550 9230 2220 605 96298 9231 2219 605 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9243 4423 667 527 639 1584 664 528 637 529 637 531 635 533 633 535 632 536 631 1618 631 536 631 1617 632 1618 631 1618 631 1618 631 1618 631 1618 631 536 631 536 631 536 631 1618 631 536 631 536 631 536 631 536 631 1618 631 1618 631 1618 631 536 631 1618 631 1618 631 1618 631 1618 631 39535 9206 2189 631 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9223 4451 637 530 636 1613 637 530 637 531 638 531 637 531 636 533 611 558 611 1640 636 532 635 1614 637 1613 636 1613 637 1613 636 1612 638 1613 635 532 610 557 636 532 636 533 608 558 636 533 635 558 611 532 635 1613 636 1614 635 1613 635 1617 608 1641 634 1615 635 1615 609 1642 634 39524 9223 2181 638 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9225 4453 636 531 638 1611 639 530 636 532 638 530 636 533 636 531 637 531 635 1612 636 534 635 1612 636 1613 637 1612 637 1613 637 1612 637 1613 635 533 635 1613 637 531 636 532 634 532 636 534 634 533 634 531 637 1612 637 531 637 1611 637 1613 635 1613 636 1613 637 1611 637 1612 636 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9325 4455 638 557 617 1611 616 558 640 559 614 531 642 531 641 533 639 534 638 1615 638 533 666 1588 640 1612 641 1612 641 1614 640 1613 640 1613 641 533 639 532 641 1617 640 558 613 1613 641 534 640 533 641 533 641 1613 642 1614 640 532 640 1616 638 534 638 1612 639 1613 641 1612 639 39473 9278 2181 640 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9241 4450 638 530 638 1612 639 530 637 530 637 533 636 535 637 532 612 558 638 1612 638 529 639 1611 638 1611 638 1612 636 1612 638 1612 636 1612 638 533 633 1615 637 1613 637 531 636 1612 637 532 636 531 637 530 637 1612 637 531 635 531 637 1613 637 531 637 1612 637 1612 636 1612 638 39512 9236 2185 636 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 41 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9340 4367 678 463 678 463 678 464 677 1585 678 466 675 466 674 466 675 468 673 1591 672 1615 647 1616 647 494 646 1617 645 1618 645 496 644 1618 645 496 645 496 645 496 645 496 645 496 644 496 645 496 645 496 645 1618 646 1618 645 1618 645 1618 645 1618 645 1618 644 1618 645 1618 644 41527 9284 2180 644 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9255 4394 647 491 646 491 674 463 675 1583 678 461 676 462 675 464 698 463 649 1585 674 1586 673 1611 648 489 647 1613 645 1614 644 493 644 1616 643 494 643 1617 642 495 642 495 642 495 642 495 642 495 642 495 642 1617 643 495 642 1617 642 1618 641 1617 642 1618 641 1618 641 1618 642 41518 9251 2179 643 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9218 4395 639 493 639 493 640 493 639 1615 639 493 639 493 639 494 639 493 639 1615 639 1616 639 1615 639 494 638 1616 639 1616 638 494 638 1616 639 494 638 494 638 494 639 494 637 1617 638 494 638 494 638 494 638 1616 638 1616 637 1616 638 1616 638 495 637 1616 638 1617 637 1617 637 41506 9241 2178 641 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9249 4393 644 491 643 490 644 490 644 1612 644 490 644 490 644 490 644 490 644 1611 645 1612 644 1612 644 492 642 1638 642 1613 642 492 642 1614 642 492 642 1614 641 492 641 492 642 1614 641 493 641 493 641 493 641 1615 640 493 641 1615 641 1615 640 494 640 1615 641 1615 641 1616 640 41503 9232 2178 643 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9704 4505 627 549 631 545 635 541 628 547 633 542 627 546 634 541 628 546 634 1649 627 1657 629 1655 631 1654 632 1654 633 1651 625 1656 630 1651 625 1654 633 1647 629 1649 627 541 628 540 629 539 630 538 621 547 622 545 624 544 625 542 627 1649 627 1650 626 1651 625 1652 624 1655 631 40068 9687 2231 625 96499 9640 2233 623 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9673 4499 633 544 636 540 629 545 635 540 629 545 635 540 629 545 635 539 630 1651 636 1648 638 1646 630 1654 632 1652 635 1650 637 1647 629 1656 630 547 633 1650 637 1647 629 545 635 538 631 541 628 544 625 547 633 1646 630 543 626 546 634 1674 602 1651 636 1645 631 1651 636 1646 630 40022 9647 2227 629 96379 9643 2232 634 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9539 4502 619 546 623 543 626 539 620 546 623 542 627 538 621 545 624 542 627 1648 628 1647 629 1647 629 1646 619 1656 620 1655 621 1655 621 1654 622 1654 622 1654 622 544 678 488 629 538 621 545 624 542 627 538 621 546 623 542 627 1648 628 1648 628 1647 629 1647 629 1647 629 1646 630 40018 9544 2234 621 96390 9543 2233 622 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9565 4508 624 544 625 542 627 540 629 538 684 483 623 544 625 541 628 539 630 1646 630 1646 630 1646 630 1646 630 1647 628 1647 629 1647 629 1647 629 538 621 546 623 543 626 540 629 538 621 545 624 542 627 540 629 1646 630 1646 630 1646 630 1646 630 1646 630 1646 630 1646 630 1646 630 40023 9545 2228 627 96393 9541 2236 619 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9572 4501 620 547 622 546 623 545 624 544 625 542 627 541 628 539 630 538 621 1656 630 1648 628 1649 627 1650 626 1652 624 1654 622 1655 621 1657 629 539 630 1647 629 539 630 537 622 546 623 545 624 543 626 542 627 1649 627 541 628 1649 627 1650 626 1651 625 1653 623 1654 622 1656 620 40038 9563 2235 620 96402 9566 2232 623 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9652 4502 631 543 627 546 624 548 632 539 630 541 629 542 628 542 628 543 626 1650 626 1652 624 1655 632 1647 682 1597 627 1652 624 1655 684 1595 681 491 626 545 625 548 632 1675 601 546 634 541 629 546 634 540 630 1651 636 1646 630 1652 635 541 628 1653 634 1648 628 1655 632 1651 636 40044 9715 2231 636 96521 9670 2238 628 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9738 4507 636 542 638 538 631 545 635 541 629 548 632 544 636 540 629 547 633 1650 637 1648 639 1646 630 1655 632 1653 634 1652 635 1650 637 1648 639 1647 640 538 631 545 635 1650 637 541 639 538 631 546 634 543 637 540 629 1655 632 1654 633 546 634 1651 636 1650 637 1650 637 1650 637 40020 9719 2230 636 96379 9708 2238 629 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9724 4507 636 542 638 540 629 547 633 544 636 541 628 548 632 544 636 540 630 1654 633 1681 606 1651 636 1648 628 1656 631 1654 633 1652 635 1649 638 539 630 1653 634 543 637 1647 629 547 633 544 636 540 629 546 634 1649 638 539 630 1653 634 543 637 1647 629 1685 602 1683 604 1652 635 40012 9716 2232 635 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9640 4503 630 541 629 543 627 545 625 547 633 538 632 540 630 542 628 544 626 1655 632 1649 628 1653 623 1657 630 1650 627 1654 633 1648 629 1652 625 547 633 539 631 1650 627 1655 632 539 631 542 628 544 626 546 624 1657 630 1651 625 546 634 538 632 1649 628 1654 633 1648 629 1653 623 40033 9628 2238 629 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9658 4506 627 548 632 541 629 544 626 548 632 541 629 544 626 547 633 540 630 1651 636 1646 630 1652 635 1646 630 1651 636 1646 630 1651 626 1656 631 1650 627 547 633 1646 631 1651 626 548 632 539 631 541 629 543 627 544 626 1653 634 539 631 541 629 1651 625 1655 632 1649 627 1654 633 40032 9626 2234 633 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9507 4501 622 543 627 538 621 544 626 539 620 545 625 540 619 546 624 542 628 1647 619 1656 620 1654 622 1652 624 1651 626 1649 627 1647 629 1646 620 545 625 1650 627 1648 628 1646 620 545 625 541 629 536 623 542 628 1647 619 546 624 541 629 537 622 1651 626 1649 628 1647 619 1656 620 40036 9529 2230 626 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9497 4505 618 546 624 540 619 545 625 540 619 544 626 539 620 543 627 538 621 1652 625 1649 628 1646 620 1654 622 1651 625 1648 629 1646 620 1654 622 542 628 1646 620 544 626 1648 628 1646 620 545 625 539 620 545 625 1649 627 537 622 1652 624 540 619 545 625 1649 627 1647 619 1655 621 40037 9506 2236 620 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 14 00 00 00 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9200 4348 645 540 616 1613 617 541 616 540 617 540 617 539 618 539 618 538 619 1611 619 538 619 1612 618 1612 642 1588 642 1589 641 1590 639 1591 639 519 638 1593 637 1593 637 521 636 1617 613 544 613 544 613 544 613 1617 613 544 613 544 613 1617 613 545 612 1617 613 1617 613 1618 612 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 3483 1746 515 353 516 354 516 1226 516 1227 514 354 516 1226 515 354 515 353 516 353 517 1227 514 354 515 354 515 1226 515 354 516 1227 514 354 515 1226 516 353 516 353 517 1227 514 354 515 354 515 354 515 354 516 1226 515 354 515 1227 515 1226 515 353 517 1227 514 353 516 353 516 354 515 354 516 1227 515 354 515 354 516 1226 516 354 515 354 515 74951 3486 1746 516 354 515 355 515 1227 516 1226 516 354 516 1226 516 354 515 353 516 354 516 1227 515 354 516 353 517 1227 515 354 516 1228 514 354 516 1227 515 353 517 355 515 1228 514 353 516 354 515 354 516 355 515 1227 515 354 516 1228 515 1227 515 354 516 1227 515 355 515 354 516 354 516 354 516 1227 515 354 515 354 516 1226 516 354 515 353 516 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 3509 1722 479 391 478 391 479 1264 478 1264 477 391 479 1264 478 390 479 389 480 391 478 1263 479 389 480 391 479 1265 477 391 479 1264 478 391 479 1263 479 391 479 391 479 1263 478 391 478 391 479 391 478 391 479 1265 477 1265 477 1264 479 1262 480 391 479 1264 478 392 477 392 478 392 478 1263 480 1264 478 392 477 391 479 1264 478 392 477 391 478 74988 3509 1721 480 391 478 392 478 1264 478 1263 479 392 478 1264 478 390 479 393 476 390 480 1263 479 391 478 392 478 1263 479 392 478 1264 478 391 479 1263 479 391 478 390 480 1264 477 389 481 389 480 390 479 392 478 1262 480 1262 480 1264 478 1265 476 391 479 1262 480 390 479 392 477 392 478 1264 478 1262 479 392 477 392 478 1262 480 392 477 390 479 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 3481 1749 452 416 453 418 452 1290 452 1290 452 419 451 1289 453 418 452 417 452 419 451 1290 452 417 452 419 451 1290 452 418 452 1291 451 417 453 1290 452 417 452 418 452 1289 452 417 452 418 451 416 453 418 451 416 454 1289 452 417 453 1289 453 418 451 1289 453 417 452 418 452 1291 451 1289 453 417 452 418 452 418 452 1290 452 417 452 419 450 75017 3484 1751 451 417 453 418 452 1291 452 1290 452 418 452 1290 452 419 451 418 452 418 452 1291 452 418 451 419 451 1290 452 419 451 1290 452 418 452 1289 453 418 452 417 453 1289 453 418 451 418 452 417 453 418 452 418 452 1290 452 419 451 1291 451 418 452 1290 452 418 452 419 451 1291 452 1289 453 417 453 417 453 417 453 1291 451 418 452 418 451 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 3509 1719 481 388 481 388 482 1259 483 1260 481 387 483 1260 482 385 484 385 484 385 485 1257 484 386 483 386 483 1257 484 385 485 1258 483 386 506 1235 508 362 507 362 508 1233 509 359 510 359 511 360 510 360 510 359 511 361 509 1232 510 1231 511 360 510 1232 510 359 510 361 509 1232 510 359 511 1232 510 359 510 360 510 1232 509 361 508 360 509 74955 3509 1723 478 391 504 366 478 1265 477 1264 478 392 478 1263 479 391 478 392 477 392 478 1263 479 392 477 391 479 1264 478 391 479 1263 479 391 479 1264 477 391 479 392 478 1263 479 389 480 390 479 391 478 390 479 391 478 391 479 1264 478 1263 479 391 479 1264 478 390 479 392 478 1264 478 391 479 1263 479 391 478 392 478 1263 479 391 478 391 478 +# +name: Power_off +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 03 FC 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 13 EC 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 17 E8 00 00 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1400 1400 700 700 700 1400 700 2800 700 2100 1400 700 700 700 700 1400 1400 2800 1400 2800 700 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 1400 1400 700 700 700 700 1400 2800 700 1400 700 1400 700 1400 700 1400 1400 2800 1400 2800 700 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 700 700 700 2100 1400 700 700 2800 700 1400 700 700 700 1400 1400 700 700 1400 700 700 700 700 700 700 700 2100 700 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 700 700 700 2100 1400 700 700 2800 700 1400 700 700 700 1400 700 700 700 700 700 700 700 700 1400 1400 700 2100 700 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 700 700 700 2100 1400 700 700 2800 700 1400 700 700 700 2800 1400 1400 700 2100 700 1400 700 1400 700 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 700 700 700 2100 1400 700 700 2800 700 2100 1400 1400 700 700 700 700 700 700 700 700 1400 1400 700 2100 700 2100 1400 700 700 700 700 1400 1400 2800 1400 2800 700 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9238 4497 624 520 623 519 624 520 623 519 624 519 624 519 624 520 623 520 623 1627 624 1627 624 1628 623 1629 622 1628 623 1628 623 1628 623 1628 623 1627 624 519 624 1628 623 520 623 520 623 519 624 1627 624 519 624 517 626 1628 622 520 623 1627 623 1628 622 1627 623 518 625 1628 622 39862 9236 2229 623 96184 9229 2231 621 96191 9224 2231 620 96186 9222 2230 622 96190 9218 2228 623 96177 9215 2227 624 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9214 4521 572 572 571 570 573 573 570 572 571 572 571 571 572 571 572 574 569 1679 572 1680 570 1679 571 1678 573 1681 570 1680 571 1680 570 1679 572 571 572 1679 572 1679 572 571 572 572 571 572 571 1679 572 571 572 1683 568 572 571 572 571 1681 570 1678 572 1682 568 569 574 1679 571 39924 9206 2252 572 96256 9203 2253 571 96261 9200 2254 570 96265 9194 2254 570 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9229 4516 596 547 596 548 595 548 595 548 595 548 595 548 595 548 595 549 594 1654 595 1655 593 1654 594 1655 594 1655 593 1656 592 1655 594 1654 594 1655 593 1655 593 1654 594 549 593 549 593 548 594 549 593 548 594 547 595 548 594 549 593 1655 593 1656 591 1655 593 1655 593 1655 593 39847 9197 2253 591 96092 9191 2252 592 96098 9189 2252 592 96101 9186 2252 593 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9230 4519 595 548 595 548 595 548 595 548 595 548 595 546 597 547 596 547 596 1655 595 1654 596 1655 595 1654 596 1655 595 1654 596 1654 595 1655 595 548 595 1656 594 1654 595 548 594 1654 595 548 594 546 597 547 596 1655 594 548 594 548 595 1655 594 546 596 1654 595 1654 595 1655 594 39860 9213 2253 594 96136 9201 2253 619 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9266 4495 623 519 624 520 623 521 624 520 624 520 624 520 624 520 624 519 625 1628 623 1628 623 1631 620 1627 623 1627 624 1628 622 1628 622 1627 623 1629 622 522 621 522 621 1630 620 1628 622 521 622 520 623 521 622 522 621 1630 620 1630 620 521 621 522 597 1656 594 1654 596 1655 595 39873 9221 2254 619 96136 9233 2227 623 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9230 4521 570 572 571 572 572 572 572 573 571 572 572 572 572 572 572 570 574 1680 570 1681 569 1679 594 1656 594 1657 571 1679 571 1679 594 1656 571 1679 571 572 571 1680 593 1656 571 572 571 571 572 571 572 571 572 571 572 1678 589 554 571 571 572 1679 593 1657 592 1658 591 1656 571 39892 9210 2252 594 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9246 4516 601 542 602 543 602 544 601 543 601 543 602 545 599 543 603 544 601 1650 601 1650 602 1652 599 1653 598 1651 601 1650 601 1651 599 1650 600 544 600 543 601 1650 602 1649 603 544 601 544 601 544 601 543 602 1649 603 1650 601 545 599 543 601 1650 601 1651 600 1650 601 1651 600 39854 9267 2226 599 96119 9215 2250 599 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9214 4517 573 570 573 570 573 570 573 570 573 571 572 569 574 570 573 570 573 1677 573 1678 571 1675 574 1679 570 1677 572 1676 573 1677 572 1677 573 571 572 570 573 570 574 1677 573 1678 572 571 572 571 573 571 573 1678 572 1677 601 1651 604 543 605 546 598 1652 598 1651 600 1651 601 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9209 4519 621 521 621 522 595 547 595 547 596 546 597 545 598 545 598 547 596 1653 621 1627 623 1628 621 1628 621 1629 619 1628 596 1654 595 1652 597 547 595 1654 596 1654 596 1656 594 1654 595 547 595 1655 594 547 596 1656 594 548 595 547 596 547 596 548 595 1655 595 548 594 1656 594 39866 9205 2253 570 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9174 4018 678 433 557 521 560 519 617 435 642 437 584 495 1071 1116 1069 1119 550 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9212 4018 643 436 558 519 536 542 594 459 1131 1079 1106 1079 591 461 587 493 557 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9308 4581 575 587 577 587 577 587 602 563 602 564 603 565 603 594 574 597 573 1716 570 1716 570 1716 570 1717 570 595 571 1718 571 1718 572 1718 571 1717 571 1717 570 596 571 595 570 595 571 595 570 595 570 596 571 596 570 595 571 1718 572 1720 570 1718 570 1718 570 1717 570 1717 570 41512 9323 2294 570 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9318 4575 575 586 600 561 601 560 601 560 601 561 599 563 597 566 595 593 568 1712 569 1713 569 1713 569 1713 569 593 569 1713 569 1713 569 1713 569 593 569 1713 568 592 569 592 568 592 569 593 568 593 568 593 568 1713 569 593 569 1714 568 1714 568 1714 568 1714 569 1714 567 1713 568 41448 9313 2292 569 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9202 4538 569 581 593 556 594 554 595 554 594 555 593 557 592 560 589 561 589 1671 589 1671 589 1670 589 1670 589 561 588 1669 590 1669 590 1670 589 561 589 560 589 1669 588 560 588 560 588 560 588 560 589 561 588 1669 588 1669 589 560 588 1669 588 1669 588 1669 589 1669 588 1669 589 41008 9210 2244 589 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9214 4548 568 582 569 581 569 581 569 581 569 582 593 557 593 558 592 558 592 1672 590 1672 590 1673 589 1673 590 561 589 1673 589 1673 589 1673 589 1674 588 1674 588 1698 564 586 564 586 564 586 564 586 564 586 564 586 564 586 564 586 564 1698 564 1699 563 1699 563 1699 564 1699 564 41159 9218 2249 592 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 12 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 08 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Power_on +type: parsed +protocol: NECext +address: A8 DE 00 00 +command: 01 FE 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: A8 DE 00 00 +command: 0E F1 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: A8 DE 00 00 +command: 16 E9 00 00 +# +name: Brightness_up +type: parsed +protocol: NECext +address: A8 DE 00 00 +command: 00 FF 00 00 +# +name: Brightness_dn +type: parsed +protocol: NECext +address: A8 DE 00 00 +command: 0D F2 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1F 00 00 00 +# +name: Power_off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1E 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 18 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 14 00 00 00 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 02 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 03 00 00 00 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9377 4500 621 532 617 532 640 508 639 509 636 537 608 538 608 538 608 539 607 1653 606 1653 606 1653 606 1653 606 539 605 1653 605 1653 606 1652 606 1653 605 1653 605 539 605 538 605 539 604 539 604 539 604 539 604 539 604 539 604 1653 604 1653 604 1653 604 1653 604 1654 603 1653 604 41183 9206 2231 603 96569 9188 2231 603 96577 9226 2229 607 97867 9287 4514 608 540 607 540 607 540 607 540 607 540 607 540 607 540 607 540 607 1654 608 1654 608 1655 607 1655 608 540 609 1655 608 1655 607 1654 608 1655 607 1655 607 540 608 540 608 541 608 540 609 540 608 540 608 540 608 540 607 1655 606 1655 606 1655 608 1655 607 1655 606 1655 606 41158 9313 2236 608 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9224 4482 661 507 635 507 634 507 633 509 631 511 629 513 628 513 628 513 629 1628 628 1628 628 1628 629 1628 628 513 629 1628 628 1628 628 1628 628 514 628 1628 628 514 627 514 627 514 627 514 627 514 627 514 627 1628 628 514 627 1628 628 1629 627 1629 627 1629 627 1629 628 1629 628 41033 9173 2209 627 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 5E 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 05 00 00 00 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9068 4355 700 451 647 477 647 478 646 478 646 478 645 479 644 480 643 482 642 1612 641 1612 642 1613 641 1613 641 484 641 1613 641 1613 641 1613 641 484 641 1613 641 484 641 484 641 484 641 484 641 484 641 484 641 1613 640 484 641 1613 640 1613 641 1613 640 1613 641 1613 641 1613 640 40687 9010 2181 641 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 53909 152687 12244 337 12198 366 66487 9983 4472 674 545 644 545 644 546 643 545 673 517 672 517 672 518 670 1631 667 1662 635 1666 632 1670 630 1696 603 1696 604 1697 603 1697 603 585 604 586 604 1697 603 586 604 586 604 1697 604 586 604 586 605 587 604 1698 603 587 604 1698 603 1698 603 587 604 1699 602 1699 602 1699 602 39988 10003 2270 605 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 147 5738 150 1865 147 84886 394 8086 9972 4508 642 546 643 546 644 547 643 550 666 553 636 555 635 557 634 1669 632 1669 632 1669 632 1669 632 1670 631 1669 632 1669 632 1669 632 558 632 558 632 1670 632 558 632 1670 632 1670 631 558 632 558 632 558 633 1670 632 559 632 1670 632 559 632 559 632 1671 631 1670 632 1671 630 39993 9998 2269 605 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9469 4505 682 478 682 478 682 478 683 477 683 477 683 478 682 479 681 478 682 1591 682 1592 681 1592 682 1590 683 479 682 1591 682 1592 682 1589 684 1591 685 1592 682 477 683 479 681 478 682 478 683 479 682 478 683 478 683 479 682 1592 681 1590 684 1591 683 1593 680 1594 680 1592 681 40987 9539 2225 685 96197 9542 2174 679 96149 9504 2224 681 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9447 4469 683 478 684 480 683 477 683 479 681 480 678 479 680 478 680 479 681 1592 681 1592 680 1590 682 1591 680 478 680 1591 680 1592 682 1591 681 477 683 1589 680 479 679 479 680 477 680 478 678 479 678 479 680 1592 680 478 682 1592 680 1593 680 1592 681 1593 682 1592 682 1592 681 41135 9407 2225 678 +# +name: Brightness_up +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9393 4563 593 565 594 565 594 564 595 564 595 565 594 564 594 564 594 565 593 1679 592 1678 593 1679 592 1678 593 564 593 1678 592 1679 592 1679 591 564 592 564 592 565 591 565 591 564 592 566 590 565 591 565 591 1680 590 1680 590 1680 590 1680 589 1679 568 1703 567 1703 567 1703 567 41265 9322 2280 566 96780 9323 2280 590 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9403 4509 677 478 678 479 678 480 677 479 677 479 677 478 678 479 677 479 677 1593 676 1594 676 1592 677 1593 676 479 676 1592 677 1593 676 1593 675 480 674 478 676 480 674 479 675 478 676 478 676 479 675 479 675 1594 674 1595 673 1593 675 1592 676 1593 674 1593 674 1593 674 1592 676 41165 9341 2228 672 96697 9330 2231 615 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9441 4485 622 532 624 533 624 533 624 534 622 532 625 533 624 534 622 533 623 1639 623 1644 623 1646 622 1646 621 534 622 1636 624 1647 623 1649 620 534 621 532 623 1648 622 534 623 532 624 532 624 532 624 533 623 1647 623 1648 622 535 621 1644 623 1648 676 1593 677 1592 675 1593 677 72746 9403 4511 675 479 677 480 675 478 677 479 676 477 678 478 677 478 677 478 677 1593 677 1594 676 1594 675 1594 675 477 677 1593 676 1594 675 1593 675 478 676 479 675 1594 675 479 675 479 675 480 674 479 676 479 675 1592 677 1594 675 478 677 1593 677 1593 676 1593 676 1594 675 1594 675 40985 9360 2217 619 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9483 4522 685 481 684 481 689 480 680 478 679 477 680 479 679 480 680 480 682 1593 678 1587 685 36306 9757 4454 680 480 678 479 679 478 679 479 678 478 679 478 680 479 679 478 680 1593 678 1593 678 1593 678 1593 677 479 677 1593 677 1593 677 36793 9383 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9414 4486 676 478 623 530 623 530 621 533 623 532 622 534 621 532 676 477 624 1634 678 1589 622 1644 622 1642 676 480 674 1583 677 1589 675 1587 678 1585 676 478 677 1584 677 476 678 478 678 478 675 496 680 477 676 477 677 1582 677 479 677 1585 675 1616 676 1592 676 1590 678 1581 678 +# +name: Green +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9528 4516 631 534 636 532 643 534 634 534 629 533 633 533 631 533 635 533 636 1642 631 1639 631 1643 634 1648 637 535 633 1646 637 1648 634 1641 638 1647 630 534 632 535 633 1648 637 533 636 535 633 534 634 534 610 560 609 1674 610 1675 609 559 606 1676 606 1675 609 1676 607 1675 607 41211 9449 2262 595 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9399 4513 673 484 673 484 673 482 674 483 674 483 674 482 675 482 675 482 675 1599 673 1598 673 1597 674 1598 673 482 674 1595 676 1596 675 1596 674 480 675 1596 674 1595 676 482 680 481 677 481 675 479 675 480 674 1593 676 479 676 479 679 1593 683 1594 678 1593 674 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9371 4482 674 479 675 479 676 477 678 478 677 478 677 478 676 479 675 479 675 1594 675 1594 675 1596 673 1594 675 479 674 1594 674 1594 675 1594 674 1595 673 1594 674 1594 674 479 675 478 675 480 673 479 674 479 674 479 674 479 674 480 673 1596 672 1594 674 1594 674 1594 674 1594 674 41002 9394 2175 672 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 6611 147 2428 4472 605 535 610 529 606 532 603 532 603 532 603 531 604 530 605 529 606 1632 596 1640 599 1639 600 1639 600 538 607 1632 607 1632 607 1632 607 1633 606 532 603 533 602 535 600 536 599 537 598 539 606 529 606 530 605 1633 606 1633 606 1633 606 1633 606 1633 606 1633 606 1632 607 40787 9187 2210 607 +# +name: Power_off +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9374 4485 589 567 596 534 619 540 592 562 591 535 618 533 588 561 592 534 671 1613 588 1670 584 1672 613 1668 586 1670 584 1671 614 1665 589 1664 590 559 583 1670 584 1672 613 537 584 562 591 531 611 536 585 563 590 1662 592 558 584 539 614 1667 587 1667 587 1671 614 1662 581 1669 585 39845 9308 2246 595 +# +name: Power_on +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9309 4474 590 589 564 562 591 561 561 589 564 560 593 560 561 587 555 569 584 1669 595 1662 592 1663 622 1664 590 1665 589 1668 617 1667 587 1669 585 1670 615 1669 595 1660 594 586 556 567 586 566 555 593 560 563 590 560 561 588 565 561 592 1663 591 1666 588 1669 616 1662 581 1668 586 39829 9230 2246 585 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: White +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9260 4486 588 561 581 565 588 558 553 593 560 535 618 558 563 557 585 564 589 1663 591 1664 590 1664 611 1668 585 1666 587 1664 610 1670 583 1671 582 1672 613 1668 585 567 586 536 585 590 563 559 583 539 582 595 558 564 589 565 567 1662 623 1665 588 1669 584 1671 614 1670 594 1664 589 39861 9327 2247 593 96045 9293 2243 587 96162 9292 2242 588 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9239 4483 591 558 584 536 606 540 581 563 579 541 612 535 586 559 583 537 605 1698 556 1668 586 1666 608 1668 586 1664 590 1661 614 1663 580 1670 636 512 578 542 611 535 586 1688 587 534 587 559 583 536 606 539 582 1693 582 1668 586 1664 590 558 584 1664 579 1671 614 1663 580 1670 584 39857 9228 2239 581 +# +name: Blue +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9205 4477 586 558 584 533 609 534 577 566 576 541 612 533 578 566 576 542 611 1663 580 1667 587 1663 611 1665 589 1662 581 1670 615 1664 590 1663 591 560 582 1668 586 564 578 1671 583 566 587 535 607 541 580 567 586 1662 581 566 587 1660 583 565 577 1671 593 1661 614 1664 642 1608 583 39864 9242 2244 586 +# +name: Red +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9305 4480 594 560 582 543 610 542 590 562 591 536 617 536 585 567 586 539 614 1667 587 1668 586 1670 615 1667 586 1697 557 1673 612 1699 565 1664 590 562 591 534 608 1673 591 1663 591 563 590 536 617 536 585 566 587 1667 587 1670 615 540 592 560 582 1673 591 1665 620 1663 591 1693 561 39858 9299 2242 588 95996 9352 2251 590 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Brightness_dn +type: raw +frequency: 38000 +duty_cycle: 0.33 +data: 9279 4480 593 559 583 541 612 537 584 562 580 541 612 538 583 563 590 533 609 1695 558 1666 587 1664 621 1661 592 1661 582 1671 614 1666 587 1664 589 559 583 1693 560 1664 610 1668 585 538 615 534 587 559 583 536 606 1696 557 538 615 534 587 561 581 1670 583 1669 616 1665 588 1665 588 39860 9164 2249 581 +# +name: Brightness_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1A 00 00 00 +# +name: Power_on +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 1C 00 00 00 +# +name: White +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 1D 00 00 00 +# +name: Red +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 44 00 00 00 +# +name: Green +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Blue +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Brightness_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 diff --git a/applications/main/infrared/scenes/infrared_scene_config.h b/applications/main/infrared/scenes/infrared_scene_config.h index 3be8e47435..89c03b88ee 100644 --- a/applications/main/infrared/scenes/infrared_scene_config.h +++ b/applications/main/infrared/scenes/infrared_scene_config.h @@ -20,6 +20,7 @@ ADD_SCENE(infrared, universal_ac, UniversalAC) ADD_SCENE(infrared, universal_fan, UniversalFan) ADD_SCENE(infrared, universal_audio, UniversalAudio) ADD_SCENE(infrared, universal_projector, UniversalProjector) +ADD_SCENE(infrared, universal_leds, UniversalLEDs) ADD_SCENE(infrared, gpio_settings, GpioSettings) ADD_SCENE(infrared, debug, Debug) ADD_SCENE(infrared, error_databases, ErrorDatabases) diff --git a/applications/main/infrared/scenes/infrared_scene_universal.c b/applications/main/infrared/scenes/infrared_scene_universal.c index a288716c7b..2a15695edc 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal.c +++ b/applications/main/infrared/scenes/infrared_scene_universal.c @@ -4,6 +4,7 @@ typedef enum { SubmenuIndexUniversalTV, SubmenuIndexUniversalAudio, SubmenuIndexUniversalProjector, + SubmenuIndexUniversalLEDs, SubmenuIndexUniversalFan, SubmenuIndexUniversalAirConditioner, } SubmenuIndex; @@ -38,6 +39,13 @@ void infrared_scene_universal_on_enter(void* context) { infrared_scene_universal_submenu_callback, context); + submenu_add_item( + submenu, + "LEDs", + SubmenuIndexUniversalLEDs, + infrared_scene_universal_submenu_callback, + context); + submenu_add_item( submenu, "Fans", @@ -73,6 +81,9 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexUniversalProjector) { scene_manager_next_scene(scene_manager, InfraredSceneUniversalProjector); consumed = true; + } else if(event.event == SubmenuIndexUniversalLEDs) { + scene_manager_next_scene(scene_manager, InfraredSceneUniversalLEDs); + consumed = true; } else if(event.event == SubmenuIndexUniversalFan) { scene_manager_next_scene(scene_manager, InfraredSceneUniversalFan); consumed = true; diff --git a/applications/main/infrared/scenes/infrared_scene_universal_leds.c b/applications/main/infrared/scenes/infrared_scene_universal_leds.c new file mode 100644 index 0000000000..334b140c52 --- /dev/null +++ b/applications/main/infrared/scenes/infrared_scene_universal_leds.c @@ -0,0 +1,133 @@ +#include "../infrared_app_i.h" + +#include "common/infrared_scene_universal_common.h" + +void infrared_scene_universal_leds_on_enter(void* context) { + InfraredApp* infrared = context; + ButtonPanel* button_panel = infrared->button_panel; + InfraredBruteForce* brute_force = infrared->brute_force; + + // Button codes + // Power_off, Power_on, Brightness_up, Brightness_dn, Red, Blue, Green, White + + infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/leds.ir")); + + button_panel_reserve(button_panel, 2, 4); + uint32_t i = 0; + button_panel_add_item( + button_panel, + i, + 0, + 0, + 10, + 12, + &I_power_19x20, + &I_power_hover_19x20, + infrared_scene_universal_common_item_callback, + context); + button_panel_add_icon(button_panel, 15, 34, &I_on_text_9x5); + infrared_brute_force_add_record(brute_force, i++, "Power_on"); + + button_panel_add_item( + button_panel, + i, + 1, + 0, + 35, + 12, + &I_off_19x20, + &I_off_hover_19x20, + infrared_scene_universal_common_item_callback, + context); + button_panel_add_icon(button_panel, 38, 34, &I_off_text_12x5); + infrared_brute_force_add_record(brute_force, i++, "Power_off"); + + button_panel_add_item( + button_panel, + i, + 0, + 1, + 10, + 42, + &I_plus_19x20, + &I_plus_hover_19x20, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Brightness_up"); + + button_panel_add_item( + button_panel, + i, + 1, + 1, + 35, + 42, + &I_minus_19x20, + &I_minus_hover_19x20, + infrared_scene_universal_common_item_callback, + context); + button_panel_add_icon(button_panel, 12, 64, &I_brightness_text_40x5); + infrared_brute_force_add_record(brute_force, i++, "Brightness_dn"); + + button_panel_add_item( + button_panel, + i, + 0, + 2, + 10, + 74, + &I_red_19x20, + &I_red_hover_19x20, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Red"); + button_panel_add_item( + button_panel, + i, + 1, + 2, + 35, + 74, + &I_green_19x20, + &I_green_hover_19x20, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Green"); + button_panel_add_item( + button_panel, + i, + 0, + 3, + 10, + 99, + &I_blue_19x20, + &I_blue_hover_19x20, + infrared_scene_universal_common_item_callback, + context); + infrared_brute_force_add_record(brute_force, i++, "Blue"); + button_panel_add_item( + button_panel, + i, + 1, + 3, + 35, + 99, + &I_white_19x20, + &I_white_hover_19x20, + infrared_scene_universal_common_item_callback, + context); + button_panel_add_icon(button_panel, 19, 121, &I_color_text_24x5); + infrared_brute_force_add_record(brute_force, i++, "White"); + + button_panel_add_label(button_panel, 20, 9, FontPrimary, "LEDs"); + + infrared_scene_universal_common_on_enter(context); +} + +bool infrared_scene_universal_leds_on_event(void* context, SceneManagerEvent event) { + return infrared_scene_universal_common_on_event(context, event); +} + +void infrared_scene_universal_leds_on_exit(void* context) { + infrared_scene_universal_common_on_exit(context); +} diff --git a/assets/icons/Infrared/blue_19x20.png b/assets/icons/Infrared/blue_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..2397ebea7b8681bd9a4ade9737edd43288ef26eb GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oEA?P#}Etu zw^JPX4k&Q2JpA9k?B+6opg@-DHR8fkPo84ZQF3xQa^^HyZAr z-FR{HuUU7atzCPg8&~Z%lhHnSF?z~L-rrd{@e&_reZ6(_h9}TH44$rjF6*2UngABO BPgMW_ literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/blue_hover_19x20.png b/assets/icons/Infrared/blue_hover_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..c6e608195f8e773e79b9e9b25b792c0ee261950b GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oDxqL#}Etu zw^I)CH5hO>zx-cs;@xYU#>J9izVpo{8>>@Xp`n6;3^rb+N-Qn4jA!4m1f5&BX0hA` z&6~~9`kDvVNX|A`{X6z&l{)X@Rj2o+GGE#-Y14_j+YP7ic#SeX8Gg?iF;p#2Ps-gUoaCfk*fK+w$pS2+(c@Pgg&ebxsLQ08nm9QUCw| literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/color_text_24x5.png b/assets/icons/Infrared/color_text_24x5.png new file mode 100644 index 0000000000000000000000000000000000000000..6656cddaa7a351c2f4425e5c39fa474c08185737 GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^5mdKI;Vst04N1C AX#fBK literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/green_19x20.png b/assets/icons/Infrared/green_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..5777e752cf8ae69b2a2490b5a6d520a56f88aa70 GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oC%&Tjv*HQ zYo`SA9Z=wKe)z9F?d7IfM;RPb?A3zjO;O59Q&U>BDkAEBy8tVX!oL?6B>GEc#I$ct zV&F-fnd9%uvHD6r`>qZ48mAg={rjz{W85O%cfe-(t(m$FlK|pTo4+My=v-n#b?W$rE_P)uiYCd#}Etu zw^I&s9Z=wLp8Ws+i@uJx+mcs_JL)&haF0n@(bSo-b$>AW&sb)UiubU=6vzl{> zqarb`r9Jgo&As>cZE}3x^WMK*?qk36l+UHBcG9e~pT4O6shyH`pzP{5<~`37zh*DI Rum@-xgQu&X%Q~loCIGlyL0$j= literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/minus_hover_19x20.png b/assets/icons/Infrared/minus_hover_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..92702ee37508fd044a67cceaa6c9d8797c936c5c GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oIp<(#}Etu zmdKI;Vst0RDF{S^xk5 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/on_text_9x5.png b/assets/icons/Infrared/on_text_9x5.png new file mode 100644 index 0000000000000000000000000000000000000000..00749e16ab47e34948e62463bc713a278b088cca GIT binary patch literal 121 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRV!3HF!2OrA@QjEnx?oJHr&dIz4a*RD)9780+ zlmGnx|G(bN&5cdq$N&HKGuS>(yr~d5!Ait4#aF@1&sCw<;An;D!8K_r$9x$s95-SN TPPS_SYG?3t^>bP0l+XkKrS~O4 literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/plus_19x20.png b/assets/icons/Infrared/plus_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..709ca0b2d01468addb73f32e8d3386342a2bf804 GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oJ>y_#}Etu zw^I&s9Z=wLp8Ws+i@p!lLWX?Z$#XyOP!6Qq1tu!N-T7XmdKI;Vst0G_--aR2}S literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/plus_hover_19x20.png b/assets/icons/Infrared/plus_hover_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..0aacac81695ba19538899c7a1a7d1adfa641f283 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oM=xM#}Etu zw^I&sF(`02Z~y;4!`XAuE)UO=3*OtG7bS}bGBQm1CFASfz_pw4XBq1%nc!)L?-ReZ z9`JQO#bwr-uzM!EfzZ^eQ;ZT~-rk?%I%liY@9m~Rrwuo6lV05I^R?|zSqjh^22WQ% Jmvv4FO#suXI9C7w literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/red_19x20.png b/assets/icons/Infrared/red_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..c50c5651aa112051328eaa903fc706f980480f65 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oEA?P#}Etu zr&GN78VopG9{y`T)+6X7vO-`n_wLL~hhBfs?(Pr}G&J6J(MsS*1LG2Q;aR_&`?_Wq zoMAt}{l;%cph&XBlR1s5I;R(gzsq$qi2s?Nu=>)rqOgOvuAY(kGI83b`5~L7YI5(C z|IWD@e{*kdu*nYD6>YimlaE_&a{X6!Bl!L|HrFo;zRD^(9s#BEl literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/red_hover_19x20.png b/assets/icons/Infrared/red_hover_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..4e67f9d9fe7b6b020f5e3944e4e9d8a272c9713f GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oH|bz#}Etu zyHf)B4k&Oqzx>PJd$8xI!NC(9-p~5$UTnU^HDhLH2g8XeQsPYt8_X5<#x^J|*Iq5b zKf|lK~+i`MLk(O|AGuW{q6OrDr#8$~Dif$^H6EG_xG&5(ZCKKbLh*2~7ZOSWHU* literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/white_19x20.png b/assets/icons/Infrared/white_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..6144feea58e53918d46927f7fa321d0ca8b29895 GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oE}dX#}Etu zw^MF&9Z=wLzWx9Ii@uJDkF1Vw;{9e2?{d+3R-BE}p)+UhZuDHOe1?&^L!7Jkzhe6% zk#j$oAMhP32=`iTZoek=se$7Ck2&j{BO~AS1isuGy-9TW(!i|}4AZBkem73o_q=jt zPye%kl!#EdsqJ&D-hW$WW!09>aA$71ZU~=yXrNz6v#sei^CGFOUpFo}y&UK+22WQ% Jmvv4FO#r-lQ>y>~ literal 0 HcmV?d00001 diff --git a/assets/icons/Infrared/white_hover_19x20.png b/assets/icons/Infrared/white_hover_19x20.png new file mode 100644 index 0000000000000000000000000000000000000000..2b76bca7c111955b67f0ee2eda7340aecefba65a GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv-!3HGrL)l7z6k~CayA#8@b22Z1oCZ%9#}Etu zw^JPX4k&Q2JpA9k?4Zczr5PfYUjp>p7C-LVprhpEa^#HFl1FX=+HY7=wrdAGHAxly z*i?FQa(e%_KN|{H)~KhiZF^9D`P3VQGEc5H1?{ZR9lVG892(;5&(vC1ng*^5cxt3$ w)S%HWYZqizJn8A2xjWp~vQC|qyfimHgQ@B3HQ&k(^b literal 0 HcmV?d00001 From d2226bd38378e722344c292d7534bcdefae76f38 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 7 Oct 2024 06:09:16 +0300 Subject: [PATCH 124/236] upd changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88fb7680e3..1cbbbbeb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,13 @@ - OFW PR 3918: NFC Some api adjustments (by @RebornedBrain) - OFW PR 3930: NFC Emulation freeze (by @RebornedBrain) - OFW PR 3885: Add API to enforce ISO15693 mode (by @aaronjamt) + - OFW: Plantain parser improvements (by @assasinfil) + - OFW: Moscow social card parser (by @assasinfil) +- Infrared: + - Add LEDs universal remote (DB by @amec0e) + - Update universal remote assets (by @amec0e | PR #813 #816) +* OFW: FuriHal, drivers: rework gauge initialization routine * OFW: New layout for BadUSB (es-LA) -* Infrared: Update universal remote assets (by @amec0e | PR #813) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * OFW PR 3931: Split BadUSB into BadUSB and BadBLE (by @Astrrra) From 41c35cd59ec64296b41a5422ae1170c7dc4edfdb Mon Sep 17 00:00:00 2001 From: Ruslan Nadyrshin <110516632+rnadyrshin@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:27:16 +0400 Subject: [PATCH 125/236] Documentation: update and cleanup (#3934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Developers Docs editing * Logo underline removed The underline has been removed when hovering over the logo. * proofread docs * application -> app in several files --------- Co-authored-by: knrn64 <25254561+knrn64@users.noreply.github.com> Co-authored-by: あく --- ReadMe.md | 6 +- .../examples/example_number_input/ReadMe.md | 14 ++- documentation/AppManifests.md | 78 ++++++++-------- documentation/AppsOnSDCard.md | 42 ++++----- documentation/ExpansionModules.md | 8 +- documentation/FuriCheck.md | 20 ++-- documentation/FuriHalBus.md | 10 +- documentation/FuriHalDebuging.md | 14 +-- documentation/HardwareTargets.md | 12 +-- documentation/KeyCombo.md | 4 +- documentation/OTA.md | 2 +- documentation/UnitTests.md | 14 +-- documentation/UniversalRemotes.md | 4 +- .../Reading logs via the Dev Board.md | 4 +- documentation/doxygen/Doxyfile.cfg | 2 +- documentation/doxygen/app_publishing.dox | 7 ++ documentation/doxygen/applications.dox | 14 +-- documentation/doxygen/dev_board.dox | 6 +- documentation/doxygen/dev_tools.dox | 7 +- documentation/doxygen/examples.dox | 15 +-- documentation/doxygen/expansion_modules.dox | 2 +- documentation/doxygen/file_formats.dox | 1 + documentation/doxygen/header.html | 4 +- documentation/doxygen/index.dox | 28 +++--- documentation/doxygen/js.dox | 21 +++-- documentation/doxygen/misc.dox | 6 +- documentation/doxygen/system.dox | 16 ++-- documentation/fbt.md | 92 +++++++++---------- documentation/file_formats/NfcFileFormats.md | 2 +- .../file_formats/TarHeatshrinkFormat.md | 4 +- documentation/js/js_badusb.md | 2 +- documentation/js/js_data_types.md | 12 +-- documentation/js/js_math.md | 2 +- 33 files changed, 247 insertions(+), 228 deletions(-) create mode 100644 documentation/doxygen/app_publishing.dox diff --git a/ReadMe.md b/ReadMe.md index 15235e6fcf..f16adc4328 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -9,8 +9,8 @@ # Flipper Zero Firmware - [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what Flipper Zero can do. -- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. -- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and anything you want to ask. +- [Flipper Zero Firmware Update](https://flipperzero.one/update). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. +- [User Documentation](https://docs.flipper.net). Learn more about your dolphin: specs, usage guides, and anything you want to ask. - [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). Dive into the Flipper Zero Firmware source code: build system, firmware structure, and more. # Contributing @@ -19,7 +19,7 @@ Our main goal is to build a healthy and sustainable community around Flipper, so ## I need help -The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development, or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). +The best place to search for answers is our [User Documentation](https://docs.flipper.net). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). ## I want to report an issue diff --git a/applications/examples/example_number_input/ReadMe.md b/applications/examples/example_number_input/ReadMe.md index 9d5a0a9e5e..8a221ba087 100644 --- a/applications/examples/example_number_input/ReadMe.md +++ b/applications/examples/example_number_input/ReadMe.md @@ -1,7 +1,13 @@ -# Number Input +# Number Input {#example_number_input} -Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input. +Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need for intense validations after a user input. -Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -. +## Source code -It is also possible to define a header text, shown in this example app with the 3 different input options. \ No newline at end of file +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_number_input). + +## General principle + +Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed within min - max, an additional button is displayed to switch the sign between + and -. + +It is also possible to define a header text, as shown in this example app with the 3 different input options. \ No newline at end of file diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 98a38ffd85..f0f9d63799 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -1,65 +1,65 @@ -# Flipper Application Manifests (.fam) {#app_manifests} +# FAM (Flipper App Manifests) {#app_manifests} All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system. -When building firmware, `fbt` collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](fbt.md) for details on build configurations. +When building firmware, `fbt` collects all app manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](fbt.md) for details on build configurations. -## Application definition +## App definition A firmware component's properties are declared in a Python code snippet, forming a call to the `App()` function with various parameters. -Only two parameters are mandatory: **appid** and **apptype**. Others are optional and may only be meaningful for certain application types. +Only two parameters are mandatory: **appid** and **apptype**. Others are optional and may only be meaningful for certain app types. ### Parameters -- **appid**: string, application ID within the build system. It is used to specify which applications to include in the build configuration and resolve dependencies and conflicts. +- **appid**: string, app ID within the build system. It is used to specify which app to include in the build configuration and resolve dependencies and conflicts. - **apptype**: member of FlipperAppType.\* enumeration. Valid values are: | Enum member | Firmware component type | | ----------- | ------------------------------------------------------------------------------------------- | | SERVICE | System service, created at early startup | -| SYSTEM | Application is not being shown in any menus. It can be started by other apps or from CLI | -| APP | Regular application for the main menu | -| PLUGIN | Application to be built as a part of the firmware and to be placed in the Plugins menu | -| DEBUG | Application only visible in Debug menu with debug mode enabled | +| SYSTEM | App is not being shown in any menus. It can be started by other apps or from CLI | +| APP | Regular app for the main menu | +| PLUGIN | App to be built as a part of the firmware and to be placed in the Plugins menu | +| DEBUG | App only visible in Debug menu with debug mode enabled | | ARCHIVE | One and only Archive app | -| SETTINGS | Application to be placed in the system settings menu | +| SETTINGS | App to be placed in the system settings menu | | STARTUP | Callback function to run at system startup. Does not define a separate app | -| EXTERNAL | Application to be built as `.fap` plugin | -| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | +| EXTERNAL | App to be built as `.fap` plugin | +| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and app bundles | - **name**: name displayed in menus. -- **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. +- **entry_point**: C function to be used as the app's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. - **flags**: internal flags for system apps. Do not use. -- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. **For external applications**: specified definitions are used when building the application itself. -- **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. -- **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, `fbt` will abort the firmware build process. +- **cdefines**: C preprocessor definitions to declare globally for other apps when the current app is included in the active build configuration. **For external apps**: specified definitions are used when building the app itself. +- **requires**: list of app IDs to include in the build configuration when the current app is referenced in the list of apps to build. +- **conflicts**: list of app IDs with which the current app conflicts. If any of them is found in the constructed app list, `fbt` will abort the firmware build process. - **provides**: functionally identical to **_requires_** field. -- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._ +- **stack_size**: stack size in bytes to allocate for an app on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._ - **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware. -- **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ -- **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications. -- **targets**: list of strings and target names with which this application is compatible. If not specified, the application is built for all targets. The default value is `["all"]`. -- **resources**: name of a folder within the application's source folder to be used for packacking SD card resources for this application. They will only be used if application is included in build configuration. The default value is `""`, meaning no resources are packaged. +- **order**: order of an app within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ +- **sdk_headers**: list of C header files from this app's code to include in API definitions for external apps. +- **targets**: list of strings and target names with which this app is compatible. If not specified, the app is built for all targets. The default value is `["all"]`. +- **resources**: name of a folder within the app's source folder to be used for packacking SD card resources for this app. They will only be used if app is included in build configuration. The default value is `""`, meaning no resources are packaged. -#### Parameters for external applications +#### Parameters for external apps The following parameters are used only for [FAPs](./AppsOnSDCard.md): -- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion. -- **fap_version**: string, application version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap. +- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Apps cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion. +- **fap_version**: string, app version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap. - **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file. -- **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. +- **fap_libs**: list of extra libraries to link the app against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. - **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within the apps folder in the file system. -- **fap_description**: string, may be empty. Short application description. -- **fap_author**: string, may be empty. Application's author. -- **fap_weburl**: string, may be empty. Application's homepage. -- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](AppsOnSDCard.md) for details. -- **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. `fbt` will run the specified command for each file in the list. -- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host application's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host application. +- **fap_description**: string, may be empty. Short app description. +- **fap_author**: string, may be empty. App's author. +- **fap_weburl**: string, may be empty. App's homepage. +- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this app. These images will be preprocessed and built alongside the app. See [FAP assets](AppsOnSDCard.md) for details. +- **fap_extbuild**: provides support for parts of app sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. `fbt` will run the specified command for each file in the list. +- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host app's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host app. -Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by `fbt`: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by `fbt`. +Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an app's temporary build folder. For that, you can use pattern expansion by `fbt`: `${FAP_WORK_DIR}` will be replaced with the path to the app's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the app's source folder. You can also use other variables defined internally by `fbt`. Example for building an app from Rust sources: @@ -73,8 +73,8 @@ Example for building an app from Rust sources: ), ``` -- **fap_private_libs**: list of additional libraries distributed as sources alongside the application. These libraries will be built as a part of the application build process. - Library sources must be placed in a subfolder of the `lib` folder within the application's source folder. +- **fap_private_libs**: list of additional libraries distributed as sources alongside the app. These libraries will be built as a part of the app build process. + Library sources must be placed in a subfolder of the `lib` folder within the app's source folder. Each library is defined as a call to the `Lib()` function, accepting the following parameters: - **name**: name of the library's folder. Required. @@ -82,7 +82,7 @@ Example for building an app from Rust sources: - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. - - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the app's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. Example for building an app with a private library: @@ -105,14 +105,14 @@ Example for building an app with a private library: ], ``` -For that snippet, `fbt` will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, `fbt` will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, `fbt` will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. -For the `loclass` library, `fbt` will add `lib/loclass` to the list of the include paths for the application and build all sources in that folder. Also, `fbt` will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. +For that snippet, `fbt` will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, `fbt` will add `lib/mbedtls/include` to the list of include paths for the app and compile only the files specified in the `sources` list. Additionally, `fbt` will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. +For the `loclass` library, `fbt` will add `lib/loclass` to the list of the included paths for the app and build all sources in that folder. Also, `fbt` will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. -Both libraries will be linked with the application. +Both libraries will be linked with the app. ## .fam file contents -The `.fam` file contains one or more application definitions. For example, here's a part of `applications/service/bt/application.fam`: +The `.fam` file contains one or more app definitions. For example, here's a part of `applications/service/bt/application.fam`: ```python App( diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index cb8106fc6c..8bc73b1ab4 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -1,29 +1,29 @@ -# FAP (Flipper Application Package) {#apps_on_sd_card} +# FAP (Flipper App Package) {#apps_on_sd_card} -[fbt](./fbt.md) supports building applications as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in. +[fbt](./fbt.md) supports building apps as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in. FAPs are built with the `faps` target. They can also be deployed to the `dist` folder with the `fap_dist` target. FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). -## How to set up an application to be built as a FAP {#fap-howto} +## How to set up an app to be built as a FAP {#fap-howto} -FAPs are created and developed the same way as internal applications that are part of the firmware. +FAPs are created and developed the same way as internal apps that are part of the firmware. -To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. +To build your app as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in app. Then configure its `application.fam` manifest, and set its `apptype` to `FlipperAppType.EXTERNAL`. See [Flipper App Manifests](AppManifests.md) for more details. -- To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. +- To build your app, run `./fbt fap_{APPID}`, where APPID is your app's ID in its manifest. - To build your app and upload it over USB to run on Flipper, use `./fbt launch APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). - To build an app without uploading it to Flipper, use `./fbt build APPSRC=applications_user/path/to/app`. This command is also available in VSCode configuration as "Build App". - To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets -FAPs can include static and animated images as private assets. They will be automatically compiled alongside application sources and can be referenced the same way as assets from the main firmware. +FAPs can include static and animated images as private assets. They will be automatically compiled alongside app sources and can be referenced the same way as assets from the main firmware. -To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in the `fap_icon_assets` field. See [Application Manifests](AppManifests.md) for more details. +To use that feature, put your images in a subfolder inside your app's folder, then reference that folder in your app's manifest in the `fap_icon_assets` field. See [Flipper App Manifests](AppManifests.md) for more details. -To use these assets in your application, put `#include "{APPID}_icons.h"` in your application's source code, where `{APPID}` is the `appid` value field from your application's manifest. Then you can use all icons from your application's assets the same way as if they were a part of `assets_icons.h` of the main firmware. +To use these assets in your app, put `#include "{APPID}_icons.h"` in your app's source code, where `{APPID}` is the `appid` value field from your app's manifest. Then you can use all icons from your app's assets the same way as if they were a part of `assets_icons.h` of the main firmware. Images and animated icons should follow the same [naming convention](../assets/ReadMe.md) as those from the main firmware. @@ -33,11 +33,11 @@ Images and animated icons should follow the same [naming convention](../assets/R With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc. -If debugging session is active, firmware will trigger a breakpoint after loading a FAP it into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since before loading it debugger cannot know the exact address of the FAP's code. +If debugging session is active, firmware will trigger a breakpoint after loading a FAP into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since the debugger cannot know the exact address of the FAP's code before loading the FAP. ### Setting up debugging environment -The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](./fbt.md#nb) for details. +The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](fbt.md) for details. To debug FAPs, do the following: @@ -45,23 +45,23 @@ To debug FAPs, do the following: 2. Flash it with `./fbt flash` 3. [Build your FAP](#fap-howto) and run it on Flipper -After that, you can attach with `./fbt debug` or VS Code and use all debug features. +After that, you can attach the debugger to the target MCU with `./fbt debug` or VS Code and use all debug features. -It is **important** that firmware and application build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. +It is **important** that firmware and app build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. -## How Flipper runs an application from an SD card +## How Flipper runs an app from an SD card -Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader application responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location. +Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location. -Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time. +Since the FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time. -Applications are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the application's major API version matches the firmware's major API version. +Apps are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the app's major API version matches the firmware's major API version. -The App Loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. +The App Loader allocates memory for the app and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the app. ## API versioning {#api-versioning} -Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory. +Not all parts of firmware are available for external apps. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory. `fbt` uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added. @@ -78,6 +78,6 @@ API versioning is mostly automated by `fbt`. When rebuilding the firmware, `fbt` ### Symbol table {#symbol-table} -The symbol table is a list of symbols exported by firmware and available for external applications. It is generated by `fbt` from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. +The symbol table is a list of symbols exported by firmware and available for external apps. It is generated by `fbt` from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` app. -`fbt` also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The application won't be able to run on the device until all required symbols are provided in the symbol table. +`fbt` also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The app won't be able to run on the device until all required symbols are provided in the symbol table. diff --git a/documentation/ExpansionModules.md b/documentation/ExpansionModules.md index fd9703adcc..2fbdb738f8 100644 --- a/documentation/ExpansionModules.md +++ b/documentation/ExpansionModules.md @@ -29,7 +29,7 @@ Depending on the UART selected for communication, the following pins area availa ## Frame structure -Each frame consists of a header (1 byte), contents (size depends of frame type) and checksum (1 byte) fields: +Each frame consists of a header (1 byte), contents (size depends on frame type) and checksum (1 byte) fields: | Header (1 byte) | Contents (0 or more bytes) | Checksum (1 byte) | |-----------------|----------------------------|-------------------| @@ -79,7 +79,7 @@ CONTROL frames are used to control various aspects of the communication and enab |-----------------|-------------------|-------------------| | 0x04 | Command | XOR checksum | -The `Command` field SHALL have one of the followind values: +The `Command` field SHALL have one of the following values: | Command | Meaning | Note | |---------|--------------------------|:----:| @@ -96,7 +96,7 @@ Notes: ### Data frame -DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is curretly open, all received bytes are forwarded to it. +DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is currently open, all received bytes are forwarded to it. | Header (1 byte) | Contents (1 to 65 byte(s)) | Checksum (1 byte) | |-----------------|----------------------------|-------------------| @@ -110,7 +110,7 @@ The `Data` field SHALL have the following structure: ## Communication flow -In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to `Settings -> Expansion Modules` and selecting the required `Listen UART` or programmatically by calling `expansion_enable()`. Likewise, disabling this feature via the same GUI or by calling `expansion_disable()` will result in ceasing all communications and not being able to detect any connected modules. +In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to `Settings → Expansion Modules` and selecting the required `Listen UART` or programmatically by calling `expansion_enable()`. Likewise, disabling this feature via the same GUI or by calling `expansion_disable()` will result in ceasing all communications and not being able to detect any connected modules. The communication is always initiated by the module by the means of shortly pulling the RX pin down. The host SHALL respond with a HEARTBEAT frame indicating that it is ready to receive requests. The module then MUST issue a BAUDRATE request within Tto. Failure to do so will result in the host dropping the connection and returning to its initial state. diff --git a/documentation/FuriCheck.md b/documentation/FuriCheck.md index 77a44ca84b..ead964af97 100644 --- a/documentation/FuriCheck.md +++ b/documentation/FuriCheck.md @@ -1,13 +1,13 @@ # Run time checks and forced system crash {#furi_check} The best way to protect system integrity is to reduce amount cases that we must handle and crash the system as early as possible. -For that purpose we have bunch of helpers located in Furi Core check.h. +For that purpose, we have a bunch of helpers located in Furi Core `check.h`. ## Couple notes before start -- Definition of Crash - log event, save crash information in RTC and reboot the system. -- Definition of Halt - log event, stall the system. -- Debug and production builds behaves differently: debug build will never reset system in order to preserve state for debugging. +- Definition of Crash — log event, save crash information in RTC and reboot the system. +- Definition of Halt — log event, stall the system. +- Debug and production builds behave differently: debug build will never reset system in order to preserve state for debugging. - If you have debugger connected we will stop before reboot automatically. - All helpers accept optional MESSAGE_CSTR: it can be in RAM or Flash memory, but only messages from Flash will be shown after system reboot. - MESSAGE_CSTR can be NULL, but macros magic already doing it for you, so just don't. @@ -16,10 +16,10 @@ For that purpose we have bunch of helpers located in Furi Core check.h. Assert condition in development environment and crash the system if CONDITION is false. -- Should be used at development stage in apps and services -- Keep in mind that release never contains this check -- Keep in mind that libraries never contains this check by default, use `LIB_DEBUG=1` if you need it -- Avoid putting function calls into CONDITION, since it may be omitted in some builds +- Should be used at development stage in apps and services. +- Keep in mind that release never contains this check. +- Keep in mind that libraries never contain this check by default, use `LIB_DEBUG=1` if you need it. +- Avoid putting function calls into CONDITION, since it may be omitted in some builds. ## `furi_check(CONDITION)` or `furi_check(CONDITION, MESSAGE_CSTR)` @@ -31,10 +31,10 @@ Always assert condition and crash the system if CONDITION is false. Crash the system. -- Use it to crash the system. For example: if abnormal condition detected. +- Use it to crash the system. For example, if an abnormal condition is detected. ## `furi_halt()` or `furi_halt(MESSAGE_CSTR)` Halt the system. -- We use it internally to shutdown flipper if poweroff is not possible. +- We use it internally to shutdown Flipper if poweroff is not possible. diff --git a/documentation/FuriHalBus.md b/documentation/FuriHalBus.md index 12c5a70ece..5e7bb5f402 100644 --- a/documentation/FuriHalBus.md +++ b/documentation/FuriHalBus.md @@ -5,7 +5,7 @@ On system startup, most of the peripheral devices are under reset and not clocked by default. This is done to reduce power consumption and to guarantee that the device will always be in the same state before use. Some crucial peripherals are enabled right away by the system, others must be explicitly enabled by the user code. -**NOTE:** Here and afterwards the word *"system"* refers to any code belonging to the operating system, hardware drivers or built-in applications. +**NOTE:** Here and afterwards, the word *"system"* refers to any code belonging to the operating system, hardware drivers or built-in apps. To **ENABLE** a peripheral, call `furi_hal_bus_enable()`. At the time of the call, the peripheral in question MUST be disabled, otherwise a crash will occur to indicate improper use. This means that any given peripheral cannot be enabled twice or more without disabling it first. @@ -24,7 +24,7 @@ Built-in peripherals are divided into three categories: Below is the list of peripherals that are enabled by the system. The user code must NEVER attempt to disable them. If a corresponding API is provided, the user code must employ it in order to access the peripheral. -*Table 1* - Peripherals enabled by the system +*Table 1* — Peripherals enabled by the system | Peripheral | Enabled at | | :-----------: | :-----------------------: | @@ -49,7 +49,7 @@ Below is the list of peripherals that are enabled and disabled by the system. Th When not using the API, these peripherals MUST be enabled by the user code and then disabled when not needed anymore. -*Table 2* - Peripherals enabled and disabled by the system +*Table 2* — Peripherals enabled and disabled by the system | Peripheral | API header file | | :-----------: | :-------------------: | @@ -69,7 +69,7 @@ Below is the list of peripherals that are not enabled by default and MUST be ena Note that some of these peripherals may also be used by the system to implement its certain features. The system will take over any given peripheral only when the respective feature is in use. -*Table 3* - Peripherals enabled and disabled by user +*Table 3* — Peripherals enabled and disabled by user | Peripheral | System | Purpose | | :-----------: | :-------: | ------------------------------------- | @@ -93,7 +93,7 @@ The DMA1,2 peripherals are a special case in that they have multiple independent Below is the list of DMA channels and their usage by the system. -*Table 4* - DMA channels +*Table 4* — DMA channels | DMA | Channel | System | Purpose | | :---: | :-------: | :-------: | ------------------------- | diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebuging.md index 5104a99982..bc7d532d21 100644 --- a/documentation/FuriHalDebuging.md +++ b/documentation/FuriHalDebuging.md @@ -1,7 +1,7 @@ # Furi HAL Debugging {#furi_hal_debugging} -Some Furi subsystems got additional debugging features that can be enabled by adding additional defines to firmware compilation. -Usually they are used for low level tracing and profiling or signal redirection/duplication. +Some Furi subsystems have additional debugging features that can be enabled by adding additional defines to firmware compilation. +Usually, they are used for low level tracing and profiling or signal redirection/duplication. ## FuriHalOs @@ -10,9 +10,9 @@ Usually they are used for low level tracing and profiling or signal redirection/ There are 3 signals that will be exposed to external GPIO pins: -- `AWAKE` - `PA7` - High when system is busy with computations, low when sleeping. Can be used to track transitions to sleep mode. -- `TICK` - `PA6` - Flipped on system tick, only flips when no tick suppression in progress. Can be used to track tick skew and abnormal task scheduling. -- `SECOND` - `PA4` - Flipped each second. Can be used for tracing RT issue: time flow disturbance means system doesn't conforms Hard RT. +- `AWAKE` — `PA7` — High when system is busy with computations, low when sleeping. Can be used to track transitions to sleep mode. +- `TICK` — `PA6` — Flipped on system tick, only flips when no tick suppression in progress. Can be used to track tick skew and abnormal task scheduling. +- `SECOND` — `PA4` — Flipped each second. Can be used for tracing RT issue: time flow disturbance means system doesn't conform Hard RT. @@ -22,8 +22,8 @@ There are 3 signals that will be exposed to external GPIO pins: There are 2 signals that will be exposed to external GPIO pins: -- `WFI` - `PB2` - Light sleep (wait for interrupt) used. Basically this is lightest and most non-breaking things power save mode. All function and debug should work correctly in this mode. -- `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. +- `WFI` — `PB2` — Light sleep (wait for interrupt) used. Basically, this is the lightest and most non-breaking things power save mode. All functions and debug should work correctly in this mode. +- `STOP` — `PC3` — STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. ## FuriHalSD diff --git a/documentation/HardwareTargets.md b/documentation/HardwareTargets.md index 9c36088eac..e35ee7991b 100644 --- a/documentation/HardwareTargets.md +++ b/documentation/HardwareTargets.md @@ -25,20 +25,20 @@ A target definition file, `target.json`, is a JSON file that can contain the fol * `excluded_modules`: list of strings specifying fbt library (module) names to exclude from being used to configure build environment. -## Applications & Hardware +## Apps & Hardware -Not all applications are available on different hardware targets. +Not all apps are available on different hardware targets. -* For applications built into the firmware, you have to specify a compatible application set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md) for details on build configurations. +* For apps built into the firmware, you have to specify a compatible app set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md) for details on build configurations. -* For applications built as external .faps, you have to explicitly specify compatible targets in application's manifest, `application.fam`. For example, to limit application to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets. +* For apps built as external FAPs, you have to explicitly specify compatible targets in the app's manifest, `application.fam`. For example, to limit the app to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets. -For details on application manifests, check out [their docs page](./AppManifests.md). +For details on app manifests, check out [their docs page](./AppManifests.md). ## Building Firmware for a Specific Target -You have to specify TARGET_HW (and, optionally, FIRMWARE_APP_SET) for `fbt` to build firmware for non-default target. For example, building and flashing debug firmware for f18 can be done with +You have to specify TARGET_HW (and, optionally, FIRMWARE_APP_SET) for `fbt` to build firmware for a non-default target. For example, building and flashing debug firmware for f18 can be done with ./fbt TARGET_HW=18 flash_usb_full diff --git a/documentation/KeyCombo.md b/documentation/KeyCombo.md index e3c5e00043..09bbb1ee0b 100644 --- a/documentation/KeyCombo.md +++ b/documentation/KeyCombo.md @@ -10,7 +10,7 @@ There are times when your Flipper feels blue and doesn't respond to any of your - Release `LEFT` and `BACK` This combo performs a hardware reset by pulling the MCU reset line down. -Main components involved: Keys -> DD8(NC7SZ32M5X, OR-gate) -> DD1(STM32WB55, MCU). +Main components involved: Keys → DD8(NC7SZ32M5X, OR-gate) → DD1(STM32WB55, MCU). It won't work only in one case: @@ -26,7 +26,7 @@ It won't work only in one case: - Release the `BACK` key This combo performs a reset by switching SYS power line off and then on. -Main components involved: Keys -> DD6(bq25896, charger). +Main components involved: Keys → DD6(bq25896, charger). It won't work only in one case: diff --git a/documentation/OTA.md b/documentation/OTA.md index 9783a70477..1499ab74b6 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -2,7 +2,7 @@ ## Executing code from RAM -In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash. +In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. The system image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash. We leverage that boot mode to perform OTA firmware updates, including operations on a radio stack running on the second MCU core. diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index b77cd56c6e..5c80e763d4 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -5,7 +5,7 @@ Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they are correct. They are crucial for writing robust, bug-free code. -Flipper Zero firmware includes a separate application called [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests). +Flipper Zero firmware includes a separate app called [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests). It is run directly on Flipper devices in order to employ their hardware features and rule out any platform-related differences. When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features. @@ -28,13 +28,13 @@ See [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/blob/d #### Entry point -The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/test_index.c) source file. +The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) app. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/test_index.c) source file. #### Test assets Some unit tests require external data in order to function. These files (commonly called assets) reside in the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). -### Application-specific +### App-specific #### Infrared @@ -50,9 +50,9 @@ To add unit tests for your protocol, follow these steps: Each unit test has three sections: -1. `decoder` - takes in a raw signal and outputs decoded messages. -2. `encoder` - takes in decoded messages and outputs a raw signal. -3. `encoder_decoder` - takes in decoded messages, turns them into a raw signal, and then decodes again. +1. `decoder` — takes in a raw signal and outputs decoded messages. +2. `encoder` — takes in decoded messages and outputs a raw signal. +3. `encoder_decoder` — takes in decoded messages, turns them into a raw signal, and then decodes again. Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions. Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder, these two are switched. @@ -61,4 +61,4 @@ Decoded data is represented in arrays (since a single raw signal may be decoded ##### Getting raw signals -Recording raw IR signals are possible using the Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. +Recording raw IR signals is possible using Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards the Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 360d8a0abb..65f64526fb 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -41,11 +41,11 @@ When the user presses a button, a whole set of parameters is transmitted to the In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, and `Heat_lo`. Each signal (except `Off`) is recorded using the following algorithm: -1. Get the remote and press the **Power Button** so that the display shows that A/C is ON. +1. Get the remote and press the **POWER** button so that the display shows that A/C is ON. 2. Set the A/C to the corresponding mode (see table below), leaving other parameters such as fan speed or vane on **AUTO** (if applicable). 3. Press the **POWER** button to switch the A/C off. 4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise. -5. Point the remote to Flipper's IR receiver as directed and press **POWER** button once again. +5. Point the remote to Flipper's IR receiver as directed and press the **POWER** button once again. 6. Save the resulting signal under the specified name. 7. Repeat steps 2-6 for each signal from the table below. diff --git a/documentation/devboard/Reading logs via the Dev Board.md b/documentation/devboard/Reading logs via the Dev Board.md index e9fc0e2ca8..c2daf83ace 100644 --- a/documentation/devboard/Reading logs via the Dev Board.md +++ b/documentation/devboard/Reading logs via the Dev Board.md @@ -8,7 +8,7 @@ The Developer Board allows you to read Flipper Zero logs via UART. Unlike readin ## Setting the log level -Depending on your needs, you can set the log level by going to **Main Menu -> Settings -> Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). +Depending on your needs, you can set the log level by going to **Main Menu → Settings → Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). ![You can manually set the preferred log level](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/INzQMw8QUsG9PXi30WFS0_monosnap-miro-2023-07-11-13-29-47.jpg) @@ -145,7 +145,7 @@ On Windows, do the following: 3. Connect the developer board to your computer using a USB Type-C cable. ![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) -4. Find the serial port that the developer board is connected to by going to **Device Manager -> Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board. +4. Find the serial port that the developer board is connected to by going to **Device Manager → Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board. ![Find the serial port in your Device Manager](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KKLQJK1lvqmI5iab3d__C_image.png) 5. Run the PuTTY application and select **Serial** as the connection type. diff --git a/documentation/doxygen/Doxyfile.cfg b/documentation/doxygen/Doxyfile.cfg index a7838163b0..90f36415f1 100644 --- a/documentation/doxygen/Doxyfile.cfg +++ b/documentation/doxygen/Doxyfile.cfg @@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "Flipper Zero Firmware" +PROJECT_NAME = "Flipper Developer Docs" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version diff --git a/documentation/doxygen/app_publishing.dox b/documentation/doxygen/app_publishing.dox new file mode 100644 index 0000000000..747891221e --- /dev/null +++ b/documentation/doxygen/app_publishing.dox @@ -0,0 +1,7 @@ +/** +@page app_publishing Publishing to the Apps Catalog + +You can publish your app in the Flipper Apps Catalog. Users will be able to download your app and install it on their Flipper Zero via [mobile apps](https://flpr.app/) and [Flipper Lab](https://lab.flipper.net/apps). Check out the documentation below: + +- [Apps Catalog: Contribution Guide](https://github.com/flipperdevices/flipper-application-catalog/blob/main/documentation/Contributing.md) — How to publish and update your app in the Apps Catalog +*/ diff --git a/documentation/doxygen/applications.dox b/documentation/doxygen/applications.dox index ad0dfba8d8..7c21a6e8d9 100644 --- a/documentation/doxygen/applications.dox +++ b/documentation/doxygen/applications.dox @@ -1,12 +1,12 @@ /** -@page applications Application Programming +@page applications App Development -Flipper Zero features full support for custom applications which (usually) do not require any changes to the firmware. +Flipper Zero features full support for custom apps which (usually) do not require any changes to the firmware. -For easy application development, a software tool called [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) is available. +For easy app development, a software tool called [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) is available. -- @subpage vscode - Flipper Zero integration for VS Code -- @subpage apps_on_sd_card - Creating apps that can be dynamically loaded from the SD card -- @subpage app_manifests - How applications announce themselves to the system -- @subpage app_examples - Various application examples, complete with the source code +- @subpage apps_on_sd_card — Creating apps that can be dynamically loaded from the SD card +- @subpage app_manifests — How apps announce themselves to the system +- @subpage app_examples — Various app examples, complete with the source code +- @subpage app_publishing — Learn how to publish and update your app in the Apps Catalog */ diff --git a/documentation/doxygen/dev_board.dox b/documentation/doxygen/dev_board.dox index f9363ed069..6caa44c705 100644 --- a/documentation/doxygen/dev_board.dox +++ b/documentation/doxygen/dev_board.dox @@ -3,8 +3,8 @@ [ESP32-based development board](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/wifi-devboard). -- @subpage dev_board_get_started - Quick start for new users -- @subpage dev_board_reading_logs - Find out what is currently happening on the system -- @subpage dev_board_fw_update - Keep the developer board up to date +- @subpage dev_board_get_started — Quick start for new users +- @subpage dev_board_reading_logs — Find out what is currently happening on the system +- @subpage dev_board_fw_update — Keep the developer board up to date */ diff --git a/documentation/doxygen/dev_tools.dox b/documentation/doxygen/dev_tools.dox index bd7a5c704f..e3c589face 100644 --- a/documentation/doxygen/dev_tools.dox +++ b/documentation/doxygen/dev_tools.dox @@ -3,7 +3,8 @@ Hardware and software tools for all kinds of programming. -- @subpage fbt - Official build and deployment tool for Flipper Zero -- @subpage dev_board - ESP32-based development board -- @subpage ota_updates - Standalone firmware self-update mechanism +- @subpage fbt — Official build and deployment tool for Flipper Zero +- @subpage vscode — Flipper Zero integration for VS Code +- @subpage dev_board — ESP32-based development board +- @subpage ota_updates — Standalone firmware self-update mechanism */ diff --git a/documentation/doxygen/examples.dox b/documentation/doxygen/examples.dox index 9743549a2d..43039d2f70 100644 --- a/documentation/doxygen/examples.dox +++ b/documentation/doxygen/examples.dox @@ -1,10 +1,13 @@ /** -@page app_examples Application Examples +@page app_examples App Examples -A collection of examples covering various aspects of application programming for Flipper Zero. +A collection of examples covering various aspects of app development for Flipper Zero. -- @subpage example_app_images - Using images and icons in an application -- @subpage example_app_assets - Using application-specific asset folders -- @subpage example_app_data - Using application-specific data folders -- @subpage example_thermo - Reading data from a 1-Wire thermometer +- @subpage example_number_input — Using a simple keyboard that limits user inputs to a full number (integer) +- @subpage example_app_images — Using images and icons in an app +- @subpage example_app_assets — Using app-specific asset folders +- @subpage example_app_data — Using app-specific data folders +- @subpage example_thermo — Reading data from a 1-Wire thermometer + +You can find more app examples in the [repository on GitHub](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples). */ diff --git a/documentation/doxygen/expansion_modules.dox b/documentation/doxygen/expansion_modules.dox index c38bb2923f..5e9731b021 100644 --- a/documentation/doxygen/expansion_modules.dox +++ b/documentation/doxygen/expansion_modules.dox @@ -3,6 +3,6 @@ Expansion modules are special pieces of hardware designed to interface with Flipper's GPIO connector, such as the [Video Game Module](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/video-game-module-for-flipper-zero). -- @subpage expansion_protocol - Transport protocol for smart expansion modules +- @subpage expansion_protocol — Transport protocol for smart expansion modules */ diff --git a/documentation/doxygen/file_formats.dox b/documentation/doxygen/file_formats.dox index 47c2362cf7..2ec780d010 100644 --- a/documentation/doxygen/file_formats.dox +++ b/documentation/doxygen/file_formats.dox @@ -9,5 +9,6 @@ Descriptions of various file formats used in Flipper Zero, grouped by applicatio - @subpage lfrfid_file_format - @subpage nfc_file_format - @subpage subghz_file_format +- @subpage heatshrink_file_format */ diff --git a/documentation/doxygen/header.html b/documentation/doxygen/header.html index cd3ea49e75..5cc0aba38f 100644 --- a/documentation/doxygen/header.html +++ b/documentation/doxygen/header.html @@ -48,11 +48,11 @@ - Logo + Logo -
$projectname $projectnumber +
$projectname $projectnumber
$projectbrief
diff --git a/documentation/doxygen/index.dox b/documentation/doxygen/index.dox index 78055caad0..7bd9024a11 100644 --- a/documentation/doxygen/index.dox +++ b/documentation/doxygen/index.dox @@ -1,26 +1,26 @@ /** @mainpage Overview -Welcome to the Flipper Zero Firmware Developer Documentation! +Welcome to the Flipper Developer Documentation! -This documentation is intended for developers who want to modify the firmware of the Flipper Zero. +This documentation is intended for developers interested in modifying the Flipper Zero firmware, creating Apps and JavaScript programs, or working on external hardware modules for the device. -If you are looking for the user manual, please visit the [User Documentation](https://docs.flipperzero.one/) instead. +If you are looking for the user manual, please visit the [User Documentation](https://docs.flipper.net/) instead. -The documentation is divided into several sections, with all of them accessible from the sidebar on the left: +The documentation is divided into several sections. All of them are accessible from the sidebar on the left: -- @ref applications - Writing applications for Flipper Zero -- @ref system - Understanding the firmware's internals -- @ref file_formats - Saving and loading data to and from files -- @ref dev_tools - Hardware and software tools for all kinds of programming -- @ref expansion - Additional modules to expand Flipper's consciousness -- @ref misc - Various useful pieces of information -- @ref js - JS-based scripting engine documentation +- @ref dev_tools — Hardware and software tools for all kinds of programming +- @ref system — Understanding the firmware's internals +- @ref applications — Developing apps for Flipper Zero +- @ref js — JS-based scripting engine +- @ref expansion — Additional modules to expand Flipper's consciousness +- @ref file_formats — Saving and loading data to and from files +- @ref misc — Various useful pieces of information -Aside from the manually-written documentation files, there's also a few automatically-generated ones at the bottom of the sidebar: +These sections are all manually written. There are also a few automatically generated ones at the bottom of the sidebar: -- [Data Structures](annotated.html) - Every data structure in a list -- [Files](files.html) - Source file tree with easy navigation +- [Data Structures](annotated.html) — Every data structure in a list +- [Files](files.html) — Source file tree with easy navigation These are generated from the source code and are useful for quickly finding the source code or API documentation for a particular function or data structure. */ diff --git a/documentation/doxygen/js.dox b/documentation/doxygen/js.dox index f4faf668fe..33ac078d92 100644 --- a/documentation/doxygen/js.dox +++ b/documentation/doxygen/js.dox @@ -1,21 +1,22 @@ /** @page js JavaScript -This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library +This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library. - [Brief mJS description](https://github.com/cesanta/mjs/blob/master/README.md) - @subpage js_data_types - @subpage js_builtin -JavaScript Modules -JS modules use the Flipper app plugin system. Each module is compiled into a .fal library file and is located on a microSD card. Here is a list of implemented modules: +## JavaScript modules -- @subpage js_badusb - BadUSB module -- @subpage js_serial - Serial module -- @subpage js_math - Math module -- @subpage js_dialog - Dialog module -- @subpage js_submenu - Submenu module -- @subpage js_textbox - Textbox module -- @subpage js_notification - Notifications module +JS modules use the Flipper app plugin system. Each module is compiled into a `.fal` library file and is located on a microSD card. Here is a list of implemented modules: + +- @subpage js_badusb — BadUSB module +- @subpage js_serial — Serial module +- @subpage js_math — Math module +- @subpage js_dialog — Dialog module +- @subpage js_submenu — Submenu module +- @subpage js_textbox — Textbox module +- @subpage js_notification — Notifications module */ diff --git a/documentation/doxygen/misc.dox b/documentation/doxygen/misc.dox index 0ef232ba24..f49a9524ab 100644 --- a/documentation/doxygen/misc.dox +++ b/documentation/doxygen/misc.dox @@ -3,7 +3,7 @@ Various pieces of information that do not fall into other categories. -- @subpage lfrfid_raw - Collecting raw data from LFRFID tags -- @subpage key_combos - Different key combination shortcuts for Flipper Zero -- @subpage universal_remotes - Creating and improving IR universal remote libraries +- @subpage lfrfid_raw — Collecting raw data from LF RFID tags +- @subpage key_combos — Different key combination shortcuts for Flipper Zero +- @subpage universal_remotes — Creating and improving IR universal remote libraries */ diff --git a/documentation/doxygen/system.dox b/documentation/doxygen/system.dox index 328717ea21..d7e42ed4fd 100644 --- a/documentation/doxygen/system.dox +++ b/documentation/doxygen/system.dox @@ -1,13 +1,13 @@ /** @page system System Programming -Lower level aspects of software development for Flipper Zero. +Lower-level aspects of software development for Flipper Zero. -- @subpage unit_tests - Automated testing, a crucial part of the development process -- @subpage furi_check - Hard checks for exceptional situations -- @subpage furi_hal_bus - Access the on-chip peripherals in a safe way -- @subpage furi_hal_debugging - Low level debugging features -- @subpage hardware_targets - Support for different hardware platforms -- @subpage firmware_assets - Various files required for building the firmware -- @subpage dolphin_assets - Animations for the Dolphin game +- @subpage unit_tests — Automated testing, a crucial part of the development process +- @subpage furi_check — Hard checks for exceptional situations +- @subpage furi_hal_bus — Access the on-chip peripherals in a safe way +- @subpage furi_hal_debugging — Low-level debugging features +- @subpage hardware_targets — Support for different hardware platforms +- @subpage firmware_assets — Various files required for building the firmware +- @subpage dolphin_assets — Animations for the Dolphin game */ diff --git a/documentation/fbt.md b/documentation/fbt.md index fee003abb0..59f6aa154e 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -3,7 +3,7 @@ FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. -If you don't need all features of `fbt` - like building the whole firmware - and only want to build and debug a single application, you can use [ufbt](https://pypi.org/project/ufbt/). +If you don't need all features of `fbt` — like building the whole firmware — and only want to build and debug a single app, you can use [ufbt](https://pypi.org/project/ufbt/). ## Environment @@ -40,7 +40,7 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio `fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder (it is used for code completion support in IDEs). -`build/latest` symlink & compilation database are only updated upon *firmware build targets* - that is, when you're re-building the firmware itself. Running other tasks, like firmware flashing or building update bundles *for a different debug/release configuration or hardware target*, does not update `built/latest` dir to point to that configuration. +`build/latest` symlink & compilation database are only updated upon *firmware build targets* — that is, when you're re-building the firmware itself. Running other tasks, like firmware flashing or building update bundles *for a different debug/release configuration or hardware target*, does not update `built/latest` dir to point to that configuration. ## VSCode integration @@ -51,7 +51,7 @@ To use language servers other than the default VS Code C/C++ language server, us - On the first start, you'll be prompted to install recommended plugins. We highly recommend installing them for the best development experience. _You can find a list of them in `.vscode/extensions.json`._ - Basic build tasks are invoked in the Ctrl+Shift+B menu. - Debugging requires a supported probe. That includes: - - Wi-Fi devboard with stock firmware (blackmagic). + - Wi-Fi Devboard with stock firmware (blackmagic). - ST-Link and compatible devices. - J-Link for flashing and debugging (in VSCode only). _Note that J-Link tools are not included with our toolchain and you have to [download](https://www.segger.com/downloads/jlink/) them yourself and put them on your system's PATH._ - Without a supported probe, you can install firmware on Flipper using the USB installation method. @@ -62,70 +62,70 @@ To use language servers other than the default VS Code C/C++ language server, us ### High-level (what you most likely need) -- `fw_dist` - build & publish firmware to the `dist` folder. This is a default target when no others are specified. -- `fap_dist` - build external plugins & publish to the `dist` folder. -- `updater_package`, `updater_minpackage` - build a self-update package. The minimal version only includes the firmware's DFU file; the full version also includes a radio stack & resources for the SD card. -- `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper. -- `flash` - flash the attached device over SWD interface with supported probes. Probe is detected automatically; you can override it with `SWD_TRANSPORT=...` variable. If multiple probes are attached, you can specify the serial number of the probe to use with `SWD_TRANSPORT_SERIAL=...`. -- `flash_usb`, `flash_usb_full` - build, upload and install the update package to the device over USB. See details on `updater_package` and `updater_minpackage`. -- `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded. -- `debug_other`, `debug_other_blackmagic` - attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. -- `updater_debug` - attach GDB with the updater's `.elf` loaded. -- `devboard_flash` - Update WiFi dev board. Supports `ARGS="..."` to pass extra arguments to the update script, e.g. `ARGS="-c dev"`. -- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board). -- `openocd` - just start OpenOCD. You can pass extra arguments with `ARGS="..."`. -- `get_blackmagic` - output the blackmagic address in the GDB remote format. Useful for IDE integration. -- `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `SWD_TRANSPORT_SERIAL=...`. -- `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. Supports `ARGS="..."` to pass extra arguments to clang-format. -- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. Supports `ARGS="..."` to pass extra arguments to black. -- `lint_img`, `format_img` - check the image assets for errors and format them. Enforces color depth and strips metadata. -- `lint_all`, `format_all` - run all linters and formatters. -- `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`. -- `doxygen` - generate Doxygen documentation for the firmware. `doxy` target also opens web browser to view the generated documentation. -- `cli` - start a Flipper CLI session over USB. +- `fw_dist` — build & publish firmware to the `dist` folder. This is a default target when no others are specified. +- `fap_dist` — build external plugins & publish to the `dist` folder. +- `updater_package`, `updater_minpackage` — build a self-update package. The minimal version only includes the firmware's DFU file; the full version also includes a radio stack & resources for the SD card. +- `copro_dist` — bundle Core2 FUS+stack binaries for qFlipper. +- `flash` — flash the attached device over SWD interface with supported probes. Probe is detected automatically; you can override it with `SWD_TRANSPORT=...` variable. If multiple probes are attached, you can specify the serial number of the probe to use with `SWD_TRANSPORT_SERIAL=...`. +- `flash_usb`, `flash_usb_full` — build, upload and install the update package to the device over USB. See details on `updater_package` and `updater_minpackage`. +- `debug` — build and flash firmware, then attach with gdb with firmware's .elf loaded. +- `debug_other`, `debug_other_blackmagic` — attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. +- `updater_debug` — attach GDB with the updater's `.elf` loaded. +- `devboard_flash` — Update WiFi dev board. Supports `ARGS="..."` to pass extra arguments to the update script, e.g. `ARGS="-c dev"`. +- `blackmagic` — debug firmware with Blackmagic probe (WiFi dev board). +- `openocd` — just start OpenOCD. You can pass extra arguments with `ARGS="..."`. +- `get_blackmagic` — output the blackmagic address in the GDB remote format. Useful for IDE integration. +- `get_stlink` — output serial numbers for attached STLink probes. Used for specifying an adapter with `SWD_TRANSPORT_SERIAL=...`. +- `lint`, `format` — run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. Supports `ARGS="..."` to pass extra arguments to clang-format. +- `lint_py`, `format_py` — run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & app manifests. Supports `ARGS="..."` to pass extra arguments to black. +- `lint_img`, `format_img` — check the image assets for errors and format them. Enforces color depth and strips metadata. +- `lint_all`, `format_all` — run all linters and formatters. +- `firmware_pvs` — generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`. +- `doxygen` — generate Doxygen documentation for the firmware. `doxy` target also opens web browser to view the generated documentation. +- `cli` — start a Flipper CLI session over USB. ### Firmware targets -- `faps` - build all external & plugin apps as [`.faps`](AppsOnSDCard.md). +- `faps` — build all external & plugin apps as [`.faps`](AppsOnSDCard.md). - `fbt` also defines per-app targets. For example, for an app with `appid=snake_game` target names are: - - `fap_snake_game`, etc. - build single app as `.fap` by its application ID. - - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build. - - `fap_snake_game_list`, etc - generate source + assembler listing for app's `.fap`. -- `flash`, `firmware_flash` - flash the current version to the attached device over SWD. -- `jflash` - flash the current version to the attached device with JFlash using a J-Link probe. The JFlash executable must be on your `$PATH`. -- `firmware_all`, `updater_all` - build a basic set of binaries. -- `firmware_list`, `updater_list` - generate source + assembler listing. -- `firmware_cdb`, `updater_cdb` - generate a `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. + - `fap_snake_game`, etc. — build single app as `.fap` by its app ID. + - Check out [--extra-ext-apps](#command-line-parameters) for force adding extra apps to external build. + - `fap_snake_game_list`, etc — generate source + assembler listing for app's `.fap`. +- `flash`, `firmware_flash` — flash the current version to the attached device over SWD. +- `jflash` — flash the current version to the attached device with JFlash using a J-Link probe. The JFlash executable must be on your `$PATH`. +- `firmware_all`, `updater_all` — build a basic set of binaries. +- `firmware_list`, `updater_list` — generate source + assembler listing. +- `firmware_cdb`, `updater_cdb` — generate a `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. ### Assets -- `resources` - build resources and their manifest files - - `dolphin_ext` - process dolphin animations for the SD card -- `icons` - generate `.c+.h` for icons from PNG assets -- `proto` - generate `.pb.c+.pb.h` for `.proto` sources -- `proto_ver` - generate `.h` with a protobuf version -- `dolphin_internal`, `dolphin_blocking` - generate `.c+.h` for corresponding dolphin assets +- `resources` — build resources and their manifest files + - `dolphin_ext` — process dolphin animations for the SD card +- `icons` — generate `.c+.h` for icons from PNG assets +- `proto` — generate `.pb.c+.pb.h` for `.proto` sources +- `proto_ver` — generate `.h` with a protobuf version +- `dolphin_internal`, `dolphin_blocking` — generate `.c+.h` for corresponding dolphin assets ## Command-line parameters {#command-line-parameters} -- `--options optionfile.py` (default value `fbt_options.py`) - load a file with multiple configuration values -- `--extra-int-apps=app1,app2,appN` - force listed apps to be built as internal with the `firmware` target -- `--extra-ext-apps=app1,app2,appN` - force listed apps to be built as external with the `firmware_extapps` target -- `--extra-define=A --extra-define=B=C ` - extra global defines that will be passed to the C/C++ compiler, can be specified multiple times -- `--proxy-env=VAR1,VAR2` - additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes the execution environment and doesn't forward all inherited environment variables. You can find the list of variables that are always forwarded in the `environ.scons` file. +- `--options optionfile.py` (default value `fbt_options.py`) — load a file with multiple configuration values +- `--extra-int-apps=app1,app2,appN` — force listed apps to be built as internal with the `firmware` target +- `--extra-ext-apps=app1,app2,appN` — force listed apps to be built as external with the `firmware_extapps` target +- `--extra-define=A --extra-define=B=C ` — extra global defines that will be passed to the C/C++ compiler, can be specified multiple times +- `--proxy-env=VAR1,VAR2` — additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes the execution environment and doesn't forward all inherited environment variables. You can find the list of variables that are always forwarded in the `environ.scons` file. ## Configuration Default configuration variables are set in the configuration file: `fbt_options.py`. Values set in the command line have higher precedence over the configuration file. -You can also create a file called `fbt_options_local.py` that will be evaluated when loading default options file, enabling persisent overriding of default options without modifying default configuration. +You can also create a file called `fbt_options_local.py` that will be evaluated when loading default options file, enabling persistent overriding of default options without modifying default configuration. You can find out available options with `./fbt -h`. ### Firmware application set -You can create customized firmware builds by modifying the list of applications to be included in the build. Application presets are configured with the `FIRMWARE_APPS` option, which is a `map(configuration_name:str -> application_list:tuple(str))`. To specify an application set to use in the build, set `FIRMWARE_APP_SET` to its name. +You can create customized firmware builds by modifying the list of apps to be included in the build. App presets are configured with the `FIRMWARE_APPS` option, which is a `map(configuration_name:str → application_list:tuple(str))`. To specify an app set to use in the build, set `FIRMWARE_APP_SET` to its name. For example, to build a firmware image with unit tests, run `./fbt FIRMWARE_APP_SET=unit_tests`. Check out `fbt_options.py` for details. diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index 5b08c3471f..da0b0a19d6 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -38,7 +38,7 @@ Version differences: ### Description -This file format is used to store the UID, SAK and ATQA of a ISO14443-3A device. +This file format is used to store the UID, SAK and ATQA of an ISO14443-3A device. UID must be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. Version differences: diff --git a/documentation/file_formats/TarHeatshrinkFormat.md b/documentation/file_formats/TarHeatshrinkFormat.md index 86c27a698e..79b75416b9 100644 --- a/documentation/file_formats/TarHeatshrinkFormat.md +++ b/documentation/file_formats/TarHeatshrinkFormat.md @@ -1,6 +1,6 @@ -# Heatshrink-compressed Tarball Format +# Heatshrink-compressed Tarball Format {#heatshrink_file_format} -Flipper supports the use of Heatshrink compression library for .tar archives. This allows for smaller file sizes and faster OTA updates. +Flipper supports the use of Heatshrink compression library for `.tar` archives. This allows for smaller file sizes and faster OTA updates. Heatshrink specification does not define a container format for storing compression parameters. This document describes the format used by Flipper to store Heatshrink-compressed data streams. diff --git a/documentation/js/js_badusb.md b/documentation/js/js_badusb.md index 78c49104ca..b21126dfcb 100644 --- a/documentation/js/js_badusb.md +++ b/documentation/js/js_badusb.md @@ -67,7 +67,7 @@ badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo ``` ## release -Release a previously hold key. +Release a previously held key. ### Parameters Same as `press` diff --git a/documentation/js/js_data_types.md b/documentation/js/js_data_types.md index de1c896dce..bd3bb1f426 100644 --- a/documentation/js/js_data_types.md +++ b/documentation/js/js_data_types.md @@ -1,13 +1,13 @@ # Data types {#js_data_types} Here is a list of common data types used by mJS. -- string - sequence of single byte characters, no UTF8 support +- string — sequence of single byte characters, no UTF8 support - number - boolean -- foreign - C function or data pointer +- foreign — C function or data pointer - undefined - null -- object - a data structure with named fields -- array - special type of object, all items have indexes and equal types -- ArrayBuffer - raw data buffer -- DataView - provides interface for accessing ArrayBuffer contents +- object — a data structure with named fields +- array — special type of object, all items have indexes and equal types +- ArrayBuffer — raw data buffer +- DataView — provides interface for accessing ArrayBuffer contents diff --git a/documentation/js/js_math.md b/documentation/js/js_math.md index 296f01c62d..12dae8fb37 100644 --- a/documentation/js/js_math.md +++ b/documentation/js/js_math.md @@ -290,7 +290,7 @@ math.pow(2, 10); // 1024 ``` ## random -Return a floating-point, pseudo-random number that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range - which you can then scale to your desired range. +Return a floating-point, pseudo-random number that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range — which you can then scale to your desired range. ### Returns A floating-point, pseudo-random number between 0 (inclusive) and 1 (exclusive). From 369e19def3e400c438f899735179ece4f0402c87 Mon Sep 17 00:00:00 2001 From: jay candel Date: Tue, 8 Oct 2024 21:07:05 +0800 Subject: [PATCH 126/236] [IR] Heavily Expand Universal Remotes (#3929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expand all universal remotes AC: 26.04% increase Audio: 277.05% increase Projector: 43.36% increase TV: 35.54% increase * formatted with flipper infrared script * Delete applications/main/infrared/tv.ir * Delete applications/main/infrared/ac.ir * Delete applications/main/infrared/audio.ir * Delete applications/main/infrared/projector.ir * more ac additions * updated universal files: all buttons have same capitalization now, ran through infrared script again. * improved raw signal checks on ac file * updated ac.ir * update ac.ir * Infrared: remove duplicate data from projector.ir Co-authored-by: あく --- .../infrared/resources/infrared/assets/ac.ir | 954 ++++ .../resources/infrared/assets/audio.ir | 4083 +++++++++++++++++ .../resources/infrared/assets/projector.ir | 350 ++ .../infrared/resources/infrared/assets/tv.ir | 1679 +++++++ 4 files changed, 7066 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 0a182c5d94..f57fe3aa13 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -942,3 +942,957 @@ type: parsed protocol: NEC address: 20 00 00 00 command: 02 00 00 00 +# +# Model: Airmax +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4453 4313 586 1554 585 483 586 1554 587 1552 587 483 587 484 586 1552 587 482 588 483 586 1553 586 483 587 483 586 1554 586 1553 585 484 587 1553 586 484 587 1553 586 1554 586 1553 586 1554 586 483 586 1553 586 1553 586 1555 585 483 586 482 588 483 585 484 585 1554 586 483 587 484 585 1553 587 1554 584 1554 585 484 586 484 586 483 586 483 586 484 586 482 588 483 584 484 586 1553 585 1555 584 1555 586 1553 586 1554 585 5129 4428 4312 585 1553 586 483 586 1555 584 1553 586 484 583 486 584 1555 585 484 585 483 586 1554 585 483 586 484 585 1554 585 1554 585 484 585 1554 585 484 584 1554 585 1554 584 1554 585 1554 586 483 585 1554 585 1555 584 1553 584 484 586 483 586 483 585 484 586 1554 583 484 586 483 585 1553 585 1553 585 1553 585 484 584 485 585 485 584 484 585 484 585 485 584 484 585 483 586 1554 585 1553 585 1554 584 1553 585 1553 585 +# +# Model: Airmet ac +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8971 4489 568 584 535 564 565 561 537 588 541 558 561 565 534 592 537 562 567 1659 559 566 563 563 536 1664 584 567 542 584 535 1664 564 587 542 558 561 564 535 591 538 562 567 558 541 585 534 566 563 562 537 589 540 559 560 566 533 593 536 563 566 560 539 587 532 567 562 564 535 591 538 1661 567 1658 590 1662 566 1659 559 1667 592 1660 558 567 562 563 536 590 539 561 558 567 542 584 535 1664 564 1662 586 1665 563 1662 566 1659 589 1663 565 560 559 566 533 593 536 564 565 560 538 587 542 557 562 564 535 591 538 1661 567 585 534 1666 562 1663 585 566 533 1667 592 560 538 +# +# Model: Airwell +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3089 3684 1991 910 1011 1854 983 847 1992 911 982 965 956 874 984 938 982 939 973 1866 1889 948 973 950 972 950 972 859 971 950 972 950 972 954 968 887 943 979 943 979 943 979 943 887 944 979 943 979 943 979 943 887 943 979 943 950 972 979 943 1803 1952 977 3051 3724 1951 978 943 1894 943 888 1951 978 943 979 943 888 942 980 942 979 943 1895 1859 978 943 979 943 979 943 888 943 979 943 979 943 980 942 888 943 980 942 980 942 980 942 888 942 980 942 980 942 980 942 888 943 979 943 980 942 980 942 1804 1950 978 3050 3725 1950 978 942 1896 941 889 1950 979 941 980 942 888 942 980 942 980 942 1896 1858 979 942 980 942 980 942 889 941 980 942 981 941 980 942 889 942 980 942 981 941 981 941 889 941 981 941 981 941 981 941 889 941 981 941 981 941 981 941 1805 1949 980 3964 +# +# Model: Airwell AWSI-PNXA012-N11 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3031 3980 1912 1945 1969 1950 1912 973 994 981 965 951 975 1027 909 1950 975 965 971 974 1941 1006 940 977 969 1003 964 951 975 1000 936 1006 940 973 973 1974 941 973 1942 1006 941 974 993 950 975 999 937 1036 910 975 992 952 974 998 938 1950 975 967 3947 3987 1916 2002 1881 1975 1939 947 999 943 972 971 996 948 967 1949 997 949 976 997 1907 980 966 1006 941 975 971 1001 945 999 968 945 970 976 970 1974 972 942 1942 975 992 952 994 949 977 970 997 948 967 979 967 1005 942 999 968 1923 971 1000 3914 3958 1965 1919 1995 1893 1969 984 962 954 972 996 971 973 942 1949 976 992 965 947 1947 1002 965 950 976 1001 945 997 939 1007 939 1006 940 998 969 1915 1000 944 2002 946 969 944 992 956 1001 943 972 1000 967 949 966 977 969 1001 966 1926 968 1003 4889 +# +# Model: Easy Home_Portable_Air_Cooler +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1281 401 1275 431 406 1248 1276 431 1244 409 439 1242 433 1247 439 1242 433 1248 438 1243 443 1239 1285 7120 1280 407 1279 406 442 1239 1285 400 1275 434 414 1241 434 1247 439 1243 443 1239 436 1246 440 1242 1282 8228 1282 405 1281 404 433 1248 1286 399 1276 406 442 1240 435 1245 441 1241 434 1247 439 1243 443 1239 1285 7121 1279 407 1279 432 416 1239 1285 400 1275 434 414 1241 434 1246 440 1242 444 1238 437 1245 441 1241 1283 8225 1285 402 1284 401 436 1245 1279 406 1280 403 434 1246 440 1241 434 1246 440 1242 444 1237 438 1244 1280 7124 1286 399 1276 408 440 1241 1283 401 1274 434 414 1240 435 1245 441 1241 434 1246 440 1242 444 1237 1287 8220 1279 408 1278 407 441 1240 1284 401 1274 409 439 1242 433 1247 439 1242 444 1238 437 1244 442 1240 1284 7121 1278 408 1278 433 415 1240 1284 399 1276 406 442 1239 436 1244 442 1239 436 1244 442 1239 436 1245 1279 8227 1283 403 1283 400 437 1244 1280 403 1283 399 438 1242 433 1247 439 1242 433 1247 439 1242 444 1238 1275 7127 1283 402 1284 426 412 1243 1281 402 1274 408 440 1240 435 1245 441 1239 436 1245 441 1240 435 1245 1279 8226 1284 402 1284 399 438 1242 1282 400 1275 407 441 1239 436 1243 443 1237 438 1242 444 1237 438 1242 1282 7120 1280 405 1281 428 409 1244 1280 403 1272 409 439 1241 435 1245 441 1239 436 1244 442 1239 436 1244 1280 8225 1285 400 1275 408 440 1241 1283 399 1276 406 432 1248 438 1242 433 1247 439 1242 444 1237 438 1242 1282 7120 1280 406 1280 403 434 1246 1278 405 1281 400 437 1243 432 1247 439 1241 434 1247 439 1242 444 1237 1276 8228 1282 405 1281 402 435 1244 1280 404 1282 400 437 1241 434 1246 440 1241 434 1246 440 1242 433 1247 1277 7126 1284 403 1283 400 437 1241 1283 402 1273 409 439 1239 436 1244 442 1239 436 1244 442 1239 436 1245 1279 8227 1283 403 1283 401 436 1242 1282 403 1283 399 438 1241 434 1246 440 1240 435 1246 440 1241 434 1246 1278 7126 1284 402 1284 399 438 1241 1283 401 1306 376 440 1239 436 1244 442 1238 437 1244 442 1239 436 1245 1279 8225 1285 402 1305 379 437 1242 1313 371 1304 378 438 1240 435 1245 441 1240 435 1245 441 1240 435 1246 1278 7125 1285 401 1306 378 470 1209 1304 380 1306 377 460 1218 436 1244 442 1238 437 1244 442 1239 436 1245 1310 8195 1315 371 1304 379 469 1211 1313 371 1304 378 470 1209 435 1245 441 1240 435 1245 441 1240 435 1245 1310 7092 1308 378 1308 375 462 1216 1308 376 1299 382 466 1213 462 1218 436 1244 442 1238 437 1244 442 1239 1306 +# +# Model: Amcor AC +# +name: Off +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 9C 00 00 00 +# +# Model: Arctic King_RG15B1 +# +name: Off +type: parsed +protocol: NECext +address: 01 FF 00 00 +command: 12 ED 00 00 +# +# Model: Argo ac +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3455 1584 486 347 483 348 482 347 482 348 480 350 430 401 429 1229 432 401 429 400 430 400 430 399 431 399 431 398 432 398 432 399 431 399 456 375 455 377 453 1207 453 378 452 1209 452 379 451 379 451 379 451 379 451 379 451 379 452 379 451 379 451 379 451 403 427 1233 427 1234 427 380 450 1234 426 1234 427 403 427 1233 427 403 427 1234 427 403 427 1234 426 404 426 403 427 403 427 403 427 403 427 1234 427 403 427 403 427 403 427 403 427 403 427 1234 427 403 427 403 427 403 427 403 427 403 427 1234 427 403 427 1234 426 1234 427 1234 426 1234 427 1234 427 403 427 403 427 403 427 1234 427 404 426 403 427 403 427 403 427 403 427 403 427 403 428 403 427 403 427 403 427 403 427 403 427 403 427 403 427 1234 427 1234 427 404 426 404 426 404 426 404 426 1234 426 404 427 404 426 404 426 1234 426 404 426 404 426 1234 427 404 426 404 426 404 426 404 426 404 426 404 426 1235 426 1235 426 1235 426 404 426 1235 425 405 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 427 404 426 404 426 404 426 404 427 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 1235 426 1235 426 1235 426 404 426 1235 426 404 426 +# +# Model: Ariston AC_A-MW09-IGX +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4481 4414 595 1596 594 527 562 1602 588 1604 586 535 565 530 570 1594 596 526 563 532 568 1596 594 528 561 534 566 1598 592 1600 590 531 569 1596 594 527 562 1603 587 1604 586 1605 648 1543 594 527 562 1603 587 1604 586 1605 595 525 564 531 569 526 563 532 568 1596 594 528 561 534 566 1598 592 1600 590 1601 589 532 568 527 562 533 567 528 561 534 566 529 560 535 565 530 570 1594 596 1596 594 1597 593 1598 592 1599 591 5252 4503 4418 590 1601 589 532 568 1597 593 1598 592 529 560 535 565 1600 590 531 569 526 563 1602 588 533 567 529 560 1604 596 1595 595 526 563 1602 588 533 567 1598 592 1599 591 1600 590 1601 589 532 568 1598 592 1599 591 1600 590 531 569 527 562 532 568 528 561 1603 587 534 566 530 559 1605 595 1596 594 1597 593 528 561 534 566 529 560 535 565 530 570 525 564 531 569 527 562 1602 588 1603 587 1604 596 1595 595 1596 594 +# +# Model: Ballu R05-BGE +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4467 4296 626 1511 627 469 626 1512 626 1538 652 442 625 469 601 1538 600 495 599 469 599 1565 598 471 597 498 596 1542 596 1569 595 474 595 1570 594 474 595 1570 594 1544 594 1570 594 1570 594 475 594 1570 594 1544 594 1570 594 475 594 501 594 475 594 500 595 1544 594 501 594 475 594 1570 594 1544 594 1570 594 475 594 501 594 475 594 501 594 475 594 501 594 475 594 475 620 1544 594 1570 594 1544 594 1570 594 1545 594 5144 4435 4328 593 1545 593 501 594 1545 593 1571 593 475 594 501 594 1545 593 501 593 475 594 1571 593 475 594 501 594 1545 593 1571 593 475 594 1571 593 476 619 1545 593 1571 593 1545 593 1571 593 476 593 1571 593 1545 593 1571 594 502 593 476 593 476 593 502 593 1545 593 502 593 476 593 1572 592 1572 592 1546 592 502 593 476 593 476 593 502 593 477 592 503 592 477 592 503 592 1546 592 1572 592 1546 592 1572 592 1572 592 +# +# Model: Portable +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3120 1593 488 1180 489 1177 492 342 492 342 492 367 467 1174 485 349 485 349 485 1181 488 1179 490 343 491 1177 492 341 493 366 458 1183 486 1181 488 346 488 1179 490 1177 492 342 492 341 493 1174 485 349 485 347 487 1180 489 345 489 344 490 370 464 368 466 341 493 341 483 376 458 375 459 348 486 374 460 372 462 345 489 370 464 369 465 368 466 341 493 367 457 348 486 374 460 348 486 1179 490 343 491 343 491 1176 493 1174 485 349 485 349 485 374 460 373 461 373 461 346 488 371 463 371 463 369 465 1175 484 350 484 350 484 348 486 374 460 346 488 372 462 345 489 371 463 370 464 368 466 340 484 376 458 376 458 375 459 348 486 347 487 345 489 370 464 370 464 369 465 342 492 368 466 367 457 375 459 348 486 374 460 347 487 372 462 345 489 371 463 343 491 368 466 341 493 367 457 376 458 375 459 1181 488 346 488 345 489 1178 491 342 492 342 492 1175 494 1173 486 1182 487 346 488 346 488 1179 490 343 491 342 492 368 466 367 457 +# +# Model: Bonaire DurangoAC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1305 435 1280 432 415 1255 1307 432 1272 439 418 1252 442 1255 1307 431 416 1255 439 1258 447 1251 443 8174 1302 437 1278 433 414 1255 1307 432 1273 438 419 1250 444 1254 1298 440 417 1253 441 1256 438 1259 445 8170 1306 433 1271 439 418 1251 1301 438 1277 434 413 1256 449 1249 1303 435 412 1258 446 1251 443 1254 440 8176 1300 438 1277 434 413 1283 1279 433 1271 440 417 1278 416 1255 1307 431 416 1253 441 1257 447 1250 444 8171 1305 433 1272 439 418 1278 1274 438 1277 433 414 1282 412 1259 1303 434 413 1256 448 1249 445 1252 442 8173 1303 435 1270 440 417 1279 1273 438 1277 433 414 1282 412 1258 1304 433 414 1282 412 1258 446 1250 444 8171 1305 433 1272 438 419 1276 1276 435 1270 441 416 1252 442 1255 1297 440 417 1279 415 1255 439 1257 447 8168 1297 439 1276 434 413 1256 1306 431 1273 436 411 1258 446 1250 1302 409 438 1284 421 1249 445 1252 442 +# +# Model: Boston Bay_MSAB_09CR +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4468 4412 543 1593 541 502 539 1596 548 1589 545 496 545 498 543 1593 541 501 540 502 539 1598 546 495 546 497 544 1591 543 1594 540 501 539 1597 547 494 547 1590 544 1591 543 1594 540 1595 539 504 547 1588 546 1590 544 1593 540 501 539 503 538 503 538 505 546 1588 546 497 544 496 545 1592 542 1592 542 1594 540 501 539 503 538 503 538 505 546 494 547 496 545 496 545 497 544 1590 544 1593 541 1594 540 1597 547 1589 545 5188 4430 4421 544 1592 541 499 541 1595 539 1596 538 505 546 494 547 1589 545 497 544 496 545 1592 542 500 540 499 542 1594 540 1596 538 503 538 1599 545 497 544 1591 543 1593 541 1595 539 1596 538 504 547 1588 546 1588 546 1590 544 498 542 498 543 499 541 500 541 1593 541 501 540 502 539 1597 547 1586 548 1587 547 496 545 495 546 497 544 498 543 497 544 498 543 499 542 498 542 1592 542 1594 540 1595 539 1597 547 1589 545 +# +# Model: Botti BL-168DLR +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9577 4536 622 576 621 577 620 577 620 578 619 578 619 578 619 579 618 1667 625 1660 622 1664 618 1668 624 1662 620 1666 616 1670 622 1664 618 580 617 581 616 1669 623 575 622 575 622 1664 618 580 617 580 617 581 616 1669 623 575 622 1664 618 1668 624 573 624 1662 620 1666 616 1670 622 39798 9556 2250 626 96841 9564 2245 620 96843 9560 2253 622 96836 9569 2244 621 96844 9571 2244 621 96843 9573 2247 619 96844 9572 2248 617 96847 9570 2248 617 96848 9568 2250 626 96833 9564 2251 625 96834 9564 2252 624 96843 9595 2223 653 96809 9597 2221 644 96822 9604 2215 650 96814 9591 2226 650 96813 9591 2225 650 96811 9600 2218 647 96816 9597 2221 654 96812 9601 2219 646 96815 9598 2220 645 96813 9601 2216 649 96815 9599 2219 657 96809 9565 2252 624 96842 9564 2254 622 96841 9565 2254 622 96838 9568 2249 616 96844 9571 2247 619 96842 9574 2245 620 96840 9566 2251 625 96838 9567 2251 625 96835 9570 2250 615 96848 9566 2254 621 96843 9572 2247 618 96847 9567 2253 623 96842 9572 2249 616 96843 9572 2247 618 96843 9572 2249 616 96849 9576 2245 621 96842 9574 2246 619 96842 9575 2246 619 96843 9573 2248 617 96843 9572 2249 616 96847 9571 2249 617 96839 9569 2251 625 96837 9569 2251 624 96837 9568 2250 626 96835 9572 2248 618 96844 9572 2246 619 96843 9564 2253 623 96834 9572 2245 620 96835 9569 2246 619 +# +# Model: Boulanger AC +# +name: Off +type: parsed +protocol: NEC +address: 81 00 00 00 +command: 6B 00 00 00 +# +# Model: Chigo CS-21H3A-B155AF +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6133 7359 575 561 550 560 577 532 576 559 550 534 602 507 549 536 573 536 573 564 546 539 571 538 545 540 570 538 570 566 545 539 571 589 545 590 521 540 570 562 521 563 547 562 545 592 521 563 548 588 522 615 521 563 547 589 521 589 548 589 547 562 521 589 548 588 548 589 521 563 547 563 546 590 521 589 547 562 546 590 521 589 547 589 547 563 521 563 548 589 521 589 548 588 549 589 545 539 548 589 548 563 521 562 549 589 521 563 548 563 547 590 521 589 548 589 548 563 521 589 547 1667 521 1641 521 563 547 589 548 589 521 589 548 562 548 590 520 589 548 589 548 589 521 590 547 588 548 1667 520 1668 545 1642 547 563 520 563 547 1667 520 589 547 589 547 1641 546 563 547 1641 546 563 546 538 546 1668 544 539 547 1668 520 590 546 590 546 1668 520 564 546 590 520 1668 547 1642 546 1668 542 7378 545 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6107 7355 578 556 579 530 551 532 605 529 604 505 550 559 602 507 574 561 522 563 573 561 546 537 548 561 548 561 522 561 549 561 522 538 572 587 549 587 522 561 548 561 547 563 522 561 548 561 522 561 549 588 546 563 521 561 549 561 522 561 549 561 522 561 549 561 547 590 521 561 549 562 521 562 548 562 545 538 548 562 547 562 522 588 548 588 548 588 522 562 548 588 548 589 521 562 548 588 547 589 521 562 548 562 521 562 548 563 546 590 521 562 548 589 546 591 520 1641 544 566 547 1667 544 1616 548 1613 547 1666 520 588 548 588 548 589 545 538 547 562 547 564 519 562 548 563 520 563 547 1667 520 589 547 564 545 1641 547 590 546 590 520 1666 548 590 543 1642 548 563 520 1666 548 590 544 538 547 1667 544 592 520 1668 543 592 520 562 548 564 519 1667 547 590 544 1642 547 1614 546 588 548 7396 543 +# +# Model: Chigo KRF-51G_79F +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6060 7357 592 1633 593 1633 593 1633 593 1633 593 1633 593 1633 592 1634 592 1634 592 515 591 515 591 515 591 516 590 517 590 516 590 517 590 517 589 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 517 590 517 590 517 590 517 590 517 590 517 589 517 590 517 590 1636 590 1637 589 1637 589 1637 589 1636 615 1612 589 1637 589 1636 590 517 590 517 589 517 590 517 590 517 590 517 589 517 590 517 590 1637 589 1637 589 1637 589 517 589 517 589 517 590 1637 589 1637 589 517 590 517 590 517 589 1637 589 1637 589 1637 590 517 589 517 589 517 589 518 589 517 589 1637 589 1637 589 517 590 1637 589 1637 589 1637 589 1637 589 1637 590 517 589 517 589 1637 589 517 589 517 590 517 589 1638 589 517 590 1637 589 518 589 1637 589 518 588 518 589 1637 589 518 588 1637 589 518 588 1637 589 518 589 1637 589 1637 589 7359 589 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6059 7355 592 1633 592 1633 592 1633 592 1633 592 1633 592 1633 592 1633 618 1608 617 489 617 490 616 490 616 491 590 517 589 517 614 492 590 517 590 1636 589 1636 590 1636 590 1636 589 1636 590 1636 590 1636 590 1636 590 517 589 517 589 517 589 517 590 517 589 517 589 517 589 517 589 517 589 1636 614 492 590 1636 590 1636 590 1636 589 1636 590 1636 589 1637 589 517 589 1636 590 517 589 517 589 517 614 492 590 517 589 1636 590 517 589 1636 590 517 589 517 589 517 589 517 590 1636 590 517 589 1636 590 517 589 1636 589 1636 590 1636 590 1637 589 517 589 517 589 1637 588 1637 589 517 589 1637 589 1636 590 517 589 1636 590 1636 590 517 589 517 589 1637 589 517 589 517 589 1637 589 517 589 518 588 1637 589 518 588 1637 589 517 590 1637 588 518 588 518 588 1637 589 518 588 1637 589 518 588 1637 589 518 588 1637 589 1637 589 7357 589 +# +# Model: Comfort Aire_RG57A6 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4381 4345 579 1558 579 488 580 1559 578 488 580 489 579 488 580 489 579 1558 579 489 579 488 580 488 580 489 579 488 580 489 579 1557 579 488 580 488 580 1559 577 1557 580 488 580 1557 579 1557 580 1557 580 488 580 1558 579 1558 579 1558 579 1557 580 1558 579 1559 578 1557 580 1557 580 1556 580 1558 579 1557 579 1559 578 1557 579 1560 577 1561 549 1584 579 1557 553 1584 552 1584 552 515 553 516 551 516 551 1585 551 1583 553 5156 4384 4343 552 515 552 1584 552 515 552 1584 552 1584 552 1584 552 1585 551 515 553 1585 551 1583 553 1584 552 1583 553 1584 552 1583 553 516 551 1584 552 1585 551 515 552 517 550 1583 553 515 552 515 552 516 551 1584 552 515 552 516 551 516 551 515 552 515 552 515 552 516 551 516 551 515 552 515 553 515 552 515 552 515 552 516 551 515 552 516 551 515 552 515 552 516 551 1583 553 1584 552 1585 551 515 552 515 552 +# +# Model: Cortlitec portable_ac +# +name: Off +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 00 FF 00 00 +# +name: Dh +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 0C F3 00 00 +# +# Model: COTech MPPH-08CRN7-QB6_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4412 4354 570 1575 568 504 572 1574 569 503 563 510 567 505 572 527 539 1580 563 509 568 505 572 1574 569 503 563 509 568 505 572 527 539 507 569 503 563 1582 571 500 566 507 569 503 563 535 542 505 561 511 566 1579 564 1582 571 1574 569 1576 567 1578 565 1581 562 1583 570 1575 568 1577 566 1580 563 1582 571 1574 569 1576 567 1579 564 1581 562 1583 570 1575 568 1577 566 1579 564 509 568 1577 566 1580 563 1582 571 501 565 5165 4412 4352 572 500 566 1579 564 509 568 1577 566 1579 564 1581 562 1583 570 502 564 1581 562 1582 571 501 565 1580 563 1582 571 1574 569 1576 567 1578 565 1579 564 509 567 1577 566 1579 564 1581 562 1583 570 1575 568 1577 566 506 571 501 565 534 542 529 537 535 542 531 535 537 539 532 545 527 539 507 569 529 537 509 568 504 562 510 566 532 544 527 539 507 569 528 538 534 542 1576 567 504 562 510 566 506 571 1574 569 +# +# Model: Daikin AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9830 9789 9825 9795 4618 2487 381 348 384 929 381 936 384 353 379 934 386 350 382 350 382 362 380 349 383 929 381 355 387 346 386 926 384 353 379 354 388 355 387 918 382 354 388 348 384 349 383 353 379 357 385 924 386 358 384 921 389 347 385 351 381 929 381 932 388 348 384 349 383 361 381 347 385 351 381 355 387 345 387 925 385 352 380 353 379 365 388 340 382 354 388 348 384 349 383 929 381 355 387 346 386 358 384 921 379 357 385 351 381 352 380 356 386 930 380 357 385 358 385 344 388 348 384 933 387 349 383 353 379 938 382 354 388 352 380 20353 4625 +# +# Model: Daikin AC_industrial_TB +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5055 2191 335 1799 360 752 338 714 365 716 364 1800 359 723 367 715 365 718 361 720 359 1805 365 747 332 1802 357 1806 364 719 360 1803 367 1798 361 1803 367 1797 362 1802 368 744 335 1799 360 721 369 714 365 716 363 719 360 721 369 713 366 1798 361 1802 368 715 364 717 362 720 359 723 367 715 364 1799 360 722 368 714 365 717 363 719 361 722 368 714 365 716 363 719 360 721 369 713 366 716 363 718 361 721 359 723 367 1797 362 1802 368 1796 363 1801 358 754 336 716 363 719 361 29583 5057 2160 366 1798 361 750 329 723 367 715 364 1800 359 753 337 715 364 717 363 720 359 1804 366 747 332 1801 358 1806 364 718 361 1803 356 1798 1802 368 1797 362 1802 368 714 365 1799 360 722 368 714 365 717 362 719 360 722 368 714 365 1798 361 1803 367 715 364 718 362 721 359 723 367 715 364 718 361 720 359 723 367 715 364 717 362 720 360 1804 366 1799 360 721 369 714 365 1798 361 1803 367 1798 361 720 359 723 367 715 364 718 361 720 360 723 367 715 364 717 362 720 359 722 368 714 365 717 362 719 360 722 368 1796 363 719 360 721 369 714 365 716 363 719 361 721 369 713 366 716 363 718 361 721 358 723 367 716 363 718 362 720 359 723 367 715 364 718 361 720 359 723 367 715 364 1799 360 1804 366 1799 360 721 369 714 365 716 363 1801 358 754 336 1798 361 720 360 1805 365 748 331 720 359 723 367 715 364 717 363 720 360 722 368 714 365 717 362 720 359 722 368 714 365 717 363 719 361 722 368 714 365 1798 361 751 339 713 366 716 363 1801 359 1805 365 1800 359 1805 365 1800 359 1805 365 1799 360 +# +# Model: Daikin ARC480A53 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 445 455 421 449 416 453 422 447 418 451 424 25400 3494 1753 416 1322 419 450 415 456 420 449 416 1325 416 451 425 444 421 448 417 453 423 1318 423 444 421 1320 421 1316 414 457 419 1320 421 1318 423 1314 417 1322 419 1320 421 449 416 453 422 1317 424 445 420 450 415 456 419 447 418 451 414 456 420 449 416 452 424 448 417 453 422 445 420 450 415 456 419 448 417 452 423 447 418 449 416 453 423 448 417 453 423 446 419 449 416 453 423 447 418 1321 420 451 414 454 422 449 416 1322 419 1319 422 449 416 1322 419 451 424 445 420 449 416 455 421 445 420 449 416 455 420 448 417 452 424 446 419 1321 420 1317 424 1315 416 1324 417 1322 419 1320 421 1320 421 446 419 1319 422 1319 422 1318 423 1313 417 453 423 445 420 450 415 454 421 449 416 454 422 446 419 449 416 456 420 448 417 453 423 444 421 450 415 454 422 447 418 451 424 445 420 450 415 455 420 447 418 452 424 447 418 451 424 445 420 448 417 453 423 446 419 451 424 444 421 448 417 452 423 446 419 451 424 445 420 448 417 451 414 456 420 452 424 443 422 449 416 451 424 448 417 449 416 455 420 1318 423 446 419 1319 422 448 417 451 424 447 418 1320 421 1316 415 455 421 451 414 455 421 449 416 451 424 446 419 1319 422 448 417 454 422 446 419 451 414 1325 416 452 423 447 418 451 424 447 418 1318 423 446 419 449 416 1327 424 1312 419 449 416 455 421 449 416 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 444 456 419 450 415 455 420 448 417 455 420 25402 3491 1755 424 1315 415 455 420 449 416 458 417 1313 417 453 422 447 418 452 423 444 421 1320 421 447 418 1321 420 1319 422 448 417 1322 419 1320 421 1318 423 1315 415 1332 419 442 423 448 417 1320 421 448 417 454 422 445 420 450 415 461 414 450 415 455 420 446 419 452 423 446 419 451 424 444 421 447 418 454 421 445 420 449 416 454 422 447 418 1322 419 449 416 454 421 447 418 451 414 1325 416 453 422 447 418 453 422 446 419 453 422 445 420 448 417 453 422 1315 415 1324 417 453 422 447 418 450 415 454 422 448 417 454 421 446 419 451 424 1314 416 1326 415 1321 420 1318 423 446 419 1321 420 449 416 1322 419 1323 418 1320 421 1317 424 1318 423 445 420 449 416 452 423 449 416 452 423 444 421 449 416 459 416 446 419 458 417 448 417 456 420 443 422 447 418 452 423 445 420 450 415 452 424 447 418 452 423 446 419 449 416 460 415 448 417 452 423 445 420 450 415 455 420 449 416 452 423 445 420 451 414 454 421 449 416 455 420 446 419 451 414 454 422 449 416 452 424 445 420 451 424 443 422 448 417 1323 418 451 424 1315 415 453 422 446 419 450 415 1325 416 1324 417 451 414 456 419 450 415 454 421 447 418 452 423 446 419 450 415 453 423 450 415 451 424 1315 416 453 422 447 418 452 424 447 418 450 415 1323 418 1321 420 1319 422 1318 423 1317 424 1316 414 453 422 +# +# Model: Daikin FTE35KV1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5041 2133 360 1770 356 693 364 685 362 689 358 1772 364 686 361 689 358 692 365 684 363 1768 358 692 365 1767 359 1771 355 694 363 1769 357 1773 363 1768 358 1772 364 1767 359 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 691 366 684 363 1767 359 1773 363 1768 358 1772 364 1767 359 689 358 1774 362 1769 357 691 366 684 363 687 360 690 357 693 364 685 362 689 358 691 366 684 363 687 360 690 357 693 364 1766 360 1773 363 1767 359 1771 355 694 363 687 360 690 357 693 364 29443 5038 2134 359 1774 362 686 361 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 1771 365 685 362 1769 357 1775 361 687 360 1772 364 1767 359 1771 355 1776 360 1770 356 693 364 686 361 1799 327 693 364 686 361 689 358 692 365 685 362 688 359 691 356 694 363 686 361 689 358 1773 363 1769 357 691 366 684 363 1798 328 692 365 1767 359 1771 355 694 363 686 361 689 358 692 365 1765 361 1772 364 684 363 687 360 1801 335 684 363 687 360 690 357 693 364 686 361 1769 357 694 363 686 361 689 358 692 365 685 362 688 359 691 356 694 363 687 360 690 357 693 365 685 362 688 359 691 367 684 363 687 360 690 357 693 365 685 362 688 359 1772 364 1768 358 690 357 693 364 686 361 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 691 356 694 363 687 360 689 358 1773 363 687 360 690 357 693 364 685 362 688 359 1772 364 686 361 1769 357 1776 360 1770 356 1775 361 687 360 +# +# Model: Daikin FTX50GV1B +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 190 26844 503 368 475 395 475 395 475 367 476 395 475 25347 3527 1695 451 1290 451 420 476 394 476 394 475 1264 475 370 471 399 470 400 470 400 443 1298 443 400 470 1270 471 1270 471 400 470 1270 471 1271 470 1270 471 1270 471 1271 470 400 470 373 470 1271 470 400 470 400 470 400 443 400 470 400 470 400 443 400 470 400 470 400 470 374 469 1271 470 400 470 1271 470 400 470 401 442 401 469 1272 469 1272 469 401 469 401 469 374 469 401 469 401 469 401 442 401 469 401 469 401 469 374 469 401 469 401 469 374 469 401 469 401 469 401 442 1300 441 1299 442 1300 441 402 468 1272 469 402 468 1272 469 1273 468 34903 3522 1703 471 1270 471 400 470 373 470 400 470 1270 471 400 470 400 443 400 470 400 470 1270 471 400 443 1298 443 1297 444 400 470 1270 471 1270 471 1270 471 1270 471 1270 471 400 470 400 470 1270 471 373 470 400 470 400 470 400 443 400 470 400 470 400 470 373 470 400 470 400 470 400 443 1298 443 400 470 400 470 400 443 400 470 1271 470 400 470 1271 470 1271 470 400 470 1271 470 1271 470 1271 470 373 470 400 470 1271 470 1271 470 400 470 1271 470 1271 470 400 443 400 470 400 470 400 470 1271 470 373 470 1271 470 401 469 1271 470 400 470 1271 470 34903 3522 1702 471 1269 472 373 470 399 471 399 471 1270 471 399 444 399 471 399 471 399 471 1270 471 372 471 1270 471 1270 471 399 471 1270 471 1270 471 1270 471 1270 471 1270 471 399 471 399 444 1297 444 399 471 399 471 399 471 372 471 399 471 399 471 373 470 399 471 399 471 399 444 400 470 399 471 399 471 373 470 400 470 400 470 399 444 400 470 1270 471 400 470 400 470 1270 471 1271 470 1271 470 372 471 400 470 400 470 400 443 1298 443 400 470 1271 470 1271 470 400 470 400 443 401 469 400 470 401 469 373 470 401 469 401 469 401 442 401 469 401 469 401 469 375 468 402 468 401 469 1273 468 1296 445 426 416 426 445 426 444 426 417 426 444 426 444 426 444 399 444 426 444 426 444 426 417 426 444 426 444 426 444 399 444 426 444 426 444 426 417 1324 417 1325 416 426 444 426 444 427 416 427 444 426 444 426 444 399 444 427 443 427 443 426 417 1325 416 1325 416 427 443 427 443 426 444 399 444 427 443 427 444 427 416 427 444 427 443 427 443 400 443 427 443 427 443 400 443 427 443 427 443 427 416 1325 416 427 443 427 443 427 443 400 443 427 443 1298 443 1298 443 427 443 427 416 427 443 427 443 427 443 400 443 427 443 1298 443 427 443 400 443 428 442 427 443 427 416 428 443 427 443 427 443 400 443 1298 443 1298 443 428 442 428 442 428 415 428 442 1299 442 +# +# Model: Daikin FTXM95PVMA +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 536 358 508 358 508 358 507 359 506 335 530 25024 3539 1656 534 1198 534 360 534 331 477 389 477 1228 533 360 507 358 508 358 508 358 507 1224 508 359 506 1225 506 1226 505 361 504 1229 502 1231 474 1258 474 1258 474 1258 474 392 474 392 474 1258 474 392 474 392 474 392 474 392 474 392 474 392 474 392 474 392 474 392 474 392 474 1259 473 392 474 1259 473 392 474 392 474 392 474 1259 473 1259 473 393 473 392 474 393 473 392 473 1259 473 393 473 393 473 393 473 392 474 393 473 393 473 393 472 393 473 393 473 393 473 393 473 1259 473 1260 472 1259 473 393 473 393 473 1259 473 1260 472 1260 472 35478 3510 1688 474 1259 473 392 474 392 474 392 474 1259 473 392 474 393 473 392 474 392 474 1259 473 393 473 1259 473 1259 473 392 474 1259 473 1259 473 1259 473 1259 473 1260 472 393 473 393 473 1259 473 393 473 393 473 393 473 393 473 393 473 393 473 393 473 393 473 394 472 394 472 394 472 1260 472 394 472 394 472 394 472 394 472 1261 471 395 471 395 471 395 471 418 448 395 471 418 448 418 448 419 447 418 448 419 447 418 448 419 447 418 448 1285 447 419 447 418 448 419 447 418 448 419 446 1285 447 419 447 419 447 1285 447 1285 447 419 447 35479 3510 1689 473 1259 473 393 473 393 473 392 474 1259 473 393 473 393 473 393 473 393 473 1259 473 393 473 1259 473 1259 473 393 473 1259 473 1260 472 1259 473 1259 473 1260 472 393 473 393 473 1260 472 393 473 394 472 394 472 393 473 393 472 393 473 393 473 393 473 393 473 393 473 394 472 394 472 394 472 394 472 394 472 394 472 394 472 394 472 1285 447 418 448 418 448 1285 447 1285 447 1285 447 419 447 419 447 419 447 1285 447 419 447 419 447 1285 447 1285 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 1286 446 1285 447 419 447 1286 446 1286 446 1286 446 1286 446 1286 446 419 447 420 446 420 446 419 447 420 446 419 447 420 446 419 447 420 446 420 446 420 446 420 445 420 446 1286 446 1286 446 420 446 420 446 420 446 420 446 420 446 420 446 420 446 420 446 420 446 420 446 1287 445 1287 445 420 446 420 446 420 446 421 445 420 446 421 445 420 446 420 446 420 446 420 445 421 445 421 445 421 445 421 445 421 445 421 445 421 445 1288 444 421 445 421 445 421 445 421 445 421 445 1288 444 1288 444 422 444 1288 444 422 444 421 445 422 443 422 444 422 444 1288 444 422 444 422 444 422 444 422 444 422 444 422 444 422 444 422 444 1288 444 422 444 1289 443 423 443 423 443 1289 443 1289 443 1290 442 +# +# Model: Daikin FTXS25KVMA +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 427 419 454 418 454 417 455 391 454 418 454 25460 3513 1723 428 1317 428 418 481 391 454 418 427 1317 428 418 454 418 454 418 454 391 454 1290 455 418 454 1291 479 1266 478 395 477 1268 476 1270 474 1272 473 1297 448 1297 448 423 422 424 449 1297 448 423 449 424 448 396 449 423 449 424 449 424 421 424 448 424 449 423 422 424 449 1297 448 424 449 1297 448 424 448 397 448 424 448 1297 448 1297 448 424 448 424 421 424 448 424 448 424 448 397 448 424 448 424 448 424 421 424 448 424 448 424 421 1325 420 425 448 424 448 424 448 1298 447 1298 447 1298 447 397 448 425 447 1298 447 1298 447 1298 447 34988 3533 1730 448 1297 448 424 448 424 421 424 448 1297 448 424 448 424 448 396 449 424 448 1297 448 424 448 1297 448 1297 448 397 448 1297 448 1297 448 1297 448 1297 448 1297 448 424 448 424 448 1297 448 424 421 424 448 424 448 424 448 397 448 424 448 424 448 425 420 424 449 424 448 424 449 1297 448 397 448 424 448 424 448 424 421 1325 420 425 448 424 448 1297 448 424 421 425 447 1297 448 424 448 1297 448 425 448 397 448 1298 447 425 448 424 448 1298 447 425 420 425 447 425 447 425 447 397 448 425 447 1298 447 1298 447 1298 447 425 447 1298 447 35014 3507 1730 448 1297 448 397 448 424 448 424 448 1297 448 424 421 424 448 424 448 424 448 1297 448 397 448 1298 447 1298 447 425 447 1299 445 1300 445 1299 446 1299 446 1299 446 426 446 426 419 1326 419 426 447 426 447 425 447 398 447 426 446 426 447 399 446 426 446 426 446 426 419 427 446 426 447 427 445 399 446 427 445 451 421 428 417 452 420 1325 420 453 420 452 420 1325 420 425 420 1325 420 453 420 452 420 425 420 452 420 452 420 452 393 453 420 452 420 1325 420 1326 419 453 419 426 420 452 420 452 420 453 392 453 419 453 419 452 421 425 420 452 420 452 420 453 392 453 419 1326 419 453 420 1326 419 1326 419 1327 418 1326 419 1327 418 453 392 454 419 453 420 453 420 425 420 453 419 453 419 453 392 453 419 453 420 453 419 426 419 453 419 1326 418 1327 418 454 419 454 391 454 419 453 419 453 392 454 419 453 419 454 419 426 419 454 418 1327 418 1327 418 454 418 455 391 454 419 454 418 454 418 426 419 454 418 454 419 454 391 454 418 454 419 454 391 454 419 454 418 454 418 427 418 454 418 1327 418 455 418 454 391 454 419 454 418 454 418 1328 417 1328 417 428 417 454 419 454 418 454 391 455 418 454 418 455 417 427 418 454 418 454 418 428 417 454 418 455 417 455 390 455 418 455 417 1328 417 455 418 428 417 455 417 1328 417 455 418 1328 417 1328 417 +# +# Model: Delonghi portable_Pinguino-Air-to-Air-PAC-N81_ac +# +name: Off +type: parsed +protocol: NECext +address: 48 12 00 00 +command: 88 08 00 00 +# +# Model: Pinguino PAC_EL275HGRKC +# +name: Off +type: parsed +protocol: NEC +address: 82 00 00 00 +command: 6B 00 00 00 +# +# Model: electriQ P15C-V2 +# +name: Off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +# Model: Electrol ESV09CRO_B2I +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3086 3063 3089 4438 571 1657 575 529 571 1656 576 529 571 534 576 1650 571 1655 577 527 573 1654 578 526 574 1653 569 1657 575 530 570 536 574 530 570 534 576 1648 573 1653 569 1658 574 530 570 535 575 530 570 534 576 527 573 531 569 535 575 529 571 533 577 526 574 530 570 534 576 528 572 532 568 536 574 529 571 533 578 526 574 530 570 534 576 528 572 1652 569 534 577 1649 573 532 579 526 574 530 570 534 576 527 573 531 569 535 575 528 572 532 568 536 574 529 571 533 577 526 574 530 570 534 577 1647 574 530 570 535 575 529 571 532 568 536 575 529 571 533 577 526 574 530 570 533 577 527 573 530 570 534 576 527 573 531 569 535 575 528 572 532 568 535 575 529 571 532 568 536 574 529 571 533 577 526 574 530 570 533 577 527 573 530 570 534 576 527 573 531 569 534 576 527 573 531 569 535 575 528 572 531 569 535 575 528 572 532 568 535 576 1648 573 531 569 1655 577 1648 573 1651 570 1654 578 1647 574 1651 570 533 577 1648 573 1651 571 +# +# Model: Emerson EARC8RE1_ac +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3110 1566 525 1084 497 356 471 1085 497 356 472 356 471 1085 497 356 470 357 474 1084 498 356 471 356 471 356 471 356 471 356 496 357 470 357 473 1061 520 357 470 1063 519 357 470 1064 518 1064 518 357 470 357 471 +# +# Model: Eurom_PAC_9.2 +# +name: Off +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 04 00 00 00 +# +# Model: Firstline AAS2500 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3405 1710 463 387 464 388 464 387 464 388 464 389 462 386 465 1224 464 388 463 388 463 389 463 387 464 388 464 387 465 387 464 389 462 389 463 388 464 388 463 1226 462 388 463 1225 463 388 463 387 464 389 462 389 462 388 463 387 464 387 464 389 463 388 463 388 464 1226 462 1224 464 1225 462 387 464 388 464 388 463 388 463 1226 462 387 464 1225 463 388 463 1225 463 388 463 389 463 387 464 1226 462 1224 464 388 463 388 463 387 465 1224 464 388 463 1224 463 388 464 387 464 1224 464 1226 461 387 464 1224 464 388 463 1225 463 1224 464 1224 463 1224 464 1223 464 388 464 387 464 387 464 387 464 389 463 389 462 388 464 388 463 388 463 387 464 387 465 387 464 388 463 387 464 388 463 388 464 388 463 1225 463 388 463 1225 463 1224 463 388 463 388 463 388 464 388 463 387 464 388 464 389 462 389 463 387 464 388 464 388 464 388 463 388 464 387 464 387 465 388 463 387 464 1225 463 1225 463 1225 462 387 464 389 462 388 463 387 465 389 462 388 463 388 464 389 463 388 463 388 464 387 464 389 462 389 463 387 464 388 463 388 463 388 464 389 462 389 463 389 462 389 463 389 462 388 463 1224 464 1225 463 1224 464 389 462 1224 464 394 437 +# +# Model: Frico PA2510E08 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 272 76189 1130 2004 1129 2008 2141 2008 1127 966 1125 967 2138 992 1094 999 2101 1004 1085 +# +# Model: Friedrich +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5624 5582 567 553 568 550 571 550 561 557 564 584 537 554 567 1672 571 1671 562 554 567 553 568 1696 537 1676 567 1673 570 1668 565 554 567 556 565 1671 562 557 564 1676 567 1671 562 584 537 555 566 1673 570 552 569 1666 567 1671 562 1676 567 1671 572 548 563 556 565 1672 571 554 567 547 564 556 565 1674 569 549 562 558 563 1676 567 551 570 554 568 548 563 557 564 555 566 554 567 1672 571 1667 566 1674 569 1673 570 545 566 554 567 1671 572 1667 566 554 567 1672 561 557 564 1677 566 +# +# Model: Friedrich 4235h +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3232 1451 581 1008 580 1007 581 326 494 326 494 326 494 1009 579 325 495 326 494 1034 579 1010 578 325 494 1012 576 326 494 326 494 1013 575 1013 576 326 494 1013 575 1013 575 326 494 326 494 1013 575 326 494 326 494 326 494 1013 575 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1013 575 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1013 575 1014 574 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1014 574 1014 574 326 494 326 494 326 494 326 494 1014 574 1014 574 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1015 573 326 494 1015 573 1015 573 1015 573 70108 3201 1510 574 1014 574 1015 574 326 494 326 494 326 494 1014 574 326 494 326 494 1014 574 1014 574 326 494 1014 574 326 494 326 494 1014 574 1015 573 326 495 1015 573 1015 573 326 494 326 494 1015 573 326 494 326 494 1015 573 326 494 326 494 326 494 327 493 326 494 326 494 326 494 325 495 326 494 325 495 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1015 573 326 494 326 494 1015 573 1015 573 326 494 326 494 326 494 326 494 1015 573 326 494 1016 572 1016 572 1016 572 1016 572 326 494 326 494 326 494 326 494 1016 572 326 494 1016 572 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 495 326 494 327 493 326 494 1018 570 326 494 326 494 1018 570 1017 571 326 495 326 494 326 493 326 496 +# +# Model: Friedrich AKB73756214 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3267 9589 679 1380 626 416 546 470 546 469 547 1512 547 469 571 445 571 445 572 1462 569 1490 570 447 544 473 566 450 567 450 568 449 594 448 568 448 568 448 568 448 569 448 569 448 567 1468 566 450 567 1493 568 449 567 449 568 448 568 1466 567 +# +# Model: Frigidaire RG15D_AC +# +name: Off +type: parsed +protocol: NECext +address: 08 F5 00 00 +command: 11 EE 00 00 +# +# Model: Fujidenzo FEA5001_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9464 4442 637 583 601 584 601 584 601 584 600 582 603 580 604 580 630 555 629 1637 627 1665 598 1668 571 1696 569 1696 594 1672 594 1672 592 1674 593 1673 593 591 594 1673 593 1672 593 591 591 593 569 616 568 616 593 593 591 1674 593 592 593 593 592 1675 592 1675 592 1700 568 1700 568 39559 9409 2272 567 96244 9461 2246 594 +# +# Model: Fujitsu AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3302 1618 435 393 436 391 438 1207 431 396 433 1213 435 392 437 391 438 388 441 1206 432 1214 434 392 437 389 440 388 431 1215 433 1213 435 392 437 390 439 388 431 396 433 395 434 392 437 390 439 388 431 396 433 394 435 392 437 390 439 388 441 1205 433 395 434 392 437 389 440 388 431 396 433 394 435 392 437 1209 439 388 431 397 432 394 435 393 436 1210 438 387 432 396 433 394 435 392 437 390 439 388 431 1215 433 394 435 1211 437 1208 440 1205 433 1213 435 1211 437 1209 439 +# +# Model: Fujitsu AC_ASU18RLF +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3306 1624 406 411 413 404 410 1224 414 403 411 1223 405 412 412 405 409 408 406 1228 410 1224 414 402 412 406 408 409 405 1229 409 1224 414 403 411 407 407 410 414 402 412 406 408 409 405 412 412 404 410 408 406 411 413 403 411 406 408 409 405 1230 408 408 406 411 413 404 410 407 407 410 414 403 411 406 408 1226 412 405 409 408 406 411 413 404 410 1224 414 403 411 406 408 409 405 412 412 405 409 408 406 1228 410 407 407 1227 411 1222 406 1229 409 1198 440 1220 408 1227 411 +# +# Model: GE AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8521 4240 539 1600 542 1572 539 1600 542 567 514 1572 539 570 511 1602 519 1593 600 4181 515 567 514 1599 543 539 511 571 520 563 508 575 485 570 511 572 592 19298 8523 4263 536 1577 544 1595 537 1577 544 564 517 1597 535 548 512 1627 515 1599 512 4245 544 565 485 1627 515 569 512 571 489 567 514 569 512 571 490 567 596 19304 8527 4262 537 1576 545 1595 537 1577 544 565 516 1623 509 548 512 1601 541 1626 485 4245 544 565 485 1601 541 569 512 571 490 567 514 569 512 570 491 566 597 19297 8524 4265 534 1580 541 1598 544 1569 542 567 514 1626 516 541 519 1620 512 1629 492 4238 541 568 492 1594 538 572 509 574 486 571 510 572 509 574 487 570 594 +# +# Model: GE Window_AC +# +name: Off +type: parsed +protocol: NECext +address: 98 6F 00 00 +command: 19 E6 00 00 +# +# Model: Gree airco +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9018 4484 651 553 653 555 650 1657 650 554 652 554 652 1656 652 1656 651 553 653 1656 651 1656 652 1657 651 554 652 553 653 554 652 553 652 557 650 554 652 554 652 554 652 554 652 554 652 1655 652 555 651 555 652 555 651 553 652 556 650 554 652 1656 652 553 653 1655 653 552 654 555 651 1656 652 555 651 19990 652 1657 650 1658 650 553 652 1655 652 554 652 554 652 554 652 554 652 554 652 555 651 554 652 553 654 556 650 553 653 1655 652 554 652 554 652 554 652 555 651 556 651 554 651 553 653 555 651 554 652 554 651 554 652 554 652 556 651 1655 652 553 652 554 651 1656 652 +# +# Model: Gree airco_lightoff +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9075 4405 725 1580 728 478 728 480 726 481 725 478 728 479 728 479 727 478 728 478 727 1583 724 479 727 1581 727 480 727 480 726 478 727 480 726 479 727 481 725 479 728 478 728 478 728 478 727 479 727 480 726 477 729 478 728 478 728 480 726 1581 726 480 726 1580 727 479 727 478 727 1582 725 480 726 19911 650 1657 650 1657 650 556 650 558 647 556 650 556 649 557 649 555 650 556 649 555 651 556 650 556 650 558 649 555 651 1658 650 555 650 555 650 556 650 555 651 556 650 557 649 556 650 557 649 553 653 555 651 554 651 583 649 555 651 1657 650 555 651 556 650 1656 651 +# +# Model: Gree KFR-70G-A1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9081 4414 685 1618 682 509 680 536 654 537 653 537 654 537 653 1675 627 564 626 1676 653 1649 653 1649 653 537 653 538 653 537 653 537 654 537 653 537 654 537 653 537 653 538 653 538 653 537 653 538 653 538 653 537 653 537 654 538 652 538 653 1649 653 538 652 1676 626 538 652 538 653 1676 653 538 652 +# +# Model: GREE YAPOF +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8995 4545 627 1658 629 1660 627 581 628 554 629 1660 627 1659 628 1685 628 555 628 1659 628 581 628 554 629 1660 627 554 629 581 628 555 628 554 629 581 628 555 628 555 628 581 628 555 628 1660 627 581 628 554 629 555 627 581 628 554 628 556 627 1686 627 555 627 1660 627 557 626 581 628 1658 629 555 628 20037 627 555 628 581 628 555 628 556 627 1687 626 556 627 555 628 582 627 556 627 556 626 582 627 555 628 557 626 581 628 1659 628 555 628 555 628 581 628 554 628 556 627 584 625 556 626 556 627 582 627 557 625 555 627 582 627 555 627 1661 625 1686 627 556 653 1632 655 39992 8996 4518 650 1660 653 1635 627 556 627 583 625 1660 628 1661 626 1660 627 581 628 1658 629 557 625 557 626 1686 627 556 627 555 628 582 627 556 627 556 627 582 627 555 628 557 626 582 627 1661 626 557 626 583 626 556 627 556 627 581 628 556 627 1661 626 1660 627 1687 626 555 628 556 627 1688 625 557 626 20012 626 581 628 556 627 556 626 583 626 556 626 556 627 583 626 556 627 557 626 582 627 555 628 556 627 582 627 556 627 556 627 583 626 556 626 556 627 584 625 556 627 556 627 581 628 1661 626 1661 626 582 627 556 627 555 628 583 626 557 625 1660 627 557 626 583 626 +# +# Model: Haier AC_HWE08XCR-L +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8714 4338 579 538 551 540 549 542 557 1613 555 1615 553 538 551 540 549 1619 549 1620 558 1612 556 1614 554 1617 551 540 559 1611 557 1612 556 531 558 1613 555 536 553 538 551 1620 558 1612 556 535 554 537 552 535 554 537 552 1618 550 1620 558 533 556 536 553 1617 551 1617 551 1608 549 +# +# Model: Hisense DG11J1-99_celsius +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8987 4555 560 1694 561 1698 561 574 562 578 562 584 560 586 561 590 560 1705 561 567 563 1698 561 1703 559 577 563 579 564 586 561 588 563 578 562 568 562 572 561 574 563 580 561 582 562 587 561 591 560 579 561 568 562 1699 561 576 561 578 562 581 562 586 561 589 561 580 560 567 563 571 562 576 561 580 561 582 562 587 560 590 561 579 561 567 562 571 562 575 562 578 562 581 563 586 561 588 563 559 563 8017 563 568 561 571 562 574 562 578 562 582 561 585 561 590 561 1702 564 567 562 572 561 574 562 580 560 581 562 584 563 589 562 578 562 569 561 573 561 575 561 577 563 582 562 586 561 589 561 577 563 569 561 571 562 574 562 578 562 582 561 586 561 590 561 579 561 567 563 571 562 575 562 577 563 582 561 585 562 589 562 577 563 568 562 571 562 575 562 578 563 582 562 585 562 589 562 578 562 569 561 571 562 575 562 579 561 581 562 585 562 588 563 578 562 568 562 1697 563 576 561 578 562 580 564 585 562 588 562 1687 562 8017 561 568 561 571 562 576 561 578 562 581 562 585 562 588 563 579 562 569 561 572 562 575 562 579 562 582 562 585 563 589 562 578 562 568 562 571 562 574 563 579 562 582 562 585 563 589 562 580 561 569 561 572 562 574 563 578 562 582 562 584 564 589 562 578 562 567 563 571 563 575 562 1705 562 583 561 584 563 589 562 579 562 568 562 571 562 574 563 578 563 581 563 586 562 588 563 578 563 568 562 572 562 574 563 1706 561 581 563 585 562 588 563 561 561 +# +# Model: Hisense room_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9115 4510 641 1653 641 1632 614 531 611 539 608 543 608 546 608 550 608 1673 608 529 608 1666 608 1670 607 540 607 544 607 570 584 574 584 564 584 553 584 557 584 1694 584 563 584 567 584 1704 584 1708 583 564 583 1686 584 557 584 560 584 564 583 1701 583 1704 584 1708 584 564 584 553 583 557 583 561 583 564 583 567 584 571 584 574 584 564 583 553 584 557 583 561 583 564 584 568 583 572 583 575 583 546 583 8052 583 554 583 557 583 561 583 565 583 568 583 572 582 575 583 1699 582 1688 583 558 583 561 583 565 582 1702 583 1706 582 575 583 565 583 555 582 558 582 562 582 565 582 569 582 572 582 576 582 566 582 555 582 558 582 562 581 565 582 569 581 573 581 576 581 566 581 555 582 559 581 562 582 566 581 570 581 573 581 577 581 566 581 556 581 560 581 563 581 567 580 570 581 574 580 578 580 1701 580 1690 581 560 580 564 580 1701 580 1704 580 575 580 579 579 568 579 1691 579 561 580 1698 579 1702 578 1705 579 1709 579 580 578 575 554 8080 554 582 555 586 554 589 555 593 554 596 555 600 554 604 554 593 555 1717 554 586 554 590 554 593 554 597 554 601 554 604 554 593 555 583 554 586 554 590 554 594 554 597 554 601 553 604 554 594 553 584 553 587 554 591 553 594 554 598 553 602 553 605 553 595 553 584 553 588 552 592 552 1729 553 599 552 602 553 606 552 595 552 585 552 588 552 592 552 620 527 599 552 628 526 631 527 621 526 1744 526 614 526 618 526 1754 526 624 527 628 526 631 527 604 526 +# +# Model: Hisense window_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8963 4401 561 543 558 545 566 537 564 540 561 542 559 1652 561 1650 563 540 561 543 558 545 567 537 564 1647 566 1645 558 1654 559 544 567 536 565 1646 567 1644 559 545 567 537 564 1647 566 537 564 540 561 542 559 545 567 537 564 539 562 541 560 544 567 536 565 538 563 541 560 543 558 545 567 538 563 540 561 542 559 545 567 537 564 539 562 542 559 544 568 536 565 539 562 541 560 543 569 536 565 538 563 541 560 543 558 545 566 538 563 540 561 542 559 545 566 537 564 539 562 542 559 544 567 536 565 539 562 541 560 544 568 536 565 538 563 541 560 543 558 545 567 538 563 540 561 543 558 545 567 537 564 540 561 542 559 544 568 536 565 539 562 541 560 570 542 536 565 538 563 541 560 543 558 546 565 538 563 540 561 543 558 571 541 537 564 540 561 542 559 545 567 537 564 539 562 542 559 544 568 536 565 539 562 541 560 544 568 536 565 538 563 541 560 543 569 535 566 538 563 541 560 543 569 535 566 538 563 541 560 543 569 536 565 538 563 541 560 544 567 536 565 539 562 542 559 544 567 537 564 539 562 542 559 544 568 537 564 539 562 542 559 544 568 537 564 539 562 542 559 545 567 537 564 540 561 542 559 545 566 537 564 540 561 543 558 545 567 538 563 541 560 543 569 536 565 538 563 541 560 544 568 536 565 539 562 542 559 544 568 1645 558 546 566 1646 567 537 564 540 561 1651 562 541 560 544 568 563 538 539 562 541 560 544 567 536 565 539 562 541 560 544 567 537 564 539 562 542 559 544 568 537 564 540 561 542 559 545 566 1645 558 546 566 1646 567 537 564 540 561 1651 562 541 560 544 588 +# +# Model: Hitachi RAK35 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 479 397 30131 50087 3427 1674 447 1253 447 483 446 483 446 483 420 483 447 483 447 483 447 483 447 483 420 510 419 483 447 483 446 1254 446 483 420 510 419 484 446 483 447 484 445 483 446 484 419 1255 445 1254 446 484 445 484 445 484 419 484 446 484 446 484 445 484 446 484 445 1255 418 484 446 1255 445 1255 418 1255 445 1255 445 1254 446 1255 419 484 446 1255 445 1255 418 511 418 485 445 484 445 484 446 484 445 485 418 511 418 484 446 1255 445 1255 418 1255 445 1255 444 1256 444 1256 417 1256 445 1255 443 487 418 511 419 485 444 1255 445 485 445 485 420 509 418 485 445 1255 445 485 444 485 445 1256 417 512 418 485 444 485 445 1256 417 1256 444 485 444 485 420 509 444 486 418 511 418 485 445 485 444 1256 444 486 418 512 417 485 420 509 445 485 445 485 445 485 417 1256 444 486 444 485 445 1256 417 1256 443 486 444 485 444 486 444 485 444 485 418 485 444 485 444 485 444 486 443 486 417 1257 420 509 420 510 444 485 444 486 444 485 418 485 445 485 443 487 444 1256 444 1256 417 485 442 487 420 510 444 485 444 486 417 1256 420 1280 443 1257 417 512 393 510 444 486 442 487 443 486 419 510 417 513 419 484 419 510 444 486 419 510 419 510 393 537 415 488 420 510 418 510 419 510 419 510 392 537 392 511 445 484 419 511 418 511 418 511 392 538 391 511 419 511 419 511 418 511 418 511 391 511 419 512 418 511 418 511 419 511 418 512 391 512 418 512 417 512 417 512 418 512 417 512 391 512 418 512 417 512 417 512 417 512 391 539 390 513 416 513 417 512 417 513 417 537 366 564 365 514 416 537 392 537 392 538 392 538 365 564 365 538 392 538 392 538 391 538 391 538 365 564 365 538 392 538 391 1309 391 1308 365 538 391 538 392 538 391 538 392 538 365 565 364 538 392 538 391 539 391 1309 364 1309 391 538 391 539 391 538 391 538 391 539 364 538 392 539 390 539 391 539 390 539 364 565 364 539 391 539 390 1309 391 539 390 1309 364 539 391 539 390 539 391 539 498 +# +# Model: Hitach RAK-18QH8 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3295 1683 467 1196 440 407 467 404 470 407 467 405 469 406 467 405 468 409 468 407 467 406 468 405 469 405 469 1168 468 403 470 406 468 411 467 404 470 406 468 404 469 406 468 405 469 406 467 404 470 409 469 405 469 404 470 405 468 406 468 407 467 405 468 1167 469 410 467 1168 468 1167 469 1167 469 1167 468 1168 468 1167 469 407 467 1173 467 1168 468 1169 467 1166 470 1168 468 1168 468 1168 468 1167 469 1171 469 405 469 409 464 404 470 432 441 405 469 404 470 407 467 410 468 404 470 405 468 1169 467 1168 468 404 469 405 469 1167 469 1169 471 1167 469 1167 469 405 469 403 470 1167 469 1168 468 406 468 409 469 404 469 1168 468 404 469 404 469 1168 468 406 467 406 468 1171 469 1169 467 404 469 1167 469 1167 469 404 469 1169 467 1195 441 409 469 1169 467 1168 468 405 469 404 470 1168 468 404 470 405 469 409 469 405 469 404 470 1167 469 1169 467 403 470 1169 467 1168 468 1171 469 404 469 405 469 1168 468 1168 468 1168 468 404 470 1169 467 408 469 1169 467 1167 469 404 470 405 469 405 468 1168 468 405 468 1173 467 406 467 406 468 407 466 406 468 406 468 406 468 406 468 410 468 1168 468 1168 468 1168 468 1169 467 1169 467 1168 468 1169 467 1172 468 405 468 406 468 407 467 407 467 406 468 405 469 406 468 411 466 1168 468 1167 469 1168 468 1169 467 1195 440 1169 467 1168 468 1171 469 405 469 407 467 407 467 406 467 407 467 406 468 406 467 410 468 1168 468 1169 467 1169 467 1169 467 1168 468 1168 468 1167 469 1172 468 407 467 404 469 405 469 405 468 406 468 406 468 406 467 410 468 1169 467 1169 467 1168 468 1168 468 1167 469 1168 468 1167 469 1172 468 405 468 405 469 407 466 406 468 407 466 406 468 406 468 410 468 1168 468 1168 468 1170 466 1168 468 1168 468 1168 468 1168 468 1173 467 1168 468 405 468 1169 467 406 467 406 468 1168 468 406 468 410 467 408 466 1168 468 407 467 1168 468 1170 466 406 468 1169 467 1173 467 1169 467 405 468 406 467 407 467 1170 466 1170 466 1170 466 1173 467 406 468 1169 467 1171 465 1169 467 406 468 406 467 406 467 411 467 406 468 406 467 407 467 407 466 407 467 407 467 408 466 412 465 1170 466 1170 466 1169 467 1169 467 1170 466 1170 466 1169 467 1174 466 407 466 408 466 408 466 407 466 407 467 407 466 406 468 411 467 1170 466 1170 466 1171 465 1168 468 1170 466 1169 467 1170 466 1174 466 407 467 408 466 407 466 407 467 407 467 408 466 407 466 412 466 1171 465 1169 467 1170 465 1170 466 1171 465 1170 465 1171 465 1174 466 1170 466 1169 467 408 465 408 465 408 465 408 466 408 465 414 464 408 466 407 467 1170 466 1170 466 1170 466 1170 466 1171 465 1174 465 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3376 1605 463 1172 464 411 463 408 466 407 466 412 462 409 465 409 465 415 463 409 464 411 463 408 465 409 464 1171 465 410 463 410 464 413 464 437 437 411 462 410 464 409 464 410 463 410 464 410 463 413 465 409 465 413 460 410 464 409 465 410 464 409 465 1173 463 415 462 1171 465 1174 462 1174 462 1173 463 1173 463 1174 462 412 462 1177 463 1175 461 1173 462 1172 464 1173 463 1174 462 1172 464 1174 462 1177 463 411 463 409 464 410 464 411 463 410 464 410 463 411 463 414 463 410 463 412 462 1174 462 1174 462 410 464 412 462 1173 463 1177 463 1174 462 1173 463 411 462 411 463 1174 462 1174 462 413 460 417 461 414 459 1174 462 411 463 413 460 1174 462 412 461 413 461 1179 461 1175 461 412 462 1172 464 1174 462 414 460 1174 462 1172 464 414 464 1172 464 1172 464 409 465 409 465 1172 464 409 464 408 466 415 463 408 465 410 464 1171 465 1171 465 408 466 1172 464 1172 464 1176 464 409 464 409 465 409 465 409 465 409 465 409 465 1172 464 412 466 1172 464 1171 465 1174 462 1171 465 1172 464 1172 464 409 465 1176 464 411 463 408 465 408 466 408 466 409 465 409 465 409 464 414 464 1172 464 1172 464 1172 464 1172 464 1173 463 1171 465 1174 462 1177 462 409 465 409 465 411 462 410 464 410 464 409 465 409 464 414 464 1172 464 1172 464 1172 464 1171 465 1173 463 1173 463 1172 464 1176 464 409 464 410 464 410 463 410 463 410 464 410 464 409 464 414 464 1172 464 1173 463 1173 463 1172 464 1172 464 1173 463 1173 463 1176 464 410 463 411 463 411 463 409 465 409 464 411 462 411 463 414 463 1173 463 1172 464 1173 463 1172 464 1172 464 1173 463 1174 461 1178 462 410 463 410 463 412 462 411 463 410 463 409 464 411 463 415 463 1173 463 1174 462 1173 463 1173 462 1173 463 1174 462 1172 464 1178 462 1171 465 1174 462 411 462 411 463 1174 462 410 463 1173 463 414 463 412 462 410 463 1174 462 1173 463 411 463 1173 463 410 464 1177 463 1174 462 411 463 411 462 411 463 1174 462 1174 462 1175 461 1178 462 412 461 1173 463 1172 464 1175 461 412 462 411 462 411 463 415 462 411 463 412 462 412 461 412 462 412 462 411 463 412 462 416 461 1174 462 1173 463 1174 462 1174 461 1174 462 1175 461 1174 462 1181 459 412 462 411 463 412 462 412 461 411 462 411 463 413 460 415 463 1174 462 1175 461 1176 460 1176 460 1174 462 1174 461 1174 462 1178 461 413 461 413 460 413 460 414 460 412 461 412 462 412 462 417 460 1175 461 1175 461 1175 461 1175 460 1176 460 1175 461 1174 462 1179 461 1175 460 1177 435 436 461 413 460 413 437 437 460 412 438 440 461 414 436 437 437 1199 460 1177 459 1176 460 1176 437 1199 437 1202 437 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3376 1603 465 1198 438 408 489 384 490 382 468 407 467 408 466 407 467 439 438 408 490 384 489 384 466 435 439 1169 467 408 466 408 489 388 467 411 463 406 467 409 465 408 490 385 465 409 488 383 467 412 489 383 467 407 466 409 465 408 466 408 466 408 466 1173 463 412 466 1171 465 1170 466 1171 465 1170 466 1170 466 1170 466 408 466 1175 465 1169 467 1171 465 1171 465 1173 463 1172 464 1170 466 1170 466 1176 463 409 465 409 465 410 463 407 467 409 465 411 462 407 467 413 464 407 466 410 464 1172 464 1173 463 408 466 411 463 1171 465 1175 465 1171 465 1171 465 408 465 410 464 1171 465 1172 463 408 465 413 465 408 466 1171 465 409 465 408 465 1173 463 410 464 408 465 1175 465 1169 467 409 464 1172 464 1172 463 408 466 1172 464 1172 464 412 466 1171 465 1172 464 409 464 409 464 1171 465 411 462 410 464 414 463 409 464 411 463 1172 463 1174 462 411 463 1173 463 1173 463 1177 463 410 463 411 463 1172 464 1173 463 1178 458 411 462 1172 464 416 462 1174 462 1173 463 412 461 411 463 411 463 1174 462 411 462 1178 462 411 462 410 464 413 461 411 463 410 464 412 462 411 463 417 461 1173 463 1175 461 1173 463 1173 463 1172 464 1173 463 1175 461 1177 463 411 463 410 464 410 463 410 464 410 464 411 463 410 463 414 464 1173 463 1173 463 1174 462 1172 464 1175 461 1173 463 1175 461 1176 464 411 463 410 463 412 461 411 462 411 463 411 462 414 460 415 463 1174 462 1174 462 1173 463 1173 463 1174 462 1173 463 1173 463 1178 462 410 463 411 463 411 463 411 463 412 462 410 463 411 462 416 462 1173 463 1174 462 1174 462 1174 462 1173 463 1174 462 1173 463 1177 463 410 463 438 436 410 463 412 462 410 463 411 463 412 461 416 462 1174 462 1174 462 1175 461 1173 463 1174 462 1173 463 1174 462 1179 461 1175 461 1174 462 411 463 412 462 1176 459 412 462 1174 461 415 463 411 462 411 463 1175 461 1174 462 411 463 1175 460 412 462 1178 461 1176 460 411 463 412 461 412 462 1174 462 1174 462 1174 462 1179 461 412 461 1174 462 1174 462 1175 461 413 460 412 462 412 462 417 461 411 463 412 462 412 461 413 460 412 462 412 462 413 461 416 461 1176 461 1175 461 1174 462 1175 461 1174 462 1176 460 1174 461 1180 460 412 461 414 460 412 462 414 459 413 461 413 461 413 460 417 461 1176 459 1176 460 1177 459 1176 460 1175 460 1175 461 1176 437 1203 460 414 459 413 461 413 460 413 437 436 461 413 438 437 437 440 461 1176 437 1198 438 1198 438 1198 461 1175 461 1177 459 1177 436 1203 437 1201 458 1175 438 436 438 436 438 437 436 437 437 436 437 441 437 438 436 436 438 1198 438 1200 436 1199 437 1199 437 1199 437 1203 436 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3297 1684 411 1224 412 461 413 462 412 460 413 461 413 461 413 463 411 466 412 461 412 461 413 461 413 461 413 1223 413 461 412 465 409 466 412 461 412 462 467 406 412 461 413 462 467 407 466 406 412 465 467 406 468 406 468 406 468 405 468 408 466 406 467 1168 412 493 440 1167 469 1168 468 1168 468 1167 468 1168 468 1168 468 407 466 1171 469 1167 469 1170 466 1168 468 1168 468 1168 468 1168 468 1168 468 1173 467 407 466 407 467 408 465 406 468 406 468 404 470 405 469 409 469 405 468 406 468 1168 468 1167 469 406 468 405 468 1166 470 1172 468 1166 470 1171 465 406 467 406 468 1168 468 1168 468 405 469 410 467 405 469 1167 469 406 468 405 469 1168 468 405 468 406 468 1173 467 1169 467 406 467 1169 467 1169 467 407 467 1169 467 1169 467 412 466 1167 469 1169 467 408 466 407 467 1169 467 408 465 408 466 411 466 407 467 409 465 1171 465 1169 467 406 467 1197 439 1171 465 1174 466 434 440 405 468 406 468 405 469 407 467 407 466 406 468 1172 468 1170 466 1168 468 1168 468 1167 469 1169 467 1167 469 1171 465 411 467 405 469 405 469 408 466 405 469 407 466 405 469 405 469 410 468 1168 468 1168 468 1168 468 1169 467 1169 467 1168 468 1169 467 1173 467 406 468 405 468 406 467 407 467 405 469 406 468 406 467 411 467 1169 467 1168 468 1168 468 1167 469 1168 468 1168 468 1169 467 1173 467 406 468 405 468 404 470 405 469 406 468 405 468 406 468 409 468 1168 468 1168 468 1169 467 1169 467 1168 468 1170 465 1169 467 1172 468 405 468 407 467 406 467 406 467 407 467 406 467 406 468 410 467 1168 468 1170 466 1168 468 1168 468 1169 467 1169 467 1168 468 1173 467 406 468 406 468 407 467 406 468 408 466 406 468 406 468 411 466 1170 466 1170 466 1168 468 1169 467 1169 467 1170 466 1169 467 1174 466 406 468 1169 467 1168 468 406 468 1168 468 406 468 1169 467 410 467 1170 466 407 467 406 468 1169 467 406 467 1169 467 407 466 1172 468 1170 466 409 465 407 467 407 466 1169 467 1170 466 1169 467 1173 467 407 466 1172 464 1171 465 1169 467 407 466 407 467 406 467 411 467 406 468 408 465 407 467 407 467 407 467 407 467 407 466 412 466 1169 467 1170 466 1170 466 1169 467 1169 467 1169 467 1170 466 1174 466 408 465 408 466 408 466 407 466 409 465 406 467 409 465 411 466 1170 466 1170 466 1170 466 1171 465 1170 466 1171 465 1170 466 1174 466 407 466 408 466 408 466 408 465 407 467 408 466 407 467 411 466 1169 467 1170 466 1170 466 1170 466 1171 465 1170 466 1170 466 1174 466 1170 466 1170 466 408 466 409 465 407 467 407 466 409 465 412 466 409 465 409 465 1171 465 1170 466 1171 465 1171 465 1171 465 1175 464 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3376 1602 466 1169 467 410 463 406 468 405 469 406 468 408 466 407 466 412 466 407 467 407 467 407 466 408 466 1171 465 406 468 406 467 411 467 407 467 407 467 407 467 407 467 409 465 406 467 406 467 412 466 406 468 406 468 407 467 406 467 407 467 407 466 1170 466 410 467 1171 465 1170 466 1169 467 1169 467 1171 465 1171 465 408 466 1174 466 1170 466 1169 467 1171 465 1169 467 1171 465 1169 467 1172 464 1173 467 408 466 408 466 410 464 407 467 408 465 409 465 407 466 412 466 408 465 409 465 1172 464 1171 465 408 466 411 463 1170 466 1175 464 1173 463 1171 465 408 465 408 466 1171 465 1172 464 411 463 412 466 409 465 1170 466 408 466 408 466 1171 465 408 465 409 465 1175 464 1171 465 410 463 1171 465 1171 465 408 466 1171 465 1171 465 412 465 1169 467 1170 466 408 466 410 464 1170 466 409 464 408 466 412 466 408 466 407 467 1171 465 1170 466 407 467 1171 465 1171 465 1175 465 436 438 408 466 1170 466 1170 466 1171 465 408 466 1170 466 413 464 1172 464 1173 463 409 464 408 466 411 462 1172 464 409 465 1177 463 409 465 410 464 409 464 409 465 409 465 410 464 409 465 414 464 1172 464 1172 464 1173 463 1172 464 1171 465 1173 463 1170 466 1177 463 409 464 410 464 408 465 437 437 410 464 410 464 410 464 413 464 1171 465 1172 464 1171 465 1173 463 1172 464 1175 461 1173 463 1176 464 410 464 410 464 411 462 408 466 410 464 409 464 410 464 414 464 1173 463 1172 463 1173 463 1175 461 1172 464 1172 464 1173 463 1177 463 411 463 410 464 410 463 410 464 409 465 410 464 410 464 414 463 1172 464 1173 463 1174 462 1173 463 1173 463 1173 463 1174 462 1178 462 409 464 410 464 411 462 412 462 411 462 412 462 411 462 414 463 1173 463 1172 464 1173 463 1173 463 1174 462 1172 464 1173 462 1177 463 412 461 1174 462 1174 462 411 463 1173 462 411 463 1175 461 415 463 1173 463 411 462 412 462 1174 462 411 463 1173 463 411 463 1178 462 1173 463 412 462 410 463 411 463 1174 462 1173 463 1174 462 1178 462 411 462 1174 462 1174 462 1174 461 412 462 412 462 411 463 416 462 412 462 411 462 412 462 412 461 411 463 412 462 412 461 416 461 1174 462 1174 462 1175 461 1175 461 1175 461 1174 462 1174 462 1179 461 412 461 413 461 412 461 412 462 412 461 414 460 412 461 416 462 1176 460 1174 462 1174 462 1175 460 1174 462 1175 460 1177 459 1179 461 412 461 413 461 413 460 413 461 413 461 412 461 412 462 416 461 1176 460 1174 462 1174 462 1175 461 1176 459 1177 459 1175 437 1203 460 1175 461 1176 436 438 459 414 460 414 460 413 460 414 459 418 437 437 437 437 437 1199 437 1200 436 1200 436 1200 436 1200 436 1202 437 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3378 1603 466 1170 466 408 465 406 468 407 467 407 467 408 466 407 467 412 466 407 467 408 465 406 468 406 468 1170 466 407 467 406 468 413 465 410 464 407 466 409 465 407 467 406 468 409 464 408 466 411 467 407 466 405 469 407 467 406 467 408 466 406 468 1170 466 412 466 1169 466 1171 465 1171 465 1170 489 1146 466 1172 488 383 467 1177 487 1149 487 1145 466 1172 487 1147 465 1169 466 1174 486 1147 465 1174 466 409 465 409 465 409 464 409 464 408 465 409 465 409 464 415 463 409 464 409 465 1170 466 1171 465 409 465 410 463 1172 463 1177 463 1172 464 1199 436 413 461 410 464 1172 463 1172 464 411 463 415 463 411 463 1173 463 411 462 410 464 1174 461 409 465 411 463 1176 464 1173 463 410 464 1172 464 1172 464 437 436 1174 462 1173 462 415 463 1174 462 1173 463 411 463 413 461 1172 464 411 462 410 463 415 463 409 465 411 463 1172 464 1172 464 409 465 1173 463 1174 462 1176 464 409 465 409 465 1171 465 1170 466 1173 463 408 466 1172 464 412 465 1171 465 1172 464 407 467 408 465 408 466 1171 465 408 466 1175 465 408 465 407 466 408 466 409 465 408 465 408 466 407 467 412 466 1170 466 1173 463 1170 466 1170 466 1170 466 1170 466 1171 465 1174 466 408 465 407 467 409 465 408 465 409 464 408 466 408 466 414 464 1171 465 1170 466 1171 465 1170 466 1172 464 1171 465 1170 466 1176 464 409 465 410 463 408 466 411 463 409 464 409 465 408 466 413 464 1170 466 1172 464 1171 465 1170 466 1171 465 1172 464 1170 466 1178 462 408 465 410 464 409 465 408 466 409 465 409 465 409 465 413 465 1171 465 1172 464 1172 464 1173 463 1171 465 1172 464 1171 465 1176 464 407 467 409 465 409 465 410 463 409 465 409 465 411 463 413 464 1173 463 1174 462 1172 464 1172 464 1171 465 1171 465 1172 464 1175 465 410 464 1172 464 1174 462 409 465 1172 464 410 463 1170 466 414 463 1172 464 410 464 409 465 1172 464 411 462 1172 464 409 465 1175 465 1172 464 410 463 410 464 410 464 409 464 1173 463 1173 463 1176 464 410 464 1173 463 1171 465 1171 465 1172 464 409 464 410 464 414 464 410 464 410 464 410 464 409 465 410 463 410 464 411 463 414 464 1173 463 1172 464 1173 463 1172 464 1172 464 1173 463 1172 464 1177 463 410 464 410 463 410 463 411 463 410 464 411 463 409 464 414 464 1173 463 1173 462 1174 462 1173 463 1173 463 1173 463 1174 462 1177 463 411 463 412 462 410 463 411 463 411 462 412 462 410 463 414 464 1173 463 1174 462 1173 463 1172 464 1174 462 1173 463 1173 463 1178 462 1173 463 1174 462 411 462 411 463 411 462 410 464 411 463 415 463 411 463 412 462 1174 462 1173 463 1174 462 1175 461 1173 463 1178 461 +# +# Model: Inventum AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4419 4378 562 1618 568 552 541 1619 567 1640 536 533 560 535 558 1649 537 559 534 537 566 1619 567 554 539 530 563 1618 568 1613 563 532 561 1626 560 537 566 554 539 530 563 557 536 536 1622 564 1616 560 1624 562 1625 561 1620 566 1615 561 1621 565 1616 560 534 559 536 567 529 564 559 534 1625 561 533 560 1625 561 1620 566 528 565 530 563 1620 566 530 563 532 561 1624 562 532 561 1650 536 1645 541 1614 562 1620 566 5226 4418 4372 558 1622 564 557 536 1623 563 1618 568 527 566 529 564 1617 559 564 539 531 562 1623 563 558 535 533 560 1621 565 1616 560 535 568 1619 567 529 564 557 536 532 561 560 533 561 542 1614 562 1619 567 1642 534 1628 569 1613 563 1619 567 1614 562 1619 567 528 565 530 563 533 560 563 540 1619 567 527 566 1619 567 1614 562 533 560 535 558 1625 561 536 567 527 566 1619 567 528 565 1620 566 1615 561 1620 566 1618 558 +# +# Model: Kelon AS-24HR4SQJUL +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9159 4545 562 1674 562 1675 561 596 560 595 535 621 535 621 535 623 533 1676 560 597 535 1702 534 1703 533 622 535 622 535 622 509 648 508 597 560 596 560 596 558 1679 534 623 534 622 535 622 534 622 509 597 560 597 560 1678 558 598 558 599 532 1728 508 649 508 1729 507 597 560 597 558 598 534 623 534 623 533 623 531 625 508 649 507 597 560 597 534 623 534 623 534 623 534 623 508 649 508 649 508 571 508 8132 558 1703 533 1703 534 623 534 623 533 623 508 625 532 649 508 1677 560 597 558 599 533 1703 534 623 533 1703 533 1705 532 625 532 597 534 623 534 623 533 599 558 599 532 625 532 624 532 624 533 574 557 599 558 599 558 598 557 599 533 624 532 624 533 624 531 575 558 599 558 598 558 1703 534 1703 533 624 507 649 508 649 507 598 559 598 533 624 532 624 533 624 532 624 507 650 507 650 507 1678 559 1678 559 598 557 599 533 1705 532 1729 507 625 532 626 531 598 556 601 533 624 533 1704 532 624 532 1704 533 1704 533 1705 531 573 506 8133 558 598 558 598 533 624 532 624 533 624 532 624 507 650 507 599 558 1678 558 599 557 599 532 624 533 624 533 624 530 627 506 599 505 575 557 599 557 599 558 599 532 625 532 625 531 625 531 574 505 599 558 599 532 625 531 625 532 624 532 625 506 651 506 599 480 600 557 599 558 599 557 600 532 625 532 625 532 626 505 600 505 600 531 625 532 625 531 625 532 624 506 650 507 650 507 600 479 1681 556 600 557 600 556 600 531 626 530 650 507 626 530 574 506 +# +# Model: Unknown Model_1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3150 2991 3125 4400 619 503 617 520 591 525 591 525 591 1621 616 1613 615 1619 640 517 589 1640 588 1645 587 1643 585 1649 584 533 583 533 583 533 583 1650 583 533 583 533 583 533 583 533 583 1650 583 1646 583 1650 583 1655 583 533 583 533 584 1654 584 1646 583 1650 583 1650 583 533 584 533 583 533 583 538 583 529 583 533 583 1651 582 1651 582 1651 582 1655 583 1646 583 534 582 1655 583 1651 582 534 582 1655 583 530 582 534 582 534 582 1655 583 529 583 1651 582 1655 582 530 582 1651 582 534 582 40182 3091 3040 3116 4436 583 533 583 538 583 529 583 533 583 1650 583 1651 583 1650 583 533 583 1650 583 1650 583 1655 583 1646 583 533 583 534 583 533 583 1651 582 533 583 534 582 534 582 533 583 1651 582 1655 583 1646 583 1651 583 534 582 534 582 1651 582 1656 581 1647 582 1656 582 530 582 534 582 534 583 534 582 539 582 534 582 1647 582 1656 582 1647 582 1651 582 1652 581 535 581 1652 581 1647 582 535 582 1652 581 535 581 535 581 535 581 1652 581 535 581 1652 581 1652 581 535 581 1653 580 535 581 +# +# Model: Legion LE-F30RH-IN +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6172 7369 602 1569 602 1569 602 1569 601 1570 573 1598 574 1598 573 1597 574 1598 574 526 573 526 573 527 572 528 571 529 570 529 570 530 569 530 568 1603 569 1603 569 1603 569 1603 569 1603 568 1603 568 1603 569 1604 568 531 568 531 568 531 568 532 567 531 568 555 544 532 567 555 543 1627 544 1628 543 1628 543 1628 543 1628 544 1628 543 1628 544 1629 543 556 543 556 543 556 543 556 543 556 543 556 543 556 543 555 543 1627 544 1628 543 1629 543 556 543 555 543 1628 543 1628 543 1629 543 556 543 556 544 555 544 1628 544 1629 543 556 543 556 543 556 543 556 543 556 543 555 543 1628 543 1629 543 555 543 1628 543 1628 543 1628 543 1628 543 1629 543 557 542 556 543 1629 543 556 543 556 543 555 543 1630 542 556 542 1629 543 557 543 1630 543 557 543 1630 543 1631 542 557 542 1631 542 557 543 1630 543 557 542 1630 543 558 542 7398 543 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6173 7370 600 1571 656 1515 603 1569 602 1570 600 1571 573 1598 574 1598 573 1598 574 525 574 526 573 526 573 527 572 528 571 529 570 530 569 530 569 1603 569 1603 569 1603 569 1603 569 1603 568 1603 569 1603 569 1604 568 531 569 531 568 531 568 531 568 532 567 555 544 532 567 555 544 1603 568 1604 567 1604 567 1605 567 1628 544 1605 567 1628 543 1629 543 556 544 555 544 556 543 556 543 556 543 556 544 556 543 555 544 1629 543 555 544 1629 543 556 543 556 543 556 543 555 543 1629 543 556 544 1630 543 556 544 1629 544 1629 544 1629 544 1630 543 557 543 556 544 1629 543 1630 543 556 544 1629 543 1630 543 556 544 1629 543 1630 543 557 544 556 543 1630 543 557 543 557 543 1630 543 557 543 557 543 1630 543 557 543 1630 543 557 543 1630 543 557 543 1629 543 1630 543 557 543 1630 543 557 543 1630 543 557 543 1631 543 557 544 7399 543 +# +# Model: Lennox AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4477 4354 606 1548 606 472 605 1551 603 474 578 501 576 524 553 524 553 1602 552 1602 553 525 552 1603 552 526 551 526 551 526 551 526 551 526 551 526 551 1604 551 1604 551 526 551 526 551 1604 551 1604 551 526 551 1604 551 526 551 526 551 526 551 526 551 526 551 526 551 1604 551 1604 551 526 551 526 551 526 551 526 551 1604 576 501 577 1579 576 501 551 1604 575 1579 576 1579 551 526 551 1604 551 1604 551 1604 551 5205 4446 4385 575 501 576 1579 576 501 576 1579 576 1579 576 1578 576 1579 576 501 576 501 576 1578 576 501 576 1579 576 1578 577 1578 577 1578 576 1578 577 1578 577 501 576 501 576 1578 577 1578 577 501 576 501 576 1578 576 501 576 1578 577 1579 576 1578 577 1579 576 1578 577 1578 576 501 576 501 576 1579 576 1579 576 1579 576 1579 576 501 576 1578 576 501 576 1579 576 501 576 501 576 501 576 1579 576 501 576 501 576 502 575 +# +# Model: LG AC +# +name: Off +type: parsed +protocol: NECext +address: 81 66 00 00 +command: 81 7E 00 00 +# +# Model: LG AC_2 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8455 4196 542 1566 541 539 519 535 513 541 517 1565 541 538 520 534 514 540 518 1563 544 1565 542 538 520 533 515 539 519 535 513 541 517 536 522 532 516 538 520 533 515 539 519 535 513 1569 538 542 516 1566 541 539 519 534 514 540 518 1564 542 +# +name: Dh +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 14 EB 00 00 +# +# Model: LG LP1417GSR_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8971 4413 598 1634 595 1641 599 526 599 529 596 533 592 541 594 541 594 1646 594 525 600 1633 596 1645 595 533 592 540 595 538 597 539 596 528 597 1629 600 520 594 526 599 526 599 530 595 537 598 538 597 530 595 523 591 1639 601 524 601 527 598 1643 597 537 598 539 596 531 594 522 592 528 597 527 598 530 595 536 599 534 591 546 600 526 599 517 597 521 593 528 597 528 597 532 593 541 594 544 591 519 595 7883 598 520 594 525 600 524 590 535 600 529 596 539 596 1651 599 1639 601 515 599 522 592 531 594 532 593 538 597 536 599 538 597 528 597 518 596 525 600 523 591 534 591 537 598 533 592 546 600 527 598 518 596 523 591 531 594 531 594 536 599 534 591 545 601 526 599 517 597 521 593 529 596 530 595 536 599 532 593 542 593 532 593 522 592 526 599 522 592 535 600 532 593 540 595 541 594 533 592 524 601 519 595 528 597 529 596 535 600 534 601 537 598 527 598 1627 592 1637 592 531 594 533 592 1652 598 537 598 1652 598 1623 596 7882 599 519 595 525 600 522 592 534 591 538 597 535 590 547 599 528 597 518 596 1634 595 1639 601 525 600 530 595 539 596 541 594 532 593 523 591 529 596 527 598 528 597 533 592 541 594 542 593 532 593 525 600 522 592 531 594 533 592 538 597 535 600 537 598 527 598 518 596 522 592 530 595 1646 594 1652 598 1650 600 538 597 530 595 521 593 527 598 526 599 529 596 534 591 543 592 546 600 527 598 519 595 1636 593 1640 600 1639 601 1644 596 1651 599 539 596 514 590 +# +# Model: LG LP1419IVSM +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3196 9607 615 1444 589 428 588 428 588 430 613 1444 589 428 588 428 587 430 588 1446 614 1445 589 429 588 429 588 429 584 434 611 406 582 483 561 455 561 456 560 456 558 460 557 459 558 1503 559 457 533 1527 559 457 559 457 586 457 560 1475 584 +# +# Model: LG R12AWN-NB11 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8838 3941 624 1427 514 500 515 497 541 473 540 1487 540 474 539 474 538 476 512 1538 514 1516 544 466 514 497 514 501 514 499 514 500 513 499 513 501 487 525 514 499 512 502 487 526 512 1517 542 470 487 1539 513 499 512 503 513 499 512 1513 512 +# +# Model: LG SX122CL +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3160 1550 576 1092 576 1091 577 329 555 338 573 329 558 1065 601 329 557 329 556 1065 601 1066 575 338 547 1093 574 339 546 339 546 1095 573 1095 572 339 546 1096 572 1097 571 341 544 341 544 1124 544 342 543 342 543 1125 542 344 542 344 541 344 542 344 542 343 541 344 542 344 542 344 542 344 542 344 542 344 542 343 541 344 542 344 542 344 542 344 542 344 542 344 542 343 541 345 542 1126 542 343 541 345 541 1126 542 1126 542 344 542 344 542 344 542 343 541 344 542 345 541 1126 541 1126 541 1127 542 1127 541 343 542 345 541 344 542 345 542 1125 541 345 542 1127 541 343 542 345 541 345 541 345 541 345 541 345 541 345 541 343 541 345 541 345 541 345 541 345 541 345 541 345 541 344 541 345 541 345 541 345 541 345 541 345 541 345 541 344 540 345 541 345 541 345 541 345 541 345 541 344 541 345 541 345 541 345 541 346 540 345 541 344 540 345 541 346 540 345 541 345 541 345 540 1128 541 1126 541 345 541 346 541 1126 541 345 541 +# +# Model: Lifetime Air +# +name: Off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: Dh +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 57 00 00 00 +# +# Model: Logik HLF-20R +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1286 428 1251 408 442 1241 1289 399 1280 431 408 1247 1283 431 408 1248 442 1242 437 1246 443 1240 439 8135 1279 434 1256 430 409 1246 1284 430 1260 426 413 1242 1287 426 413 1243 436 1247 443 1241 438 1245 445 8130 1284 428 1262 424 415 1240 1279 435 1255 430 409 1247 1283 430 409 1247 443 1241 438 1245 434 1249 441 8133 1281 432 1258 428 411 1244 1286 428 1251 407 443 1240 1279 434 416 1240 439 1244 435 1249 441 1243 436 8137 1287 400 1279 433 417 1239 1280 433 1257 430 409 1246 1284 429 410 1246 444 1240 439 1245 445 1239 440 8133 1281 405 1285 428 411 1245 1285 429 1261 425 414 1241 1289 425 414 1242 437 1246 444 1240 439 1245 434 8139 1285 428 1251 435 415 1240 1279 434 1256 430 409 1246 1283 430 409 1247 443 1241 438 1245 445 1239 440 8133 1281 432 1258 428 411 1244 1286 428 1251 407 443 1240 1279 434 416 1240 439 1244 435 1249 441 1243 436 8137 1287 426 1253 432 407 1248 1281 432 1258 401 438 1244 1285 427 412 1244 435 1248 442 1242 437 1247 442 8131 1283 403 1287 426 413 1242 1287 426 1253 433 417 1239 1280 432 418 1238 441 1243 436 1247 442 1241 438 8136 1288 398 1281 404 435 1248 1281 404 1286 400 439 1244 1285 400 439 1244 435 1249 441 1243 436 1247 442 8131 1283 404 1285 400 439 1243 1286 401 1278 407 443 1240 1289 397 442 1240 439 1245 434 1249 441 1243 436 8138 1286 401 1278 407 443 1240 1279 407 1283 403 436 1246 1283 403 436 1246 444 1240 439 1245 434 1249 441 8134 1280 406 1284 402 437 1246 1283 402 1288 398 441 1242 1288 399 440 1242 437 1247 443 1241 438 1245 434 8140 1284 402 1288 398 441 1242 1287 425 1254 432 407 1249 1281 432 407 1249 441 1243 436 1247 443 1241 438 8136 1288 425 1254 405 434 1248 1281 406 1284 401 438 1245 1285 402 437 1246 433 1250 440 1244 435 1248 442 +# +# Model: Midea AC_MAW05R1WBL +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4459 4372 589 1565 597 480 595 1559 592 485 590 487 588 488 598 479 596 1558 593 1562 589 1565 597 1558 593 483 593 485 590 486 589 488 598 479 596 480 595 1559 592 485 590 486 589 1565 597 481 594 1559 592 485 590 1564 598 479 596 481 594 483 592 484 591 1563 588 489 597 1558 593 1561 590 486 589 488 598 479 596 481 594 483 592 484 591 1563 599 478 597 480 595 482 593 483 592 485 590 486 589 488 588 489 597 5158 4462 4367 594 483 592 1563 588 488 598 1557 594 1560 591 1563 599 1556 595 482 593 484 591 485 590 487 588 1566 596 1559 592 1562 589 1565 597 1558 593 1562 589 488 598 1556 595 1559 592 485 590 1564 598 479 596 1558 593 484 591 1563 588 1566 596 1559 592 1562 589 488 598 1557 594 482 593 484 591 1563 588 1566 596 1559 592 1563 588 1565 597 1558 593 484 591 1563 588 1566 596 1559 592 1562 589 1565 597 1558 593 1561 590 1564 598 +# +# Model: Midea silent_cool_26_pro +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4528 4233 682 1461 684 389 683 1461 684 390 737 335 681 391 681 390 682 1461 683 1461 684 1461 683 1461 683 392 626 468 603 469 603 468 603 469 628 443 629 1515 629 443 628 444 627 445 626 1518 626 1519 625 1519 626 1519 626 446 626 446 625 446 625 446 626 446 625 1519 625 1519 626 1519 626 446 626 446 625 446 626 446 625 446 626 446 626 1519 625 446 625 446 626 446 625 446 625 1519 625 447 625 1519 626 446 626 5105 4471 4290 625 447 625 1520 625 447 625 1519 625 1520 624 1520 625 1520 625 447 625 447 625 447 624 447 624 1519 625 1520 625 1520 624 1520 625 1520 624 1520 625 447 625 1520 624 1520 624 1520 625 447 625 447 625 447 624 447 625 1520 624 1520 624 1520 624 1520 624 1520 624 447 625 447 625 447 625 1520 624 1520 625 1520 624 1521 624 1520 624 1521 624 447 624 1520 624 1521 624 1521 623 1520 624 448 624 1520 624 448 624 1521 624 +# +# Model: mitsubishi-MSY-GE10VA +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3439 1755 439 1262 435 1286 432 438 441 423 456 418 441 1282 436 433 436 431 438 1285 433 1314 435 408 461 1293 435 408 440 426 433 1289 460 1296 412 455 414 1312 406 1319 409 429 440 429 461 1267 441 426 464 406 442 1281 437 430 439 427 432 436 464 405 433 432 437 458 411 456 434 422 437 428 462 412 436 428 441 422 457 411 437 456 413 454 405 436 433 434 435 427 432 435 465 404 434 435 434 431 459 413 435 429 440 422 437 433 457 1270 438 1288 461 410 438 426 464 404 434 1293 435 1315 413 423 436 1287 462 407 431 434 435 435 434 433 467 415 433 1318 410 1286 463 409 439 1284 455 1301 438 410 438 426 464 1262 456 1269 439 429 461 410 469 407 462 1268 440 1285 433 461 408 456 413 1284 465 1261 457 414 455 1271 468 1277 431 1321 438 405 433 460 409 430 439 426 464 407 462 407 441 450 409 434 435 430 439 452 438 431 438 405 464 405 464 405 464 409 460 409 439 430 439 427 442 423 436 460 430 415 433 431 438 425 434 444 435 456 413 432 437 427 442 425 434 436 464 429 430 439 430 417 442 428 441 428 462 411 437 452 438 405 433 434 435 456 413 427 442 425 434 455 414 427 432 435 434 433 436 427 463 406 432 437 442 449 430 1268 460 1265 463 1262 466 405 464 406 442 422 437 1289 439 428 441 17052 3577 1746 407 1321 407 1294 434 460 409 431 459 439 409 1288 440 429 440 431 438 1309 430 1272 456 415 464 1289 408 433 457 412 436 1289 439 1284 465 404 434 1292 436 1289 439 428 441 450 409 1315 413 427 442 451 408 1294 434 432 437 430 439 428 462 406 432 432 458 414 455 414 434 430 439 428 441 426 433 436 433 432 437 454 405 438 441 428 431 434 456 418 441 448 431 411 437 429 440 425 434 433 436 458 411 425 434 433 436 457 412 1287 462 1263 455 419 461 408 440 430 439 1310 429 1272 435 460 409 1288 440 429 461 408 441 425 465 405 464 407 441 1286 432 1291 437 432 437 1315 413 1314 414 450 440 406 442 1283 456 1270 458 413 456 413 456 413 435 1297 431 1294 465 404 434 433 436 1314 414 1285 433 433 436 1288 440 1285 464 1264 433 433 436 453 416 425 434 460 409 434 435 432 437 454 415 423 436 431 459 424 435 430 460 413 435 430 439 423 456 413 435 431 438 455 414 427 432 435 434 455 414 426 464 406 432 437 442 422 457 414 434 431 438 424 435 435 455 414 455 416 432 433 436 457 412 426 464 410 438 453 437 412 436 437 463 409 439 425 434 455 414 426 433 437 432 433 436 433 436 457 433 412 436 453 437 406 432 435 465 1266 462 1263 434 1318 410 426 464 410 438 426 433 1293 435 434 435 +# +# Model: Mitsubishi MSH-30RV +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3520 1642 515 1199 514 1199 514 383 486 383 486 383 486 1199 514 383 486 383 486 1199 513 1199 514 382 487 1199 513 383 486 383 486 1226 486 1226 486 383 486 1226 486 1226 486 383 486 383 486 1226 486 384 510 359 510 1202 510 360 509 360 509 360 509 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 1204 508 361 508 361 508 361 508 361 508 361 508 1205 508 361 508 361 508 361 508 361 508 1205 507 1205 508 1205 507 361 508 362 507 362 507 361 508 362 507 361 508 1205 507 362 507 362 507 1205 508 362 507 362 507 362 507 362 507 362 507 361 508 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 1205 507 362 507 362 507 362 507 362 507 362 507 362 507 1205 507 362 507 1206 506 1206 506 362 507 1206 507 362 507 +# +# Model: Mitsubishi MSH_GA71VB +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3480 1701 468 1248 467 1250 465 407 460 411 467 405 462 1255 460 412 466 405 462 1255 460 1257 468 404 463 1253 462 410 468 404 463 1253 462 1255 470 402 465 1252 463 1254 461 411 467 405 462 1254 461 411 467 405 462 1254 461 411 467 405 462 409 469 403 464 407 460 411 467 405 462 409 469 403 464 407 461 411 467 405 462 409 469 403 464 407 460 411 467 404 463 1254 461 410 468 404 463 1254 461 410 468 404 463 408 460 412 466 406 462 1255 460 412 466 406 461 410 468 404 463 1253 462 1255 470 1247 468 404 463 409 469 402 465 406 461 411 467 1250 465 407 460 1256 469 1248 467 1250 465 1252 463 410 468 403 464 408 460 412 466 406 461 410 468 404 463 408 460 412 466 406 461 410 468 404 463 408 459 412 466 405 462 409 469 403 464 407 460 411 467 405 462 409 469 403 464 407 460 411 467 405 462 409 469 402 465 407 460 411 467 404 463 1253 462 1255 470 402 465 406 461 1256 469 402 465 1252 463 408 470 1248 467 1250 465 407 460 1256 469 +# +# Model: Mitsubishi SRK20ZJ-S +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3195 1551 429 365 428 1158 429 365 429 365 428 1158 429 365 428 1158 429 366 428 365 428 1159 428 1158 429 1159 428 364 429 1158 429 366 428 1158 429 1158 429 1158 429 365 428 365 428 365 429 365 428 1158 429 1158 429 365 428 1158 429 1158 458 354 440 353 441 1128 459 353 494 353 386 1128 486 353 441 353 440 1101 459 1128 459 353 440 1128 459 1127 460 1128 459 1128 459 353 440 1129 457 1129 458 1130 457 1131 456 353 440 354 439 354 439 1132 455 354 439 353 440 354 439 354 439 1132 455 1132 455 1132 455 1132 455 354 440 354 439 1133 454 1133 454 354 439 353 440 354 439 353 440 1133 454 1133 454 353 440 353 440 1133 454 353 440 1133 454 1133 454 1133 454 353 440 353 440 353 440 1133 454 1133 454 353 440 353 440 354 439 1133 454 1133 454 1133 454 354 439 +# +# Model: Mitsubishi SRK35ZS-W +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3199 1594 380 414 380 1208 379 415 389 405 389 1201 386 407 386 1202 385 409 384 408 385 1203 384 1203 384 1204 383 410 383 1206 381 412 382 1208 379 1207 380 1209 389 404 390 406 387 405 389 406 387 1201 386 1202 385 408 385 1204 383 409 384 1204 383 1205 382 412 382 411 382 413 380 1206 382 414 380 1208 379 414 379 416 388 1201 386 1201 386 1201 386 408 385 1202 385 1204 383 1204 383 1206 381 1205 382 1207 380 1207 380 1208 390 405 388 405 389 406 387 406 387 406 387 407 386 408 385 1202 385 1204 383 1203 384 411 382 1205 382 1205 382 1207 380 1207 381 414 390 404 379 414 380 1210 388 406 387 408 385 406 387 408 385 1202 385 1203 384 409 384 1205 382 1205 382 1206 381 1206 381 1208 379 415 389 404 389 1199 388 407 386 406 387 407 386 408 385 409 384 1202 385 1204 383 1204 383 1204 383 1205 382 412 381 1208 379 1208 379 415 389 404 390 406 387 406 387 407 386 1200 387 409 384 408 385 1202 385 1205 383 410 383 1205 382 1205 382 412 381 1207 380 1207 380 415 389 404 379 1209 389 406 387 405 388 1200 387 408 385 408 385 1202 385 1204 383 1204 383 1204 383 1208 379 1207 380 1207 380 1208 379 416 388 405 388 407 386 407 386 407 386 407 386 407 386 408 385 408 385 1204 383 1204 383 1205 382 1205 382 1207 380 1208 379 414 379 1210 388 406 387 406 387 406 387 407 386 407 386 409 384 1203 384 +# +# Model: Moretti Air_Cooler +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1299 412 1270 412 426 1225 1299 412 1244 438 399 1279 400 1254 451 1253 426 1253 426 1253 426 1254 1268 7130 1266 416 1266 416 421 1258 1267 416 1267 416 422 1258 421 1258 422 1258 421 1259 421 1258 422 1258 1266 7132 1266 417 1266 417 421 1259 1266 417 1266 417 421 1259 421 1259 421 1259 421 1259 421 1259 421 1259 1266 7133 1265 417 1266 417 421 1259 1266 418 1266 417 422 1259 421 1260 420 1259 421 1259 421 1259 420 1259 1266 7133 1265 418 1266 418 421 1260 1265 418 1266 418 421 1260 420 1260 420 1260 420 1260 420 1260 420 1260 1266 7135 1265 418 1266 419 420 1260 1266 419 1266 419 420 1261 420 1261 420 1261 420 1261 420 1261 420 1261 1265 7137 1264 420 1265 420 419 1262 1266 420 1265 420 420 1262 420 1262 419 1262 420 1262 419 1262 419 1262 1265 7139 1264 421 1265 421 419 1263 1265 421 1265 421 418 1264 418 1264 418 1263 419 1264 417 1264 418 1264 1264 7142 1263 423 1263 422 418 1288 1241 446 1240 446 394 1288 394 1289 393 1288 394 1288 394 1288 394 1288 1241 7168 1240 446 1241 446 394 1289 1241 447 1241 447 394 1289 394 1289 394 1289 394 1289 394 1289 394 1289 1241 7171 1239 447 1240 447 393 1290 1240 447 1241 447 393 1290 393 1290 393 1290 393 1290 393 1290 393 1290 1241 7174 1239 447 1241 448 392 1291 1240 448 1241 448 393 1291 393 1292 392 1292 392 1291 393 1292 392 1291 1240 7177 1239 449 1240 449 392 1292 1240 449 1240 449 392 1293 392 1293 392 1292 392 1293 391 1293 392 1293 1239 7179 1239 450 1239 450 391 1294 1239 450 1240 451 391 1294 391 1294 391 1295 390 1294 391 1295 390 1294 1239 7184 1237 452 1238 475 367 1319 1215 476 1215 476 366 1320 366 1320 366 1320 365 1320 365 1320 366 1320 1215 7211 1213 476 1190 501 366 1321 1215 477 1214 477 365 1322 364 1321 365 1322 364 1321 365 1321 365 1322 1213 7214 1213 478 1213 478 364 1322 1189 503 1189 503 364 1323 339 1348 338 1348 364 1323 363 1324 362 1323 1213 7218 1187 528 1164 529 313 1374 1163 529 1164 529 313 1375 313 1375 312 1375 312 1374 313 1374 313 1375 1163 7271 1162 530 1163 530 312 1376 1163 531 1162 530 312 1377 311 1378 310 1402 285 1378 310 1402 285 1403 1136 7300 1136 557 1136 557 285 1404 1136 558 1136 559 283 1404 285 1431 257 1405 284 1431 257 1431 257 1431 1109 7330 1108 585 1109 586 256 1432 1109 586 1109 638 177 1486 222 1468 221 1468 221 1468 221 1467 221 1495 1055 7413 1028 666 1029 2500 885 +# +# Model: Ok AC_OAC_7020 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4371 4390 527 1618 527 545 526 1618 527 520 552 545 527 545 527 521 551 1593 552 545 527 545 527 545 527 546 526 545 527 545 526 1618 527 545 527 545 527 1618 527 545 527 545 527 520 552 1618 527 1593 551 1618 527 1618 527 1618 527 1618 526 1618 526 1594 579 1591 527 1618 526 1619 527 1618 526 1593 552 1618 527 1618 527 1593 552 1618 527 1618 527 1618 527 1618 527 1618 527 545 527 1618 527 1618 527 545 527 1619 554 518 527 5203 4373 4391 526 545 527 1618 527 545 527 1593 552 1618 527 1595 550 1618 528 544 528 1618 527 1618 526 1594 552 1617 528 1619 526 1618 527 545 527 1618 527 1619 527 519 553 1618 527 1618 527 1618 527 522 550 545 527 546 526 545 555 517 528 545 527 545 528 544 527 521 551 522 550 545 527 545 526 521 552 521 551 545 527 544 528 545 527 545 527 545 527 545 527 545 528 1618 527 545 527 545 527 1619 527 545 527 1619 526 +# +# Model: Osaka CH_09_DSBP +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9034 4455 670 1627 695 507 693 508 692 510 690 514 688 514 688 514 688 514 687 514 688 1609 688 514 688 514 688 514 688 514 688 515 687 515 687 514 688 515 687 515 687 515 687 515 686 1611 686 515 687 515 687 516 686 517 685 517 685 516 686 1612 685 540 662 1635 662 540 662 540 662 1635 662 540 662 19937 688 514 688 514 688 514 688 514 688 514 688 514 688 514 688 514 687 515 687 515 687 515 687 515 687 515 686 1611 686 515 687 516 686 516 686 539 662 517 685 516 686 539 663 539 663 540 662 540 662 540 662 540 661 540 662 540 662 1635 662 1635 662 1636 661 1635 662 +# +# Model: Panasonic A75C4187 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3498 1744 439 431 442 1305 442 431 442 432 441 431 442 431 442 432 441 432 441 433 440 431 442 432 441 431 442 432 441 1306 441 431 442 432 441 432 441 432 441 432 441 432 441 432 441 1308 439 1305 442 1305 442 431 442 433 440 1305 442 434 439 433 440 431 442 432 441 432 441 432 441 433 440 431 442 432 441 433 440 432 441 433 440 432 441 432 441 432 441 432 441 432 441 434 439 433 440 432 441 433 440 433 440 432 441 432 441 432 441 432 441 432 441 431 442 432 441 433 440 1308 439 1306 441 432 441 432 441 431 442 432 441 432 441 10333 3499 1741 442 432 441 1306 441 432 441 432 441 431 442 432 441 431 442 433 440 432 441 433 440 432 441 432 441 432 441 1308 439 431 442 432 441 432 441 432 441 433 440 432 441 432 441 1306 441 1307 440 1306 441 432 441 432 441 1307 440 433 440 432 441 433 440 432 441 431 442 432 441 432 441 432 441 432 441 432 441 432 441 433 440 1308 439 432 441 1305 442 1305 442 432 441 432 441 433 440 432 441 1307 440 1307 440 431 442 1306 441 432 441 1306 441 1307 440 432 441 433 440 1306 441 432 441 432 441 433 440 431 442 432 441 1308 439 432 441 +# +# Model: Panasonic Climate_A75C2600 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3545 3410 937 803 936 2542 937 803 936 832 907 832 933 2545 933 2546 931 809 928 813 925 2580 899 839 900 840 900 839 900 2580 899 2580 899 840 899 840 900 2580 899 840 900 840 900 839 900 840 899 840 900 840 899 840 900 2580 899 840 899 840 899 840 900 840 900 840 899 840 3511 3449 898 841 898 2581 898 840 900 840 899 841 898 2581 898 2581 899 840 899 841 898 2581 898 841 898 841 898 841 898 2581 898 2581 898 841 898 841 898 2581 899 841 898 841 898 841 899 841 898 841 898 842 898 841 898 2581 898 841 898 842 897 842 897 842 897 842 898 842 3509 3450 897 13896 3509 3451 896 842 897 843 897 843 896 843 896 2583 896 843 897 843 896 2583 896 844 895 844 895 868 871 869 871 2609 870 869 871 869 870 2609 870 869 871 2609 870 2609 870 869 871 2609 870 2609 870 869 870 869 870 869 870 2609 870 2609 870 869 871 2609 870 2610 869 870 869 870 3482 3478 869 870 869 870 869 870 870 870 869 2610 869 870 869 870 870 2610 869 871 869 871 869 871 868 871 869 2611 868 871 868 871 869 2611 868 872 867 2611 869 2611 868 871 868 2612 867 2612 868 872 867 897 842 897 843 2637 843 2637 842 897 843 2637 843 2637 842 898 842 898 3454 3506 841 +# +# Model: Panasonic CWA75C4179 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3472 1744 416 457 415 1306 418 463 419 466 416 473 419 474 418 478 414 457 415 458 414 463 419 462 420 465 417 472 420 1316 418 479 413 458 414 459 413 464 418 463 419 466 416 473 419 1318 416 1324 420 1295 418 454 418 459 413 1312 422 463 419 470 422 471 421 475 417 454 418 455 417 460 422 459 413 472 420 469 413 480 412 485 417 453 419 454 418 459 413 468 414 471 421 468 414 479 413 484 418 452 420 453 419 458 414 468 414 471 421 467 415 478 414 483 419 451 421 453 419 1301 412 1312 422 463 419 470 422 471 421 476 416 433 418 10535 3469 1746 414 459 413 1308 415 465 417 468 414 475 417 476 416 481 421 449 413 461 421 455 417 465 417 467 415 474 418 1319 415 482 420 450 422 451 421 457 415 466 416 469 413 476 416 1320 414 1327 417 1297 416 457 415 462 420 1305 419 466 416 473 419 474 418 478 414 457 415 458 414 463 419 462 420 465 417 472 420 473 419 477 415 456 416 1301 412 464 418 463 419 1310 413 1319 415 1321 413 485 417 453 419 454 418 459 413 1312 422 1307 416 472 420 1317 416 480 412 459 413 460 412 465 417 464 418 467 415 474 418 475 417 479 413 1302 422 1295 418 1302 421 1303 421 1308 415 473 419 1318 416 481 421 1293 420 1296 417 460 412 1313 421 1307 416 473 419 473 419 478 414 457 415 458 414 463 419 462 420 465 417 472 420 473 419 477 415 456 416 457 415 1306 418 1307 416 1312 422 467 415 478 414 483 419 451 421 452 420 457 415 467 415 469 413 476 416 1321 413 1328 416 1298 415 458 414 463 419 462 420 465 417 472 420 472 420 477 415 456 416 457 415 462 420 461 421 464 418 470 422 471 421 476 416 454 418 1299 414 463 419 461 421 464 418 471 421 472 420 477 415 1299 414 459 413 464 418 463 419 466 416 473 419 473 419 478 414 457 415 458 414 463 419 462 420 465 417 472 420 473 419 477 415 456 416 457 415 1306 417 1307 416 468 414 1319 415 478 414 483 419 430 421 +# +# Model: Remko RKL +# +name: Off +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 12 ED 00 00 +# +# Model: Rinnai AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9026 4519 590 1654 616 1657 623 574 561 582 563 582 563 583 562 1688 592 1656 675 1627 593 1650 620 1654 677 521 563 1660 620 1656 614 1664 616 581 564 578 567 575 560 582 563 581 564 579 566 1684 596 1655 615 1661 619 577 568 574 561 581 564 580 565 579 566 580 565 582 563 581 564 577 568 574 561 582 563 580 565 579 566 1657 613 1664 616 582 563 578 567 575 560 583 562 581 564 580 565 582 563 584 561 582 563 578 567 575 560 583 562 581 564 580 565 1659 621 579 566 578 567 574 561 581 564 579 566 577 568 576 569 577 568 579 566 578 567 574 561 580 565 578 567 576 569 575 560 587 568 579 566 577 568 573 562 580 565 578 567 576 569 575 560 586 569 552 593 577 568 573 562 580 565 577 568 576 559 585 560 586 569 552 593 576 569 1703 567 575 560 1713 567 577 568 576 569 577 568 553 592 578 567 1704 566 1707 563 1656 614 1662 618 1659 621 577 568 553 592 1706 564 +# +# Model: Royal Clima_RC-TWN55HN +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4412 4413 548 1606 548 530 601 1552 602 1552 601 475 601 475 548 1606 548 528 548 528 548 1605 548 528 548 529 547 1607 546 1608 546 531 545 1610 544 534 542 1635 519 1635 519 1635 519 1635 518 558 519 1635 542 1612 518 1636 518 558 519 558 519 558 519 558 542 1612 518 558 519 558 519 1636 517 1636 518 1636 518 558 519 558 518 558 542 535 518 559 518 559 541 535 518 559 518 1635 518 1635 518 1636 518 1635 519 1636 518 5235 4384 4443 517 1636 541 535 518 1636 518 1636 518 559 518 559 518 1635 518 559 518 559 517 1636 518 559 518 558 518 1636 541 1612 518 559 518 1636 517 559 518 1636 518 1636 518 1636 518 1636 517 559 518 1636 518 1636 518 1636 541 535 518 559 518 559 541 536 517 1636 518 559 518 559 518 1636 517 1636 518 1636 518 559 517 559 517 559 517 559 517 559 518 559 518 559 517 559 518 1636 518 1636 518 1636 517 1636 517 1636 517 +# +# Model: Samsung AC_AR12K +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 270 18152 3021 8955 523 499 495 1497 492 504 500 468 526 496 498 498 496 499 495 501 493 502 492 1499 500 496 498 524 470 1521 498 497 497 499 495 1496 492 1499 500 1491 497 1494 494 1497 502 494 500 495 499 497 497 498 496 500 494 528 466 530 464 531 473 522 493 503 491 504 500 495 499 497 497 498 496 500 494 501 493 503 491 504 500 495 499 496 498 498 496 499 495 501 493 529 465 531 473 522 472 523 492 504 490 505 499 496 498 498 496 499 495 1496 492 1499 500 1492 496 1494 525 2947 2999 8953 525 1519 469 499 516 507 497 498 496 500 494 501 493 503 491 504 500 495 499 1492 496 499 495 501 493 1498 501 495 499 1492 496 1522 466 1524 496 1496 492 1499 500 1492 496 499 495 500 494 502 492 504 500 495 499 496 498 498 496 499 495 500 494 502 492 530 474 521 473 523 523 472 522 474 489 506 498 497 497 499 495 500 494 502 492 503 501 494 500 496 498 497 497 499 495 500 494 502 492 503 501 521 473 523 471 524 522 474 520 475 498 497 497 499 495 500 494 2978 2999 8952 525 1492 496 499 495 501 493 503 491 504 500 495 499 497 497 525 469 526 468 1524 516 479 494 501 493 503 491 504 500 495 499 497 497 1494 494 1497 492 1500 499 1492 496 499 495 1497 491 531 473 1518 522 1469 499 496 498 498 496 500 494 1497 491 1500 499 1492 496 499 495 501 493 502 492 504 500 495 499 523 471 525 469 526 520 1472 516 1475 493 502 492 504 500 495 499 1492 496 499 495 501 493 502 492 504 500 495 499 496 498 498 496 1522 466 1525 525 1466 491 1500 499 +# +# Model: Samsung AR13TYHZCWKN +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 645 17766 3059 8884 533 461 557 1427 560 435 584 433 534 458 534 458 534 458 588 404 587 405 586 1399 586 407 584 408 558 1429 555 1457 502 491 525 1460 528 1458 554 1431 554 1431 555 1431 554 438 555 438 554 439 553 440 553 464 528 464 528 464 529 465 528 465 526 467 526 467 527 465 553 439 554 439 554 438 554 439 554 439 553 439 554 439 553 439 553 439 554 440 552 464 528 464 528 465 528 466 527 466 501 492 525 468 526 466 527 466 553 439 554 439 553 440 552 1433 553 1433 553 2936 3024 8893 552 1458 527 465 528 465 527 467 526 467 500 493 524 468 526 467 526 467 552 1434 552 441 552 441 552 1435 550 465 528 1458 527 1458 528 1459 527 1485 501 1485 500 1486 501 491 527 466 528 465 528 465 527 465 528 465 527 465 528 465 528 465 527 465 528 465 527 466 527 466 527 492 500 492 501 492 500 493 500 493 500 492 526 466 527 465 527 465 527 465 527 466 527 466 527 466 527 465 527 466 526 466 527 466 526 467 526 493 499 493 474 518 499 494 500 493 526 2936 3025 8918 526 1459 527 466 527 466 527 466 526 466 527 466 526 467 526 467 525 467 526 1460 525 493 499 493 498 495 498 495 499 493 500 493 525 1460 527 1460 525 1460 526 1460 525 1460 526 1460 526 468 525 1487 499 1487 497 495 498 495 499 493 500 1486 525 1460 525 1460 525 467 525 467 526 467 525 467 526 468 524 1462 524 468 524 494 498 1488 497 1488 499 494 499 493 525 468 525 1461 524 468 525 468 524 468 525 468 525 468 524 468 525 469 524 494 498 494 499 1488 473 1514 524 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 619 17878 3008 8908 535 458 535 1451 534 459 534 459 533 459 534 485 507 485 587 404 532 460 533 1451 536 456 537 456 535 1450 561 432 560 433 533 1454 531 1455 530 1456 529 1457 529 1508 501 491 502 491 502 490 503 490 503 489 504 488 504 489 503 489 504 489 504 489 504 488 504 489 504 464 529 464 529 464 529 464 528 516 476 516 477 516 502 491 502 490 503 490 503 489 504 489 503 489 504 489 504 489 504 489 503 489 504 489 503 489 504 464 529 1458 528 1509 476 1509 501 1484 504 2934 3028 8939 504 1482 504 489 503 489 504 489 504 489 503 489 504 489 503 489 504 465 528 1459 527 516 476 516 501 1485 502 1484 503 1483 503 1482 503 489 504 1482 504 1483 503 1482 503 1459 527 1459 527 516 476 1509 501 1485 501 491 502 490 503 489 504 1482 503 1482 503 1483 503 489 504 489 503 489 504 489 503 489 503 490 503 516 477 516 501 1485 502 1484 502 490 503 490 503 490 503 490 503 1483 503 490 503 490 503 489 503 490 503 490 502 490 503 1510 475 1510 500 1485 502 1484 503 +# +# Model: Samsung Wind-Free +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 606 17832 2994 8936 520 501 496 1491 494 501 496 498 489 532 465 529 488 505 492 502 495 499 488 1499 496 499 498 495 492 1496 499 1463 522 499 498 1463 522 1467 517 1469 526 1491 493 1495 520 500 487 507 490 504 493 500 497 497 490 504 493 501 496 498 489 505 492 501 496 498 499 495 492 502 495 499 498 522 465 529 468 526 492 502 495 499 488 506 491 503 494 499 498 496 491 503 494 500 497 497 490 504 493 500 497 497 490 504 493 500 497 497 490 531 466 528 469 1518 487 1475 520 2947 3018 8939 517 1471 524 496 491 503 494 500 497 497 490 504 493 500 497 497 490 504 493 1521 464 530 467 527 491 1471 514 507 490 1471 524 1465 519 1468 517 1472 523 1465 520 1468 517 477 520 501 496 497 490 531 466 528 469 525 493 501 496 498 489 505 492 502 495 498 499 495 492 502 495 499 498 495 492 502 495 499 498 496 491 502 495 499 498 496 491 529 468 526 471 523 495 499 488 506 491 503 494 499 498 496 491 503 494 500 497 496 491 503 494 500 497 497 490 503 494 2973 2992 8939 517 1469 526 522 465 529 488 506 491 503 494 499 488 506 491 503 494 500 497 1490 495 499 498 496 491 1497 498 1464 520 1468 517 1470 525 524 463 1498 517 1471 524 1465 520 1468 517 1471 524 1465 520 1468 517 1472 523 497 490 504 493 501 496 1491 494 1496 519 1469 516 504 493 501 496 498 489 505 492 501 496 498 499 495 492 502 495 1492 493 1469 526 495 492 529 468 1492 513 1476 519 502 495 498 489 505 492 502 495 499 498 496 491 503 494 499 498 496 491 1497 498 1464 541 +# +# Model: Sharp AH-A9UCD +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9111 4374 765 1548 712 496 713 497 713 496 710 1598 711 1602 712 1602 712 496 712 497 712 498 710 495 709 501 729 496 711 496 707 498 707 498 707 498 708 498 707 499 707 499 707 498 707 1600 706 499 708 497 709 498 706 500 706 499 707 499 707 1597 705 499 710 1596 705 497 707 500 708 1601 708 500 709 19935 707 1604 706 498 707 500 709 500 708 499 709 500 710 500 709 500 708 1600 708 500 709 500 710 498 705 499 708 1597 704 498 709 500 707 498 709 498 705 499 709 498 709 498 708 498 708 499 707 499 707 498 707 498 708 499 707 498 708 1600 706 497 707 1597 705 1600 709 +# +# Model: Sharp AH_X9VEW_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3821 1901 456 507 456 1424 430 490 484 1425 429 475 488 1420 434 471 482 1427 437 467 486 1420 434 486 457 1416 459 1432 432 487 456 1436 439 480 483 1425 439 1425 460 1420 455 1419 435 485 457 472 491 1417 458 1407 437 483 459 487 456 474 458 487 466 1432 432 471 461 485 457 472 491 1415 460 1378 466 480 483 1424 430 474 458 487 466 1424 461 1419 456 1426 438 465 457 489 464 466 456 489 464 1426 438 481 461 484 437 493 460 1429 435 482 460 485 458 1440 435 484 458 1438 437 467 465 480 462 467 465 481 461 468 464 481 461 469 463 481 461 468 485 1424 430 1434 462 1418 457 1425 439 465 457 489 464 465 457 489 464 466 456 490 463 467 465 480 463 467 465 480 463 467 486 1422 432 471 461 485 457 472 460 485 457 472 460 485 457 472 460 486 456 488 434 497 456 473 459 486 456 488 465 1414 461 1393 482 1423 462 1419 435 469 463 482 460 469 463 482 460 1436 439 1425 439 480 483 +# +# Model: Sharp CVP10MX +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3827 1854 505 433 507 1382 506 434 506 1383 505 434 506 1410 478 433 507 1381 507 433 507 1383 505 435 505 1382 505 1383 505 435 505 1382 506 435 505 1383 505 1382 506 1384 504 1381 507 434 506 434 506 1382 506 1382 506 432 508 434 506 436 504 433 507 1382 506 435 505 434 506 436 504 1381 507 1383 505 434 506 1382 506 1381 507 434 506 1381 507 1381 507 1383 505 435 505 434 506 435 505 433 507 1383 505 435 505 435 505 434 506 1381 507 433 507 435 505 434 506 1382 506 433 507 434 506 433 507 438 502 434 506 433 507 434 506 434 506 432 508 435 505 434 506 433 507 433 507 1387 501 435 505 436 504 433 507 433 507 435 505 433 507 434 506 434 506 435 505 435 505 432 508 1410 478 461 479 432 508 435 505 434 506 433 507 436 504 434 506 434 506 433 507 435 505 1383 505 435 505 1381 507 1381 507 1382 506 1382 506 1381 507 435 505 433 507 433 507 461 478 1383 505 433 507 434 506 +# +# Model: Shivaki SSA18002 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3197 1545 581 1033 553 1006 606 338 463 342 485 339 488 1033 553 342 485 342 485 1034 551 1035 550 342 485 1037 548 342 485 340 487 1040 546 1040 546 340 487 1040 546 1040 546 340 487 340 488 1040 546 340 487 340 488 1040 546 340 487 340 487 340 487 342 485 340 487 340 487 340 487 342 485 342 485 342 485 340 487 342 485 340 487 340 487 342 485 342 485 342 485 342 485 340 488 342 485 1041 545 340 487 340 487 1041 545 1041 545 340 488 340 487 340 487 340 487 340 488 342 485 342 485 340 487 340 488 1041 545 340 487 340 487 342 486 342 485 340 487 340 487 340 487 340 487 342 485 341 486 1041 545 340 487 340 487 340 487 340 487 340 488 342 485 340 487 342 485 342 485 340 487 342 485 340 487 340 488 342 485 343 484 340 488 340 487 340 487 340 487 340 487 342 485 340 487 340 487 341 486 342 485 340 488 342 485 342 485 342 485 340 487 340 487 340 488 1042 544 340 487 340 487 340 487 340 487 340 487 340 487 340 488 342 485 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3196 1545 581 1005 580 1008 604 337 464 338 488 338 489 1033 553 339 488 338 489 1033 552 1034 551 339 487 1036 549 339 488 339 488 1039 547 1039 547 340 487 1040 546 1040 546 339 488 339 488 1040 546 340 487 339 488 1040 546 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 1040 546 339 488 339 488 1040 546 339 488 339 488 339 488 1040 546 339 488 339 488 339 488 339 488 339 488 339 488 1040 546 1041 545 1041 545 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 340 487 339 488 1042 544 339 488 1042 544 339 488 339 488 339 488 339 488 1042 544 1042 544 +# +# Model: SINCLAIR ASH13BIF2 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9048 4430 705 500 706 1602 705 1602 729 476 730 1579 728 478 728 1580 727 480 726 480 726 480 726 480 726 1606 702 505 701 481 725 481 724 505 701 481 725 504 702 505 701 504 701 505 701 1606 702 504 702 504 701 504 701 504 702 505 701 505 701 1606 701 504 702 1607 701 504 701 504 701 1607 701 505 701 19915 726 1582 726 481 725 504 701 505 701 1606 701 505 701 505 701 505 701 505 700 1606 701 505 701 505 701 505 701 504 702 1606 701 505 701 504 701 505 701 504 702 505 701 505 701 505 701 505 701 505 701 505 701 505 701 505 701 505 701 1606 701 505 701 1607 700 1606 701 39924 9075 4406 728 478 728 1581 727 1581 726 481 725 1582 725 480 725 1606 701 504 702 481 725 505 701 505 701 1606 701 504 702 505 701 505 701 505 701 505 701 505 701 505 701 505 701 505 701 1606 701 505 701 505 701 505 701 505 701 505 701 505 701 1606 701 1607 701 1607 701 505 701 505 701 1607 701 506 701 19913 726 480 726 480 726 480 726 480 726 479 727 479 727 480 725 479 727 479 726 480 726 480 726 480 726 480 726 480 726 480 726 504 701 480 726 480 725 505 702 481 725 1606 701 505 701 481 725 481 725 505 700 505 701 504 701 505 701 1607 701 504 701 505 700 1607 701 +# +# Model: SoleusAir +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6149 7347 601 535 573 560 602 505 602 480 597 509 600 534 599 487 593 541 591 568 569 539 568 490 594 539 570 513 568 540 570 566 570 540 568 543 594 540 569 566 543 515 594 540 569 514 567 542 570 514 568 543 594 540 569 490 592 542 570 514 568 541 570 541 542 541 569 567 570 515 567 542 570 515 567 542 570 568 568 516 570 568 570 542 568 542 570 515 569 542 570 568 568 517 569 541 569 517 569 568 570 515 568 570 569 569 543 542 569 569 569 516 569 542 570 543 568 1640 570 516 568 543 570 569 543 1613 569 516 568 1613 570 569 543 570 569 542 543 543 569 543 542 544 569 516 569 544 570 543 570 570 570 543 543 544 569 1615 569 571 569 570 568 519 569 1615 569 544 569 1615 568 572 569 571 542 1642 569 571 568 1617 569 1642 542 1642 569 1589 568 545 569 1590 569 518 568 1617 569 545 568 7372 568 +# +# Model: Subtropic in-07HN1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9019 4453 576 1665 603 1639 602 504 602 505 601 507 599 508 598 1644 573 1669 573 1669 572 1670 572 1670 572 1670 572 1670 572 1670 572 1670 572 535 572 535 572 535 572 535 572 535 572 535 572 1670 572 1670 572 1670 571 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 536 571 1670 572 535 572 1670 572 535 572 535 572 536 571 536 571 536 571 536 571 535 572 536 571 536 571 536 571 536 571 536 571 536 571 1670 572 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 537 570 536 571 537 570 536 571 537 570 536 571 537 570 537 570 537 570 537 570 537 570 537 570 538 569 538 569 538 569 561 546 562 545 562 545 562 545 1696 546 562 545 1697 545 562 545 562 545 562 545 562 545 562 545 1696 546 1697 545 1697 545 562 545 562 545 1697 545 1697 545 1697 545 +# +# Model: Tcl +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3087 1607 488 1064 517 335 492 1087 484 315 512 340 487 1091 490 336 491 338 489 1089 492 333 494 332 485 341 486 340 487 338 489 336 491 339 488 1090 491 334 493 1085 486 340 487 1091 490 1088 493 333 484 343 484 44227 178 +# +# Model: Timberk RG05D4-BGE +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4414 4312 565 1591 565 517 565 1596 564 1592 565 517 565 517 565 1591 566 519 565 519 565 1595 565 517 565 517 565 1592 564 1593 563 519 563 1599 563 521 563 1594 562 1594 563 1594 563 1595 562 520 562 1598 563 1597 562 1600 562 520 562 520 562 520 562 520 562 1595 562 520 562 522 562 1601 561 1595 562 1595 562 521 561 521 561 521 561 521 561 523 561 523 561 521 561 521 561 1600 561 1596 561 1596 561 1596 561 1597 561 5165 4384 4310 560 1596 561 521 561 1600 561 1596 561 521 561 521 561 1595 562 523 561 523 561 1600 561 521 562 521 561 1595 562 1595 562 520 562 1601 562 523 561 1595 562 1595 562 1595 562 1595 562 521 561 1599 562 1597 562 1601 562 521 561 521 561 521 561 521 561 1595 562 521 561 522 562 1601 562 1595 562 1595 562 521 561 521 561 521 561 521 561 523 561 523 561 521 561 521 561 1599 562 1596 561 1596 561 1595 562 1598 562 +# +# Model: Tora TS_16-Classic +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3121 1585 524 1061 524 1036 549 346 473 346 471 346 499 1036 549 346 473 346 472 1061 524 1061 524 346 472 1064 545 347 473 346 472 1092 493 1092 493 346 473 1092 493 1092 493 346 498 346 473 1067 518 346 473 346 497 1068 518 346 474 346 473 346 498 346 474 346 473 346 497 346 475 346 473 346 499 347 472 346 473 346 499 346 473 346 472 353 492 346 473 346 498 346 474 346 473 346 473 1093 492 346 499 346 473 1094 491 1094 491 346 473 346 499 346 473 346 473 346 499 346 473 346 497 346 474 346 473 1094 491 346 473 346 499 346 473 346 498 347 473 346 473 346 498 346 474 346 473 346 497 346 475 346 472 347 498 346 473 346 473 347 498 346 473 346 473 347 498 346 473 346 498 347 474 346 473 346 497 346 475 346 473 346 473 354 491 346 473 346 499 346 473 347 472 346 498 347 472 346 473 353 492 347 472 346 473 347 498 353 465 346 473 347 498 353 465 346 499 353 466 353 466 347 497 354 465 354 465 347 472 1095 490 354 491 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3112 1543 540 1041 544 1039 546 346 500 346 473 346 473 1063 522 346 494 347 475 1064 546 1039 543 346 448 1068 517 346 472 347 497 1046 514 1096 516 346 473 1069 516 1069 516 346 473 346 473 1069 516 347 498 346 473 1069 516 346 472 347 498 346 473 346 473 346 499 346 472 346 498 346 473 346 473 346 498 346 474 346 473 346 498 347 473 346 473 347 498 346 473 1070 515 346 472 347 471 1071 538 346 500 346 448 347 496 1070 515 346 474 346 472 346 497 347 474 346 497 346 474 346 473 346 473 346 499 346 473 346 473 346 499 346 473 346 498 347 473 347 472 346 497 346 474 346 473 346 496 347 474 346 473 347 498 346 473 346 473 346 499 346 473 346 473 347 498 346 473 346 497 346 475 346 473 347 495 346 476 346 473 346 472 355 490 346 473 346 498 346 473 347 472 346 498 347 473 346 473 355 490 346 473 347 472 346 499 347 471 346 473 346 499 346 473 346 496 1072 490 1095 490 347 497 1070 490 1096 489 1095 490 346 499 346 473 +# +# Model: Toshiba RAS13SKV2E +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4387 4349 553 1609 553 1607 555 1605 546 1613 549 531 550 530 551 1610 552 527 554 526 555 526 555 525 556 524 557 1602 549 1611 551 529 552 1608 554 526 555 525 556 525 556 524 546 533 548 532 549 1611 551 1608 554 1606 556 1604 558 1603 548 1611 551 1609 553 1607 555 525 556 525 556 524 557 523 547 533 548 533 548 532 549 531 550 530 551 1607 555 526 555 1605 557 1603 548 531 550 531 550 530 551 529 552 528 553 527 554 526 555 526 555 525 556 524 546 1612 550 1610 552 1608 554 527 554 526 555 526 555 525 556 524 557 523 547 533 548 532 549 531 550 1609 553 1607 555 525 556 525 556 1603 548 1612 550 530 551 7454 4385 4352 549 1611 551 1609 553 1608 554 1606 556 525 556 524 557 1602 549 531 550 531 550 530 551 530 551 529 552 1633 529 1604 558 523 558 1601 550 531 550 530 551 530 551 529 552 528 553 527 554 1604 558 1603 548 1611 551 1609 553 1608 554 1606 556 1604 558 1602 549 532 549 531 550 530 551 530 551 529 552 528 553 527 554 526 555 525 556 1603 548 532 549 1610 552 1608 554 527 554 527 554 526 555 525 556 524 557 523 547 533 548 532 549 531 550 530 551 1608 554 1606 556 1604 558 523 547 533 548 532 549 531 550 530 551 529 552 528 553 527 554 526 555 1605 557 1602 549 531 550 531 550 1609 553 1607 555 526 555 +# +# Model: Toshiba RG57H4 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4415 4348 568 1578 569 503 570 1575 572 500 563 510 563 508 565 533 540 1578 569 1576 571 527 546 500 563 509 564 533 540 532 541 1603 544 502 571 500 563 1582 565 1580 567 1578 569 504 569 502 571 1574 563 509 564 1581 566 506 567 504 569 529 544 501 572 500 563 509 564 1580 567 1578 569 529 544 501 572 500 563 509 564 507 566 1578 569 1576 571 501 572 1573 564 508 565 1606 541 504 569 1576 571 501 572 1573 564 5169 4410 4351 565 507 566 1579 568 504 569 1575 572 1573 564 1582 565 1580 567 505 568 503 570 1575 572 1573 564 1581 566 1580 567 1578 569 503 570 1575 572 1573 564 534 539 507 566 505 568 1577 570 1575 572 500 563 1582 565 507 566 1579 568 1577 570 1575 572 1573 564 1582 565 1580 567 505 568 504 569 1576 571 1574 563 1582 565 1580 567 1579 568 504 569 503 570 1574 563 509 564 1581 566 506 567 1578 569 503 570 1574 563 510 563 +# +# Model: Tosot T24H-ILF +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9072 4445 602 1586 603 477 601 478 600 479 599 480 598 481 598 482 597 482 572 1617 597 1593 572 1617 597 483 572 507 572 507 572 507 572 507 572 507 597 482 597 482 572 507 597 482 572 1618 571 507 572 507 572 507 572 507 572 507 572 507 572 1618 571 507 572 1618 572 507 572 507 572 1618 572 507 652 20153 572 507 597 482 572 507 572 507 597 482 597 482 572 507 596 483 572 507 572 507 572 507 572 507 572 507 572 1618 596 483 572 507 596 483 595 484 572 507 597 482 595 484 597 482 597 482 572 507 572 507 597 482 572 507 597 482 572 507 597 483 571 1618 597 482 675 40391 9179 4421 599 1590 599 481 597 482 597 482 597 481 598 482 597 482 597 482 597 1592 597 1592 598 1592 597 482 597 482 597 482 597 482 597 482 597 482 597 482 597 482 597 482 572 507 597 1593 597 482 572 507 572 507 572 507 572 507 572 508 571 1618 597 1593 596 1593 572 507 572 507 572 1618 597 482 678 20152 597 483 596 482 597 482 597 482 597 482 597 482 572 507 597 482 597 482 597 482 572 507 597 482 597 482 597 482 597 482 597 482 572 507 597 482 597 482 572 507 597 482 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 1618 596 482 572 507 572 +# +# Model: Tropic AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1373 348 1310 376 463 1190 1318 400 1286 401 439 1244 442 1244 1288 400 465 1218 468 1218 468 1219 467 7970 1307 404 1281 405 435 1252 1281 406 1280 406 434 1252 434 1252 1281 406 434 1253 434 1252 434 1252 434 8000 1280 406 1281 406 434 1252 1281 406 1280 406 434 1252 434 1252 1281 406 434 1253 433 1253 433 1253 434 8000 1280 406 1280 406 434 1253 1280 406 1280 406 434 1253 433 1253 1280 406 434 1253 433 1253 433 1253 433 8001 1279 406 1280 406 434 1253 1280 407 1279 407 433 1253 434 1253 1280 407 433 1253 433 1253 433 1253 433 8001 1279 407 1279 407 433 1253 1280 407 1280 407 433 1253 433 1253 1280 407 433 1253 433 1253 434 1253 433 +# +# Model: Trotec PAC2600X +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4329 4399 534 1607 511 561 534 1607 535 535 536 537 533 537 533 563 507 1606 535 1610 507 559 535 563 506 536 534 563 507 535 511 557 536 1605 535 537 533 1607 533 563 507 536 534 535 535 1605 535 1606 534 564 507 1634 506 1605 535 1635 506 1634 483 1629 512 1630 534 1607 511 1631 533 1635 506 1634 506 1608 533 1606 535 1607 511 1630 534 1609 532 1608 511 557 513 1658 483 560 510 1631 510 1631 532 565 484 562 508 1631 510 5210 4353 4396 510 559 511 1631 510 561 509 1628 513 1630 511 1658 483 1631 511 561 509 560 510 1634 508 1631 511 1658 484 1631 511 1633 509 1658 484 563 508 1658 484 559 512 1658 484 1633 509 1632 510 559 512 559 511 1631 511 559 512 562 508 560 510 561 509 558 512 559 511 559 511 560 510 560 510 560 510 560 510 560 510 560 510 560 510 587 483 587 483 1630 511 558 512 1631 510 559 511 562 508 1658 483 1630 512 560 510 +# +# Model: Vornado +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1347 405 1322 422 410 1332 1300 448 1289 480 383 1303 445 1298 439 1331 417 1299 438 1332 416 1302 1320 6834 1292 456 1292 450 413 1304 1328 445 1292 452 411 1302 446 1297 440 1303 445 1298 439 1303 445 1301 1321 6806 1320 453 1295 422 441 1301 1321 454 1294 422 441 1301 436 1305 443 1300 448 1295 442 1301 447 1298 1324 6802 1324 451 1297 420 443 1299 1323 424 1324 419 444 1298 439 1304 444 1298 439 1304 444 1299 438 1306 1326 6815 1321 453 1295 421 442 1302 1320 426 1322 421 442 1301 436 1306 442 1301 447 1296 441 1301 447 1299 1323 6802 1324 424 1324 418 445 1300 1322 424 1324 419 444 1298 439 1304 444 1299 438 1304 444 1299 438 1306 1326 6809 1328 420 1317 424 439 1306 1326 419 1318 424 439 1304 444 1299 438 1305 443 1299 438 1305 443 1302 1320 +# +# Model: Whynter AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 781 710 2930 2876 775 716 781 735 752 2175 779 711 776 2177 746 745 773 2232 753 2173 749 768 750 2177 745 745 773 743 754 737 750 741 746 744 754 737 771 746 751 739 748 2178 776 742 755 2172 771 719 778 2227 747 744 754 2200 754 737 750 2203 751 741 746 2181 773 744 753 737 750 741 746 +# +# Model: Windfree Actest2 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 548 17943 2990 9008 467 560 469 1533 469 560 468 559 468 561 467 560 468 562 467 561 466 559 468 1536 467 559 468 560 467 1535 468 1533 469 560 467 1536 467 1534 468 1533 468 1534 468 1534 467 560 468 559 468 559 469 560 469 559 469 559 468 559 468 560 468 560 467 562 467 559 469 558 469 559 468 559 469 561 468 559 468 559 468 559 468 559 469 560 469 559 468 559 468 560 468 559 468 561 467 560 469 559 468 559 468 558 469 559 469 561 468 560 467 559 469 559 468 1535 468 1533 491 2976 3014 8997 492 1509 493 536 493 535 492 534 494 535 492 535 493 537 491 536 493 533 494 1509 493 536 493 536 491 1508 494 536 493 1509 492 1510 493 1510 491 1509 494 1508 493 1511 492 534 494 534 494 534 494 535 492 536 493 535 493 533 495 534 493 536 491 535 493 537 492 534 493 535 493 534 494 534 494 536 493 535 493 535 493 534 493 534 493 536 493 534 493 533 495 534 494 534 494 535 493 536 493 534 493 535 493 534 494 534 493 536 493 534 494 534 493 534 494 535 492 2973 3017 8996 493 1508 494 536 493 535 493 535 493 533 495 535 493 536 492 536 493 534 493 1508 494 535 494 535 492 1509 493 535 494 1507 494 1510 493 533 494 1509 494 1505 496 1509 494 1508 494 1512 491 1507 494 1509 494 1508 494 533 494 535 493 534 495 1507 494 1509 494 1509 493 533 494 535 493 536 493 534 494 535 492 1508 519 509 495 1508 493 533 495 1508 493 1509 493 534 494 1508 518 1484 494 535 492 533 519 508 495 534 493 535 494 533 495 534 494 534 494 534 495 1508 493 1507 494 76867 4382 4447 533 1656 532 509 533 1655 533 1655 533 510 533 509 533 1655 533 509 532 509 533 1656 533 509 532 510 532 1657 531 1657 531 509 533 1657 532 509 532 1656 532 1656 532 1655 533 1658 531 511 530 1656 533 1657 531 1656 532 510 533 510 531 510 532 510 532 1656 532 508 534 511 532 1656 532 1655 533 1655 533 509 532 512 531 509 532 511 531 508 533 508 534 511 532 509 533 1656 532 1655 533 1657 531 1656 532 1657 532 5202 4355 4447 534 1656 533 510 531 1655 533 1655 533 508 534 508 533 1657 532 508 533 509 533 1657 531 508 533 511 532 1656 532 1656 532 509 532 1656 531 511 532 1655 533 1656 531 1655 532 1656 531 509 532 1658 531 1658 530 1656 531 510 532 510 531 509 534 510 532 1657 530 511 530 510 531 1658 531 1656 532 1656 531 509 533 509 532 510 532 510 533 510 531 509 533 508 533 509 532 1659 531 1656 532 1656 532 1655 533 1655 533 +# +# Model: Windfree Ac_test +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 549 17944 2988 9004 491 537 492 1510 491 537 490 537 490 537 492 536 491 537 491 536 491 537 491 1510 491 536 491 537 491 1509 492 1511 491 537 491 1509 492 1510 492 1509 492 1512 491 1510 491 536 491 535 492 537 492 535 492 535 492 535 493 535 493 537 492 535 493 536 491 534 494 534 493 537 492 536 492 535 493 535 493 536 492 536 492 536 493 536 491 535 492 535 493 535 493 537 492 536 491 535 492 535 493 535 492 536 493 535 492 536 492 534 493 535 493 537 492 1508 493 1510 493 2970 3017 8996 493 1508 494 533 494 534 494 534 493 534 493 535 494 533 494 533 494 537 491 1509 492 535 493 534 493 1510 493 533 494 1509 493 1508 492 1510 492 1507 494 1509 493 1507 494 534 493 536 491 533 494 535 493 534 493 534 494 535 492 535 492 537 492 535 492 535 493 534 493 534 493 535 494 533 494 534 494 534 494 536 491 537 490 535 494 534 493 535 493 534 493 534 493 535 494 535 493 535 492 534 493 534 493 536 493 534 493 535 492 534 494 534 493 535 493 536 493 2972 3015 8994 493 1508 493 535 492 534 493 535 493 535 492 536 493 536 492 534 494 537 491 1509 493 534 493 535 492 1510 493 535 492 1511 491 1509 492 534 493 1510 492 1509 492 1510 492 1509 492 1510 492 1509 492 1510 492 1509 492 535 492 536 493 533 494 1512 491 1509 492 1509 492 535 494 535 492 537 491 535 492 536 493 1510 491 535 492 1509 492 536 492 1510 492 1510 491 536 491 1510 492 1509 492 534 493 536 491 536 493 535 493 535 492 535 492 537 490 535 492 536 492 1508 493 1511 492 76863 4380 4448 507 1680 508 534 507 1682 507 1680 508 535 506 534 508 1680 507 536 507 533 508 1679 508 534 507 534 507 1680 508 1682 507 534 507 1678 509 534 507 1680 508 1682 507 1680 507 1679 508 533 508 1679 509 1681 508 1681 507 532 510 534 507 535 507 532 510 1682 507 534 508 533 508 1679 509 1680 508 1681 508 534 507 534 507 533 509 534 507 534 509 535 506 535 507 533 509 1680 508 1679 509 1682 507 1679 509 1678 509 5226 4354 4451 507 1679 509 533 508 1678 510 1682 508 534 507 532 509 1680 507 534 507 534 509 1680 508 533 509 534 507 1680 508 1680 508 534 509 1681 506 533 508 1680 508 1679 509 1681 508 1679 509 534 507 1679 509 1679 509 1681 508 533 508 533 509 533 509 534 508 1680 507 535 508 533 508 1680 508 1679 509 1680 508 535 508 534 507 534 507 534 507 533 508 535 508 534 508 533 509 1682 506 1681 507 1680 508 1682 507 1680 508 +# +# Model: Windfree Remote3 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 602 17896 3013 8976 522 505 522 1481 521 507 522 507 521 505 522 507 520 505 523 506 522 504 523 1481 521 505 522 508 521 1481 520 506 521 505 523 1478 522 1482 520 1479 522 1480 522 1480 521 506 521 507 520 509 520 506 521 505 523 506 521 506 521 507 522 506 521 506 521 506 522 503 524 508 521 505 523 506 521 504 523 506 521 506 521 507 522 505 522 504 523 505 522 506 521 507 522 505 522 506 522 506 521 505 522 508 521 506 522 507 520 506 521 1483 519 1479 522 1482 520 1480 521 2944 3041 8969 520 1482 521 506 521 507 520 506 521 507 520 506 521 507 522 506 521 505 522 1483 519 506 521 506 521 1483 518 506 522 1481 519 1482 520 506 521 1482 520 1481 519 1482 520 1479 521 1481 521 1482 518 1482 520 1482 519 508 519 508 519 508 520 1481 519 1483 494 1508 493 533 494 533 496 534 493 533 494 532 495 535 493 1506 494 1506 495 534 494 1506 495 534 493 533 494 534 494 1507 519 508 519 509 493 534 495 534 493 532 495 533 494 534 494 1507 493 1507 495 1506 495 1509 493 76841 4380 4447 508 1680 507 533 508 1678 510 1681 508 533 508 533 508 1679 508 533 508 534 508 1679 508 533 508 533 508 1679 508 1678 509 535 508 1680 507 533 508 1679 508 1679 508 536 507 1679 508 533 508 1679 508 1679 508 1680 507 535 508 534 507 1680 507 532 509 1680 507 535 506 535 507 1681 506 1679 508 1680 508 533 508 534 509 533 508 532 509 533 508 533 508 535 508 533 508 1680 508 1679 508 1679 509 1679 509 1680 509 5225 4354 4450 507 1679 508 533 508 1679 508 1679 509 532 509 535 508 1677 510 531 510 534 507 1679 508 535 508 534 507 1679 508 1679 508 533 508 1680 508 532 509 1678 509 1678 509 533 508 1679 508 534 509 1679 508 1679 508 1679 508 534 507 534 509 1679 509 532 509 1678 509 532 509 534 508 1679 509 1679 508 1680 507 532 509 534 507 534 509 532 509 532 509 533 508 532 509 534 509 1679 508 1680 507 1678 509 1679 508 1680 509 +# +# Model: Xiaomi AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 988 606 586 2210 587 1472 586 882 587 2211 586 375 584 2210 587 374 584 1472 586 374 585 376 582 375 583 400 559 400 558 883 587 375 584 375 583 2210 587 374 584 884 586 882 587 374 584 376 583 2211 586 884 585 375 584 375 583 374 584 374 584 375 584 1472 585 882 587 376 583 2211 586 884 585 2211 586 375 583 375 583 400 558 884 586 374 584 375 583 376 583 375 583 375 583 2211 586 2211 585 375 583 883 586 +# +# Model: York AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4481 655 551 655 1653 654 550 656 1652 655 1652 656 550 656 550 656 550 656 550 656 551 655 1652 655 551 655 1652 655 550 656 551 655 1654 654 550 656 550 656 551 655 1652 655 550 656 551 654 550 656 1652 655 1651 657 550 655 551 655 550 656 1654 654 550 656 1653 654 551 654 551 655 1651 656 551 655 19984 655 550 656 551 655 550 656 551 655 550 655 551 655 551 655 550 656 1654 653 1653 655 1653 655 550 655 551 655 550 656 551 655 551 655 550 656 551 655 551 655 551 655 551 655 551 655 550 656 550 656 550 656 551 655 551 655 551 655 1652 656 550 656 551 655 550 656 39996 8999 4479 656 551 655 1652 656 550 656 1653 655 1653 655 550 656 551 655 550 656 551 655 551 655 1652 655 551 655 1652 655 550 656 551 655 1653 655 551 655 550 656 550 656 1652 655 551 654 551 655 551 655 1652 655 1652 656 551 655 551 655 552 654 551 655 1653 655 1653 655 551 655 549 656 1653 655 552 654 19984 655 1652 655 551 655 550 656 1652 656 551 655 551 655 551 655 1652 655 1652 655 551 656 1652 656 1653 655 1653 655 551 655 1652 655 551 655 551 655 551 654 551 654 551 655 551 655 1653 655 550 656 551 655 1652 656 1653 654 551 655 551 655 551 655 550 655 550 656 551 655 diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index f3ff853333..20070bbe06 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -567,3 +567,4086 @@ type: parsed protocol: NECext address: 00 7F 00 00 command: 1E E1 00 00 +# # Model: Adastra WA215 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 44 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 49 00 00 00 +# +# Model: Adcom GTP500II +# +name: Power +type: parsed +protocol: NEC42 +address: 51 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC42 +address: 51 00 00 00 +command: 1F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC42 +address: 51 00 00 00 +command: 1E 00 00 00 +# +# Model: Aiwa RC-T506 +# +name: Power +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 4D 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 4E 00 00 00 +# +# Model: AIWA XR-EM300 +# +name: Mute +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 4C 00 00 00 +# +# Model: ASR EMITTER1HD +# +name: Power +type: parsed +protocol: RC5 +address: 10 00 00 00 +command: 0C 00 00 00 +# +# Model: Auna AV2_CD508 +# +name: Power +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 16 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 12 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 4B 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 04 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 13 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 5E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 06 00 00 00 +# +# Model: CambridgeAudio 650A +# +name: Next +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 2B 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 30 00 00 00 +# +# Model: AEG +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 01 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0E 00 00 00 +# +# Model: AIWA NSXR71 +# +name: Play +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 40 00 00 00 +# +name: Pause +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 44 00 00 00 +# +name: Pause +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 41 00 00 00 +# +# Model: Denon RC_1199 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 03 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 04 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 05 00 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 06 00 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 47 00 00 00 +# +name: Next +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 46 00 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 0F 00 00 00 +# +name: Next +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 0E 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 3F 00 00 00 +# +# Model: Fisher SLIM-1500 +# +name: Power +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 06 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 13 00 00 00 +# +name: Play +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 13 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 1B 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: A0 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 20 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 60 00 00 00 +# +# Model: GPX cd_radio +# +name: Pause +type: parsed +protocol: Samsung32 +address: 81 00 00 00 +command: 09 00 00 00 +# +name: Next +type: parsed +protocol: Samsung32 +address: 81 00 00 00 +command: 11 00 00 00 +# +# Model: GPX HC221B +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 04 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 00 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 01 00 00 00 +# +# Model: LG +# +name: Power +type: parsed +protocol: Samsung32 +address: 10 00 00 00 +command: 1E 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 10 00 00 00 +command: 1F 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: 10 00 00 00 +command: 05 00 00 00 +# +# Model: Marantz RC2100DR_CD +# +name: Prev +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 21 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 20 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 34 00 00 00 +# +# Model: Marantz RC2100DR_CDR +# +name: Power +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 0C 00 00 00 +# +name: Prev +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 21 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 20 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 34 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 30 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 36 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 35 00 00 00 +# +# Model: Onkyo DX-7333 +# +name: Pause +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1F E0 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1C E3 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1B E4 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 01 FE 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 00 FF 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1E E1 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1D E2 00 00 +# +# Model: Panasonic SC-HC58 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: 01 00 00 00 +# +# Model: Philips CD_720 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 11 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 10 00 00 00 +# +# Model: Philips CD_Player_723 +# +name: Prev +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 32 00 00 00 +# +# Model: Rockola juke +# +name: Power +type: parsed +protocol: NEC +address: 4D 00 00 00 +command: 00 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 4D 00 00 00 +command: 0F 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 4D 00 00 00 +command: 10 00 00 00 +# +# Model: Sony CFD-S35CP +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 44 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 44 00 00 00 +command: 13 00 00 00 +# +name: Play +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 32 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 39 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 38 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 3A 00 00 00 +# +name: Next +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 3B 00 00 00 +# +# Model: SONY minidisk-deck +# +name: Power +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 15 00 00 00 +# +name: Play +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 2A 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 29 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 28 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 20 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 2B 00 00 00 +# +# Model: Sony RM-R52_RCD-W500C_Deck-A +# +name: Play +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 32 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 39 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 38 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 30 00 00 00 +# +name: Next +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 31 00 00 00 +# +name: Next +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 34 00 00 00 +# +# Model: Sony RM-R52_RCD-W500C_Deck-B +# +name: Play +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 2A 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 29 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 28 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 20 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 21 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 2C 00 00 00 +# +# Model: TEAC RC-505_Remote_Control_Unit +# +name: Play +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 08 F7 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 05 FA 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 09 F6 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 0D F2 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 0C F3 00 00 +# +# Model: Unkown KC-806 +# +name: Power +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 00 FF 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 04 FB 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 17 E8 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 02 FD 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 01 FE 00 00 +# +# Model: Winnes KC809 +# +name: Pause +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 16 E9 00 00 +# +# Model: Denon RC1253 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 05 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 70 01 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 71 01 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 72 01 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 22 00 00 00 +# +# Model: Douk ST-01_Pro +# +name: Power +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 45 FF 00 00 +# +name: Play +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 44 FF 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 40 FF 00 00 +# +name: Next +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 43 FF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 09 FF 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 15 FF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 47 FF 00 00 +# +# Model: Edifier r1280t +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 3C C3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 4D B2 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 2B D4 00 00 +# +# Model: Edifier r1700bt +# +name: Power +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 09 F6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 0C F3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 00 FF 00 00 +# +# Model: Edifier r1855db +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 06 F9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 47 B8 00 00 +# +# Model: Edifier r2000db +# +name: Power +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 00 FF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 01 FE 00 00 +# +# Model: Emotiva PT100_TA100 +# +name: Power +type: parsed +protocol: NECext +address: 00 79 00 00 +command: 80 7F 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 79 00 00 +command: 99 66 00 00 +# +# Model: Grundig CMS_5000 +# +name: Pause +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 05 FA 00 00 +# +# Model: Harman Kardon AVI200 +# +name: Power +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C0 3F 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C1 3E 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C7 38 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C8 37 00 00 +# +# Model: DTR-7 +# +name: Power +type: parsed +protocol: NECext +address: D2 6D 00 00 +command: 04 FB 00 00 +# +name: Power +type: parsed +protocol: NECext +address: D2 6C 00 00 +command: 47 B8 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D2 6D 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D2 6D 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: D2 6D 00 00 +command: 05 FA 00 00 +# +# Model: Logitech Z906 +# +name: Power +type: parsed +protocol: NECext +address: 02 A0 00 00 +command: 80 7F 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 02 A0 00 00 +command: EA 15 00 00 +# +# Model: NAD712 +# +name: Power +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 80 7F 00 00 +# +# Model: NAD Amp_1 +# +name: Power +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: C8 37 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 02 FD 00 00 +# +# Model: Onkyo +# +name: Power +type: parsed +protocol: NECext +address: D2 6C 00 00 +command: CB 34 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 6C 00 00 +command: 54 AB 00 00 +# +# Model: Onkyo RC627S +# +name: Power +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 04 FB 00 00 +# +name: Power +type: parsed +protocol: NECext +address: D2 04 00 00 +command: 47 B8 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 03 FC 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1B E4 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1F E0 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1C E3 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 8B 74 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 8A 75 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1E E1 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1D E2 00 00 +# +# Model: Onkyo RC866M_Receiver +# +name: Prev +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 90 6F 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 80 7F 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 8F 70 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 91 6E 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 8D 72 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 8E 71 00 00 +# +# Model: Onkyo RC898M +# +name: Mute +type: parsed +protocol: NECext +address: D2 11 00 00 +command: 05 FA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D2 19 00 00 +command: 82 7D 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D2 19 00 00 +command: 83 7C 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 90 6F 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 81 7E 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 80 7F 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 8F 70 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 91 6E 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 8D 72 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 8E 71 00 00 +# +# Model: Panasonic N2QAYB000210 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: A2 02 20 00 +command: 02 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: A2 02 20 00 +command: 62 00 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: A2 02 20 00 +command: A2 00 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: AC 02 20 02 +command: B1 02 00 00 +# +# Model: Panasonic SA_PM602 +# +name: Play +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: 61 00 00 00 +# +# Model: Panasonic SC-PMX92-94 +# +name: Next +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: 31 00 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: 21 00 00 00 +# +# Model: Philips BTD2180 +# +name: Power +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 10 00 00 00 +# +name: Mute +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 2C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 11 00 00 00 +# +name: Prev +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 21 00 00 00 +# +name: Pause +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 31 00 00 00 +# +name: Next +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 20 00 00 00 +# +name: Prev +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 83 00 00 00 +# +# Model: Pioneer AXD7741 +# +name: Power +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 0A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 0B 00 00 00 +# +# Model: Pioneer VSX-D1-S +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 06 F9 00 00 +# +# Model: Pioneer XXD3105 +# +name: Play +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0C 00 00 00 +# +name: Pause +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 5B 00 00 00 +# +# Model: Portta Toslink_Audio_Switcher +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 17 00 00 00 +# +# Model: Pyle P2203ABTU +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 05 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0B 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 4D 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 54 00 00 00 +# +# Model: Rega IO +# +name: Prev +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 08 00 00 00 +# +# Model: Revo Superconnect_BZAWDFB0315H2 +# +name: Power +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 02 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 01 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 12 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 59 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 18 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 58 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 5B 00 00 00 +# +# Model: Rio Sonic_Blue_059PXC +# +name: Power +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 84 7B 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 85 7A 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 90 6F 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 83 7C 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 87 78 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 80 7F 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 8A 75 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 81 7E 00 00 +# +# Model: SiriusXM Onyx_EZR +# +name: Power +type: parsed +protocol: RC5X +address: 1B 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: RC5X +address: 1B 00 00 00 +command: 0D 00 00 00 +# +# Model: SMSL RC-8A +# +name: Power +type: parsed +protocol: NECext +address: 12 34 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 12 34 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 12 34 00 00 +command: 0B F4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 12 34 00 00 +command: 09 F6 00 00 +# +# Model: SMSL RC-8C +# +name: Power +type: parsed +protocol: NECext +address: 12 36 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 12 36 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 12 36 00 00 +command: 0B F4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 12 36 00 00 +command: 09 F6 00 00 +# +# Model: Sony Amp +# +name: Power +type: parsed +protocol: SIRC15 +address: 30 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 30 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 30 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC15 +address: 30 00 00 00 +command: 14 00 00 00 +# +# Model: Sony I-WXH-80 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 02 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 01 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0B 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 06 00 00 00 +# +# Model: Sony MHC-GS300AV +# +name: Power +type: parsed +protocol: SIRC15 +address: 10 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 10 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 10 00 00 00 +command: 13 00 00 00 +# +# Model: Sony RMT-AA320U_AV +# +name: Pause +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 38 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 31 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 30 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 7D 00 00 00 +# +# Model: Sony RM_AMU009_Sony_Audio_System_CMT +# +name: Prev +type: parsed +protocol: SIRC20 +address: 3A 07 00 00 +command: 30 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 3A 07 00 00 +command: 31 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 3A 07 00 00 +command: 34 00 00 00 +# +# Model: Sony STR-DH590 +# +name: Prev +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 33 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 34 00 00 00 +# +# Model: Firetv EVG487 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 67 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 38 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 3C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 3D 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 8C 00 00 00 +# +# Model: Audac IMEO2 +# +name: Power +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 21 DE 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 2B D4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 1D E2 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 2A D5 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 13 EC 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 14 EB 00 00 +# +# Model: Bestisan7020 +# +name: Power +type: parsed +protocol: NECext +address: 29 A1 00 00 +command: 7F 80 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 29 A1 00 00 +command: 9B 64 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 29 A1 00 00 +command: 9E 61 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 29 A1 00 00 +command: 9F 60 00 00 +# +# Model: Bose CINEMATE_1_SR +# +name: Power +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 4C B3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 03 FC 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 02 FD 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 01 FE 00 00 +# +# Model: Bose Solo_5 +# +name: Next +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 5A A5 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 59 A6 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 56 A9 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 1A E5 00 00 +# +# Model: Bose Solo_Soundbar +# +name: Mute +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 01 FD 00 00 +# +# Model: Bose Soundbar_300 +# +name: Power +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 4C B3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 03 FC 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 02 FD 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 01 FE 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 57 A8 00 00 +# +# Model: Amz snd_bar +# +name: Power +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 45 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 1B 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 51 00 00 00 +# +# Model: Soundblasterx +# +name: Power +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 08 F7 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 0C F3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 01 FE 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 07 F8 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 06 F9 00 00 +# +# Model: Canton Smart10 +# +name: Mute +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 1E E1 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 1D E2 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 18 E7 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 0A F5 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 0D F2 00 00 +# +# Model: YARRA 3DX +# +name: Power +type: parsed +protocol: NECext +address: 0A 1D 00 00 +command: 01 FE 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 0A 1D 00 00 +command: 03 FC 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 0A 1D 00 00 +command: 08 F7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 0A 1D 00 00 +command: 0A F5 00 00 +# +# Model: Craig CHT912 +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 05 00 00 00 +# +# Model: Craig CHT921 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 12 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 03 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 01 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1E 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1F 00 00 00 +# +# Model: Craig CHT939 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +# Model: Soundbar creative_stage +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 06 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 07 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 1E 00 00 00 +# +# Model: Denon RC-1230 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 52 54 32 00 +command: 83 00 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 52 54 32 00 +command: 86 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 52 54 32 00 +command: 84 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 52 54 32 00 +command: 85 00 00 00 +# +# Model: Hisense HS215 +# +name: Power +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 20 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 34 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 34 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 21 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 22 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 23 00 00 00 +# +# Model: Hitachi HSB40B16 +# +name: Power +type: parsed +protocol: NEC +address: 5B 00 00 00 +command: 80 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 5B 00 00 00 +command: 88 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 5B 00 00 00 +command: 82 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 5B 00 00 00 +command: 83 00 00 00 +# +# Model: iLive ITP280B_Remote +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1E 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 42 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 41 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +# Model: iLive Soundbar +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 14 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 10 00 00 00 +# +# Model: JBL 5_1_Soundbar +# +name: Power +type: parsed +protocol: NECext +address: 84 74 00 00 +command: FF 00 00 00 +# +# Model: JBL CINEMA_SB120 +# +name: Power +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 53 AC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: AD 52 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 26 D9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 28 D7 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 27 D8 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 25 DA 00 00 +# +# Model: JVC THBY370A +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 00 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 08 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 09 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 01 00 00 00 +# +# Model: Klipsch Soundbar +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 01 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 03 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 06 00 00 00 +# +# Model: Larksound L200 +# +name: Vol_up +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 19 E6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 16 E9 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 09 F6 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 5E A1 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 0C F3 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 18 E7 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 45 BA 00 00 +# +# Model: LG SJ4 +# +name: Power +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 1E 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 17 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 16 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 1F 00 00 00 +# +name: Play +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 4F 00 00 00 +# +name: Prev +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 06 00 00 00 +# +# Model: LG AKB74815321 +# +name: Next +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 07 00 00 00 +# +# Model: LG NB5541 +# +name: Pause +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 4F 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 05 00 00 00 +# +# Model: Majority K2_SoundBar +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 42 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 43 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 1A 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 4F 00 00 00 +# +# Model: Onn sound_bar_and_subwoofer +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 16 E9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 0F F0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 15 EA 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 0C F3 00 00 +# +# Model: Panasonic SCH-TB8 +# +name: Power +type: parsed +protocol: Kaseikyo +address: A0 02 20 00 +command: D0 03 00 00 +# +# Model: Pheanoo P27 +# +name: Power +type: parsed +protocol: NECext +address: 83 B6 00 00 +command: 4D B2 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 B6 00 00 +command: 43 BC 00 00 +# +# Model: Philips HTL2161B_Soundbar +# +name: Power +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 11 00 00 00 +# +# Model: Philips HTL2163_Soundbar +# +name: Mute +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 0D 00 00 00 +# +# Model: Philips TAB5105_79 +# +name: Power +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: C7 00 00 00 +# +name: Next +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 20 00 00 00 +# +name: Prev +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 21 00 00 00 +# +name: Play +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 2C 00 00 00 +# +# Model: Polk RE9114_1 +# +name: Power +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 00 FF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 20 DF 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 26 D9 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 21 DE 00 00 +# +name: Next +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 24 DB 00 00 +# +# Model: POLK RE9641_1 +# +name: Vol_up +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 1E E1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 1F E0 00 00 +# +# Model: Promethean ActivSoundBar_ASB-40 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 05 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 08 00 00 00 +# +# Model: Quantis LSW-1 +# +name: Power +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 12 ED 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1D E2 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1A E5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 03 FC 00 00 +# +# Model: RCA RTS7110B2 +# +name: Power +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 5C 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 45 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 45 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 4E 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 55 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 59 00 00 00 +# +# Model: Sennheiser Ambeo_Soundbar +# +name: Power +type: parsed +protocol: RC5 +address: 13 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 13 00 00 00 +command: 0D 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 13 00 00 00 +command: 0E 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 13 00 00 00 +command: 0C 00 00 00 +# +# Model: Sonos ARC_Beam_Playbar_Playbase +# +name: Vol_up +type: parsed +protocol: NECext +address: 80 D9 00 00 +command: 8A 75 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 80 D9 00 00 +command: 88 77 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 80 D9 00 00 +command: 8C 73 00 00 +# +# Model: Sony Old_XBR +# +name: Power +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 14 00 00 00 +# +# Model: Sony Soundbar_HT-XT3 +# +name: Play +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 32 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 39 00 00 00 +# +# Model: TaoTronics TT-SK023_Sound_Bar +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 12 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 0D 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1E 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 0A 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1F 00 00 00 +# +# Model: Taotronics TT-SK026 +# +name: Power +type: parsed +protocol: NECext +address: D9 14 00 00 +command: 6D 92 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: D9 14 00 00 +command: 6E 91 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D9 14 00 00 +command: 4F B0 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D9 14 00 00 +command: 50 AF 00 00 +# +# Model: TCL TS5010 +# +name: Pause +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 2A D5 00 00 +# +# Model: Teufel Cinebase_Soundbar +# +name: Power +type: parsed +protocol: NECext +address: EF 01 00 00 +command: 25 DA 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: EF 01 00 00 +command: 14 EB 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: EF 01 00 00 +command: 13 EC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: EF 01 00 00 +command: 28 D7 00 00 +# +# Model: Toshiba SBX4250 +# +name: Power +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 12 ED 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 1F E0 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 1F E0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: BD 42 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 60 9F 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 61 9E 00 00 +# +# Model: Vizio SB3651_E6 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 41 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 48 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8B 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8A 00 00 00 +# +# Model: Vizio V51-H6 +# +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8E 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8E 00 00 00 +# +# Model: Binnifa Live1T +# +name: Mute +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 50 00 00 00 +# +# Model: Yamaha BAR400 +# +name: Power +type: parsed +protocol: NEC +address: 78 00 00 00 +command: CC 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 1E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 1F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 9C 00 00 00 +# +# Model: Audioengine a5 +# +name: Mute +type: parsed +protocol: NECext +address: 00 FD 00 00 +command: 03 FC 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 FD 00 00 +command: 09 F6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 FD 00 00 +command: 07 F8 00 00 +# +# Model: Biseoamz DY29S +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +# Model: Bumpboxx +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +# Model: Como audio +# +name: Power +type: parsed +protocol: NEC +address: 77 00 00 00 +command: F1 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 77 00 00 00 +command: F3 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 77 00 00 00 +command: F7 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 77 00 00 00 +command: FB 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 77 00 00 00 +command: FC 00 00 00 +# +# Model: Craig CHT729 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 47 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 +# +# Model: Craig CHT904 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 0E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 1A 00 00 00 +# +# Model: Creative z5400 +# +name: Power +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 10 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 1A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 0E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 16 00 00 00 +# +# Model: DollarTec BT5AmpBoard +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +# Model: EASTERN DA_9000 +# +name: Power +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 1D 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 18 00 00 00 +# +# Model: Edifier AirPulse_A80 +# +name: Power +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 00 FF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 19 E6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 1C E3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 18 E7 00 00 +# +# Model: Edifier R1280DB +# +name: Prev +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 06 F9 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 08 F7 00 00 +# +# Model: Edifier R1700BT +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 2B D4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 3C C3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 1A E5 00 00 +# +# Model: Edifier R1700BTs +# +name: Prev +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 44 BB 00 00 +# +# Model: Edifier R2800 +# +name: Mute +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 09 F6 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 01 FE 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 02 FD 00 00 +# +# Model: Edifier RC80B +# +name: Next +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 5D A2 00 00 +# +# Model: Edifier S360DB +# +name: Power +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 0F F0 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 03 FC 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 40 BF 00 00 +# +# Model: Fluance AI40 +# +name: Power +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 18 E7 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 55 AA 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 59 A6 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 15 EA 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 16 E9 00 00 +# +# Model: Dream Aurora_AC6923 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 57 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 03 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 10 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 13 00 00 00 +# +# Model: Quacker LED_Speaker +# +name: Prev +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0D F2 00 00 +# +# Model: Geneva Sound_System_Model_L +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 00 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 06 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0B 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 11 00 00 00 +# +# Model: IBIZA PORT_15_UHF +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 48 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1D 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 11 00 00 00 +# +# Model: JBL Cinema_CB150 +# +name: Power +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 1B E4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 2A D5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 14 EB 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 13 EC 00 00 +# +# Model: JBL LSR4326P +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 14 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 08 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 00 00 00 00 +# +# Model: JBL On_Stage_IIIP_Speaker +# +name: Mute +type: parsed +protocol: NECext +address: 40 AF 00 00 +command: 19 E6 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 40 AF 00 00 +command: 12 ED 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 40 AF 00 00 +command: 05 FA 00 00 +# +# Model: JVC D62BM +# +name: Play +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 0D F2 00 00 +# +# Model: KEF LSX_Speakers +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 02 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 04 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 4B 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 4A 00 00 00 +# +# Model: Klipsch fives +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1A 00 00 00 +# +# Model: Klipsch r15pm +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 0A 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 07 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 02 00 00 00 +# +# Model: Logi WD216XM +# +name: Power +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 02 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 08 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 01 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 05 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 04 00 00 00 +# +# Model: Z906 +# +name: Vol_up +type: parsed +protocol: NECext +address: 02 A0 00 00 +command: AA 55 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 02 A0 00 00 +command: 6A 95 00 00 +# +# Model: Microlab RC071 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 02 00 00 00 +# +# Model: Microlab SOLO11 +# +name: Prev +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 5A 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 5B 00 00 00 +# +# Model: Naim Muso +# +name: Power +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 11 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 20 00 00 00 +# +# Model: Sangean RC30 +# +name: Vol_up +type: parsed +protocol: NEC42 +address: 01 00 00 00 +command: 0C 00 00 00 +# +# Model: Sony Amp_STR-DE875 +# +name: Power +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 2E 00 00 00 +# +name: Power +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 2F 00 00 00 +# +name: Play +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 0C 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 0C 00 00 00 +# +# Model: Sony SRS-GU10iP +# +name: Power +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 15 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 14 00 00 00 +# +name: Play +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 28 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 13 00 00 00 +# +name: Next +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 29 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 2A 00 00 00 +# +# Model: Steljes Desktop_Speakers +# +name: Power +type: parsed +protocol: NECext +address: 85 23 00 00 +command: 99 66 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 85 23 00 00 +command: 57 A8 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 85 23 00 00 +command: 47 B8 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 85 23 00 00 +command: 97 68 00 00 +# +# Model: Teufel 3SIXTY +# +name: Power +type: parsed +protocol: NEC +address: FD 00 00 00 +command: E2 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: FD 00 00 00 +command: E1 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: FD 00 00 00 +command: E7 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: FD 00 00 00 +command: B9 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: FD 00 00 00 +command: EA 00 00 00 +# +# Model: Teufel CEM500RC +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 13 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 17 00 00 00 +# +# Model: Toshiba RM-V329 +# +name: Power +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 12 ED 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 11 EE 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 10 EF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 13 EC 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 03 FC 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 07 F8 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 06 F9 00 00 +# +# Model: Technics EUR646496 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: D0 03 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: 90 02 20 00 +command: A0 00 00 00 +# +name: Next +type: parsed +protocol: Kaseikyo +address: AA 02 20 01 +command: A0 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: AA 02 20 00 +command: 00 00 00 00 +# +# Model: terratec m3po +# +name: Mute +type: parsed +protocol: RC5X +address: 0A 00 00 00 +command: 2F 00 00 00 +# +name: Play +type: parsed +protocol: RC5X +address: 0A 00 00 00 +command: 31 00 00 00 +# +name: Pause +type: parsed +protocol: RC5X +address: 0A 00 00 00 +command: 0F 00 00 00 +# +name: Pause +type: parsed +protocol: RC5X +address: 0A 00 00 00 +command: 2C 00 00 00 +# +# Model: X4 TECH_TU-1200 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 04 00 00 00 +# +# Model: YAMAHA AX-380 +# +name: Power +type: parsed +protocol: NEC +address: 7A 00 00 00 +command: 1F 00 00 00 +# +# Model: Yamaha RAS5 +# +name: Power +type: parsed +protocol: NECext +address: 7E 81 00 00 +command: 2A D4 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 7A 85 00 00 +command: 1A E4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 7A 85 00 00 +command: 1B E5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 7A 85 00 00 +command: 1C E2 00 00 +# +# Model: Yamaha RAV203_V473170_US +# +name: Mute +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 05 00 00 00 +command: 29 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 7A 00 00 00 +command: 59 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 05 00 00 00 +command: 35 00 00 00 +# +# Model: Yamaha WS19340 +# +name: Power +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 0F 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 02 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 04 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 03 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 01 00 00 00 +# +# Model: Yamaha ZP45780 +# +name: Prev +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 6A 95 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 6B 94 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 68 97 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 67 98 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 69 96 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/projector.ir b/applications/main/infrared/resources/infrared/assets/projector.ir index 5a161d153c..c63d5a3128 100644 --- a/applications/main/infrared/resources/infrared/assets/projector.ir +++ b/applications/main/infrared/resources/infrared/assets/projector.ir @@ -798,6 +798,8 @@ protocol: NECext address: 83 F4 00 00 command: 17 E8 00 00 # +# Model: ViewSonic X1_Projector +# name: Vol_up type: raw frequency: 38000 @@ -839,3 +841,351 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 9010 4253 564 566 566 1671 566 1699 565 565 539 568 564 566 565 539 566 1699 565 1672 565 566 566 1672 564 567 565 1672 565 567 565 567 564 541 564 1698 566 539 565 567 565 567 562 542 565 1699 564 539 567 1699 565 540 564 1698 566 1672 565 1698 566 1672 565 567 565 1671 565 566 566 +# +# Model: Apeman LC650_ +# +name: Power +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 01 FE 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 6A 95 00 00 +# +# Model: BenQ MH856UST +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 97 68 00 00 +# +# Model: BenQ TRY01 +# +name: Power +type: parsed +protocol: NECext +address: 04 B1 00 00 +command: 58 A7 00 00 +# +# Model: Generic Universal_Remote +# +name: Power +type: parsed +protocol: NECext +address: 48 50 00 00 +command: 02 FD 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 48 50 00 00 +command: 27 D8 00 00 +# +# Model: Coolux X3S +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 00 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 1A 00 00 00 +# +# Model: Dell projector +# +name: Power +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 02 FD 00 00 +# +# Model: Dell tsfm_ir01 +# +name: Mute +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 0F F0 00 00 +# +# Model: Epson 4650 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2288 611 571 587 1153 587 572 587 572 1167 572 1168 571 587 573 587 572 587 572 589 570 587 572 587 572 587 572 588 571 77346 2287 611 571 588 1152 588 571 588 571 1168 571 1168 571 588 571 588 571 588 571 589 570 587 572 588 572 587 572 587 572 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2288 610 572 588 1151 587 573 588 571 1169 570 586 573 587 572 587 1152 587 573 586 1153 588 572 587 573 586 573 587 572 76771 2289 610 572 588 1151 587 573 587 572 1166 573 587 572 587 572 587 1152 588 571 588 1151 587 573 587 572 587 573 588 571 +# +# Model: Epson EHTW5650 +# +name: Mute +type: parsed +protocol: NECext +address: 83 55 00 00 +command: AD 52 00 00 +# +# Model: Epson EMP822H +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 55 00 00 +command: B1 4E 00 00 +# +# Model: Epson projector_Power_Only +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500 +# +# Model: Gateway 210_projextor +# +name: Power +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 0B 00 00 00 +# +# Model: Groview +# +name: Power +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 4A B5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 0E F1 00 00 +# +# Model: Infocus Navigator_3 +# +name: Power +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 17 E8 00 00 +# +# Model: JVC LX-UH1B +# +name: Power +type: parsed +protocol: NECext +address: 00 6A 00 00 +command: 40 BF 00 00 +# +# Model: LG PH300-NA +# +name: Power +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: AD 52 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 09 F6 00 00 +# +# Model: Maxell MC-EU5001 +# +name: Mute +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 52 AD 00 00 +# +# Model: NexiGo-PJ20 +# +name: Mute +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 02 00 00 00 +# +# Model: Optoma projector +# +name: Vol_up +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 07 F8 00 00 +# +# Model: Optoma Remote_HOF04K276D6 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 0A F5 00 00 +# +# Model: Optoma UHZ45 +# +name: Mute +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 03 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 09 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 0C 00 00 00 +# +# Model: Philips PicoPix_Max_PPX620_Projector +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 12 00 00 00 +# +# Model: PVO YG300Pro +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 40 00 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9107 4376 681 1573 681 472 655 472 654 474 652 475 652 476 651 476 652 476 651 476 651 1604 651 1604 651 1604 651 1603 652 1604 651 1604 651 1604 651 1604 650 476 651 477 650 477 650 476 651 477 651 1604 650 476 651 477 650 1604 651 1604 650 1604 651 1604 650 1604 651 477 650 1604 650 39498 9079 2178 651 +# +# Model: RIF6-cube-projector-raw +# +name: Power +type: raw +frequency: 36045 +duty_cycle: 0.330000 +data: 9024 4506 570 582 542 582 518 606 518 606 518 606 518 606 518 606 518 610 518 1698 548 1698 546 1700 546 1700 546 1698 546 1700 546 1696 548 1702 570 1674 546 610 516 1698 546 606 542 582 542 582 544 1674 546 610 520 606 542 1674 570 582 542 1676 546 1698 570 1674 572 582 520 1698 546 +# +name: Mute +type: raw +frequency: 36045 +duty_cycle: 0.330000 +data: 9044 4484 572 580 544 580 544 580 544 580 542 582 544 580 520 604 542 586 544 1674 570 1674 570 1676 570 1674 572 1672 570 1674 546 1700 568 1678 570 582 542 1672 572 580 542 580 542 1674 566 582 542 1672 570 584 538 1674 564 580 534 1674 556 1674 556 580 530 1672 558 582 524 1676 552 +# +# Model: Samsung Freestyle_Gen2 +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 02 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: D1 00 00 00 +# +# Model: Samsung VG-TM2360E +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1171 433 566 433 881 433 2381 433 1486 434 565 434 1486 433 1776 433 2380 433 565 434 2381 433 1170 434 87358 1220 1171 433 566 433 883 431 2381 433 1486 433 567 432 1487 432 1775 434 2381 432 566 433 2380 434 1171 433 86252 1221 1172 432 565 434 880 435 2381 433 1486 433 565 434 1487 432 1776 433 2380 434 566 433 2379 434 1170 434 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1173 431 566 433 882 432 2382 432 1487 432 566 433 565 434 566 433 2671 432 2670 433 2381 433 566 433 87411 324 937 325 358 325 647 326 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1172 432 566 433 882 433 2381 432 1486 434 566 433 565 434 882 433 1486 434 2669 434 2065 433 566 433 87779 324 936 326 358 325 647 326 +# +# Model: Sharp RRMCGA664WJSA_Notevision XR-32S-L +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 293 1801 296 753 295 1801 296 1801 296 752 296 754 294 1801 296 1800 297 752 296 1802 295 752 296 1801 296 753 295 1800 297 752 296 42709 296 1800 297 753 295 1800 297 1800 297 753 295 1802 295 753 295 753 295 1801 296 753 295 1801 296 754 294 1802 295 753 295 1801 296 42694 295 1800 297 752 296 1803 294 1803 294 753 295 753 295 1801 296 1802 295 752 296 1802 295 752 296 1801 296 753 295 1802 295 753 295 42709 295 1802 295 753 295 1803 294 1801 296 753 295 1802 295 752 296 752 296 1801 296 752 296 1803 294 754 294 1803 294 754 294 1804 293 42694 294 1802 294 755 293 1803 294 1804 268 779 269 779 269 1828 269 1828 269 780 268 1829 268 778 270 1829 323 725 268 1829 268 781 324 +# +# Model: SMART Projectors +# +name: Power +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 12 ED 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 11 EE 00 00 +# +# Model: Sony RM_PJ27 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 13 00 00 00 +# +# Model: TopVision +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 11 00 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 724cb90cec..ae3c4d4b44 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1978,3 +1978,1682 @@ type: parsed protocol: NECext address: 00 7F 00 00 command: 18 E7 00 00 +# # Model: FireTV Omni_Series_4K +# +name: Power +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 46 B9 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 19 E6 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 4C B3 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 0F F0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 5A A5 00 00 +# +# Model: Android TV_MXQ +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 40 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 18 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 41 00 00 00 +# +# Model: APEX LE4643T +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 0A F5 00 00 +# +# Model: BAIRD T15011DLEDDS_RC-6 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0C 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0D 00 00 00 +# +# Model: BBK TV_LEM-1071 +# +name: Power +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 4B B4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 4F B0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 08 F7 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 09 F6 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 05 FA 00 00 +# +# Model: BGH BLE2814D +# +name: Power +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 15 EA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 16 E9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 1A E5 00 00 +# +# Model: Blaupunkt +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 50 AF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1E E1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5F A0 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5C A3 00 00 +# +# Model: Blitzwolf BWPCM2 +# +name: Power +type: parsed +protocol: NECext +address: A0 B7 00 00 +command: E9 16 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: A0 B7 00 00 +command: AF 50 00 00 +# +# Model: Bolva TV +# +name: Ch_prev +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 41 BE 00 00 +# +# Model: Bose TV +# +name: Power +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 4C B3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 01 FE 00 00 +# +# Model: BUSH TV_VL32HDLED +# +name: Power +type: parsed +protocol: NEC +address: 08 00 00 00 +command: D7 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 80 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 8E 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 83 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 86 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 08 00 00 00 +command: DF 00 00 00 +# +# Model: CCE RC512_Remote +# +name: Power +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 40 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 12 00 00 00 +# +# Model: ContinentalEdison +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5C A3 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 48 B7 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 44 BB 00 00 +# +# Model: ContinentalEdison CELED32JBL7 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 12 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 14 00 00 00 +# +# Model: Crown 22111 +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 52 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 53 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 09 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 03 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +# Model: Daewood Parsed +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 82 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 9F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 8B 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 9B 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 8F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: D0 00 00 00 +# +# Model: Dual DL-32HD-002 +# +name: Vol_up +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 0F F0 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 5A A5 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 0C F3 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 19 E6 00 00 +# +# Model: Dynex DX-RC01A-12 +# +name: Power +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0F F0 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0D F2 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0A F5 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0B F4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0E F1 00 00 +# +# Model: DYON Movie_Smart_32_XT +# +name: Power +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 0F F0 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 15 EA 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 1C E3 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 1E E1 00 00 +# +# Model: EdenWood TV +# +name: Ch_next +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 14 00 00 00 +# +# Model: Elitelux L32HD1000 +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 15 EA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1B E4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1A E5 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 19 E6 00 00 +# +# Model: Enseo +# +name: Power +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 02 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 06 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 0C 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 08 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 0E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 04 00 00 00 +# +# Model: Fetch TV_Box_AUS +# +name: Power +type: parsed +protocol: NECext +address: 64 46 00 00 +command: 5D A2 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 10 00 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 64 46 00 00 +command: DE 21 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 64 46 00 00 +command: DB 24 00 00 +# +# Model: Furrion +# +name: Ch_next +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 02 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 03 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +# Model: AORUS Monitor +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1A 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 33 00 00 00 +# +# Model: Grandin +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 12 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 10 00 00 00 +# +# Model: Grandin Unknown_Model +# +name: Vol_up +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 1A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 1E 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 1B 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 1F 00 00 00 +# +# Model: GRUNDIG +# +name: Ch_next +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 20 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 21 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 11 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0D 00 00 00 +# +# Model: Hitachi 43140 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 11 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 0D 00 00 00 +# +name: Ch_next +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 20 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 21 00 00 00 +# +# Model: Hitachi LE46H508 +# +name: Mute +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 15 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 19 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 18 00 00 00 +# +# Model: KRAFT KTV +# +name: Power +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 0B F4 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 1E E1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 5F A0 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 5C A3 00 00 +# +# Model: LG 27GR95QE_TV +# +name: Power +type: parsed +protocol: NECext +address: 04 F4 00 00 +command: 08 F7 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 04 F4 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 04 F4 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 04 F4 00 00 +command: 09 F6 00 00 +# +# Model: LG Hotel_TV_Home2 +# +name: Power +type: parsed +protocol: NECext +address: 69 69 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 69 69 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 69 69 00 00 +command: 0B F4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 69 69 00 00 +command: 0E F1 00 00 +# +# Model: LG MR21GC_Magic_Remote +# +name: Ch_next +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 34 00 00 00 +# +name: Ch_prev +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 33 00 00 00 +# +# Model: lodgenet lrc3220 +# +name: Power +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 80 7F 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 8F 70 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 93 6C 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 8D 72 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 91 6E 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 97 68 00 00 +# +# Model: LOEWE TV +# +name: Ch_next +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 17 00 00 00 +# +# Model: Manta +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 48 B7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 49 B6 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 01 FE 00 00 +# +# Model: Manta TV +# +name: Power +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 00 FF 00 00 +# +# Model: Manta TV_2 +# +name: Power +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 5F 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 40 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 5D 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 03 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 1F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 1C 00 00 00 +# +# Model: Matsui 1435b +# +name: Power +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 14 00 00 00 +# +name: Ch_prev +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 11 00 00 00 +# +name: Ch_next +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 13 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 12 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 15 00 00 00 +# +# Model: Medion MD21302 +# +name: Power +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 18 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 56 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 4F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 0D 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 4C 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 0F 00 00 00 +# +# Model: Mivar LCD_TV +# +name: Power +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 0A F5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 58 A7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 4B B4 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 1E E1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 0F F0 00 00 +# +# Model: NEC E425 +# +name: Power +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 01 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 10 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 18 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 1C 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 04 00 00 00 +# +# Model: Neo TV +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 16 E9 00 00 +# +# Model: Panasonic 58JX800_Series +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 00 02 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 10 02 00 00 +# +# Model: Panasonic N2QAYA_152 +# +name: Ch_next +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 40 03 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 20 03 00 00 +# +# Model: Panasonic N2QAYB000705 +# +name: Ch_prev +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 50 03 00 00 +# +# Model: Panasonic N2QAYB000752_Full +# +name: Ch_prev +type: parsed +protocol: Kaseikyo +address: B0 02 20 00 +command: 50 03 00 00 +# +name: Ch_next +type: parsed +protocol: Kaseikyo +address: B0 02 20 00 +command: 40 03 00 00 +# +# Model: Panasonic TH-43HS550K +# +name: Power +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 14 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 15 00 00 00 +# +name: Ch_next +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 12 00 00 00 +# +name: Ch_prev +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 0D 00 00 00 +# +# Model: Philips 22IT_TV_Monitor +# +name: Power +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 10 EF 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 18 E7 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 1C E3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 04 FB 00 00 +# +# Model: Philips 32PFL4208T +# +name: Ch_next +type: parsed +protocol: RC6 +address: 00 00 00 00 +command: 4C 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC6 +address: 00 00 00 00 +command: 4D 00 00 00 +# +# Model: Philips TV_14PV172_08 +# +name: Ch_next +type: parsed +protocol: RC5X +address: 00 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5X +address: 00 00 00 00 +command: 11 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5X +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5X +address: 00 00 00 00 +command: 15 00 00 00 +# +# Model: Pioneer Kuro_PDP_LX508A +# +name: Power +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0B 00 00 00 +# +name: Mute +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 49 00 00 00 +# +# Model: Samsung +# +name: Ch_next +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 10 00 00 00 +# +# Model: Samsung AA59-00741A +# +name: Ch_prev +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 12 00 00 00 +# +# Model: Samsung BN59-01180A +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 98 00 00 00 +# +# Model: Samsung Broadband_Hospitality +# +name: Ch_next +type: parsed +protocol: SIRC20 +address: 5A 0E 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: SIRC20 +address: 5A 0E 00 00 +command: 11 00 00 00 +# +# Model: Sanyo DP26640 +# +name: Ch_next +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0A 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 18 00 00 00 +# +# Model: Sceptre 8142026670003C +# +name: Ch_next +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 14 00 00 00 +# +# Model: Sharp g0684cesa_NES_TV +# +name: Ch_next +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0C 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 10 00 00 00 +# +# Model: Silver LE410004 +# +name: Power +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 0C F3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 10 EF 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 0E F1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 11 EE 00 00 +# +# Model: Soniq QSP500TV6 +# +name: Power +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 10 EF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 11 EE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 58 A7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 5B A4 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 5E A1 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 56 A9 00 00 +# +# Model: Sony RM-V310 +# +name: Vol_up +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 14 00 00 00 +# +name: Ch_next +type: parsed +protocol: SIRC +address: 0D 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: SIRC +address: 0D 00 00 00 +command: 11 00 00 00 +# +# Model: Sony XBR +# +name: Power +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 2E 00 00 00 +# +# Model: Strong RCU-Z400N +# +name: Power +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 1C 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 5F 00 00 00 +# +# Model: Sunbrite +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1C 00 00 00 +# +# Model: SWEEX Generic_Monitor +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 11 00 00 00 +# +# Model: TCL Roku_TV +# +name: Power +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 97 68 00 00 +# +# Model: Vitec Exterity_IPTV +# +name: Power +type: parsed +protocol: NECext +address: AD ED 00 00 +command: B5 4A 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: AD ED 00 00 +command: BA 45 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: AD ED 00 00 +command: BB 44 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: AD ED 00 00 +command: B0 4F 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: AD ED 00 00 +command: B1 4E 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: AD ED 00 00 +command: C5 3A 00 00 From 5b36c5465d0b63f5dda428948345162ae328a9b3 Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 8 Oct 2024 21:50:31 +0200 Subject: [PATCH 127/236] FuriThread: Improve state callbacks (#3881) State callbacks assumed they were invoked from the thread that changed its state, but this wasn't true for FuriThreadStateStarting in the past, and now it's not true for FuriThreadStateStopped either. Now it is safe to release the thread memory form the state callback once it switches to FuriThreadStateStopped. Therefore, pending deletion calls can be removed. Co-authored-by: Aleksandr Kutuzov --- applications/services/loader/loader.c | 8 ++- applications/services/region/region.c | 15 ++--- applications/services/rpc/rpc.c | 67 +++++++++---------- .../system/updater/util/update_task.c | 8 +-- furi/core/thread.c | 16 ++--- furi/core/thread.h | 6 +- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 8 files changed, 58 insertions(+), 66 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index b76b38c25c..72cac4b626 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -308,12 +308,14 @@ static void loader_applications_closed_callback(void* context) { furi_message_queue_put(loader->queue, &message, FuriWaitForever); } -static void loader_thread_state_callback(FuriThreadState thread_state, void* context) { +static void + loader_thread_state_callback(FuriThread* thread, FuriThreadState thread_state, void* context) { + UNUSED(thread); furi_assert(context); - Loader* loader = context; - if(thread_state == FuriThreadStateStopped) { + Loader* loader = context; + LoaderMessage message; message.type = LoaderMessageTypeAppClosed; furi_message_queue_put(loader->queue, &message, FuriWaitForever); diff --git a/applications/services/region/region.c b/applications/services/region/region.c index dffcc6b2d5..bed676f9bd 100644 --- a/applications/services/region/region.c +++ b/applications/services/region/region.c @@ -104,19 +104,12 @@ static int32_t region_load_file(void* context) { return 0; } -static void region_loader_pending_callback(void* context, uint32_t arg) { - UNUSED(arg); - - FuriThread* loader = context; - furi_thread_join(loader); - furi_thread_free(loader); -} - -static void region_loader_state_callback(FuriThreadState state, void* context) { +static void + region_loader_release_callback(FuriThread* thread, FuriThreadState state, void* context) { UNUSED(context); if(state == FuriThreadStateStopped) { - furi_timer_pending_callback(region_loader_pending_callback, furi_thread_get_current(), 0); + furi_thread_free(thread); } } @@ -126,7 +119,7 @@ static void region_storage_callback(const void* message, void* context) { if(event->type == StorageEventTypeCardMount) { FuriThread* loader = furi_thread_alloc_ex(NULL, 2048, region_load_file, NULL); - furi_thread_set_state_callback(loader, region_loader_state_callback); + furi_thread_set_state_callback(loader, region_loader_release_callback); furi_thread_start(loader); } } diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 00ec2259c7..08a2c3f6de 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -67,7 +67,7 @@ static RpcSystemCallbacks rpc_systems[] = { struct RpcSession { Rpc* rpc; - FuriThread* thread; + FuriThreadId thread_id; RpcHandlerDict_t handlers; FuriStreamBuffer* stream; @@ -172,7 +172,7 @@ size_t rpc_session_feed( size_t bytes_sent = furi_stream_buffer_send(session->stream, encoded_bytes, size, timeout); - furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtNewData); + furi_thread_flags_set(session->thread_id, RpcEvtNewData); return bytes_sent; } @@ -220,7 +220,7 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { break; } else { /* Save disconnect flag and continue reading buffer */ - furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); + furi_thread_flags_set(session->thread_id, RpcEvtDisconnect); } } else if(flags & RpcEvtNewData) { // Just wake thread up @@ -347,35 +347,32 @@ static int32_t rpc_session_worker(void* context) { return 0; } -static void rpc_session_thread_pending_callback(void* context, uint32_t arg) { - UNUSED(arg); - RpcSession* session = (RpcSession*)context; +static void rpc_session_thread_release_callback( + FuriThread* thread, + FuriThreadState thread_state, + void* context) { + if(thread_state == FuriThreadStateStopped) { + RpcSession* session = (RpcSession*)context; - for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { - if(rpc_systems[i].free) { - (rpc_systems[i].free)(session->system_contexts[i]); + for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { + if(rpc_systems[i].free) { + (rpc_systems[i].free)(session->system_contexts[i]); + } } - } - free(session->system_contexts); - free(session->decoded_message); - RpcHandlerDict_clear(session->handlers); - furi_stream_buffer_free(session->stream); - - furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); - if(session->terminated_callback) { - session->terminated_callback(session->context); - } - furi_mutex_release(session->callbacks_mutex); - - furi_mutex_free(session->callbacks_mutex); - furi_thread_join(session->thread); - furi_thread_free(session->thread); - free(session); -} + free(session->system_contexts); + free(session->decoded_message); + RpcHandlerDict_clear(session->handlers); + furi_stream_buffer_free(session->stream); + + furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); + if(session->terminated_callback) { + session->terminated_callback(session->context); + } + furi_mutex_release(session->callbacks_mutex); -static void rpc_session_thread_state_callback(FuriThreadState thread_state, void* context) { - if(thread_state == FuriThreadStateStopped) { - furi_timer_pending_callback(rpc_session_thread_pending_callback, context, 0); + furi_mutex_free(session->callbacks_mutex); + furi_thread_free(thread); + free(session); } } @@ -407,12 +404,14 @@ RpcSession* rpc_session_open(Rpc* rpc, RpcOwner owner) { }; rpc_add_handler(session, PB_Main_stop_session_tag, &rpc_handler); - session->thread = furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); + FuriThread* thread = + furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); + session->thread_id = furi_thread_get_id(thread); - furi_thread_set_state_context(session->thread, session); - furi_thread_set_state_callback(session->thread, rpc_session_thread_state_callback); + furi_thread_set_state_context(thread, session); + furi_thread_set_state_callback(thread, rpc_session_thread_release_callback); - furi_thread_start(session->thread); + furi_thread_start(thread); return session; } @@ -424,7 +423,7 @@ void rpc_session_close(RpcSession* session) { rpc_session_set_send_bytes_callback(session, NULL); rpc_session_set_close_callback(session, NULL); rpc_session_set_buffer_is_empty_callback(session, NULL); - furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); + furi_thread_flags_set(session->thread_id, RpcEvtDisconnect); } void rpc_on_system_start(void* p) { diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index cca488475e..9db8339efc 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -395,14 +395,15 @@ bool update_task_open_file(UpdateTask* update_task, FuriString* filename) { return open_success; } -static void update_task_worker_thread_cb(FuriThreadState state, void* context) { - UpdateTask* update_task = context; +static void + update_task_worker_thread_cb(FuriThread* thread, FuriThreadState state, void* context) { + UNUSED(context); if(state != FuriThreadStateStopped) { return; } - if(furi_thread_get_return_code(update_task->thread) == UPDATE_TASK_NOERR) { + if(furi_thread_get_return_code(thread) == UPDATE_TASK_NOERR) { furi_delay_ms(UPDATE_DELAY_OPERATION_OK); furi_hal_power_reset(); } @@ -427,7 +428,6 @@ UpdateTask* update_task_alloc(void) { furi_thread_alloc_ex("UpdateWorker", 5120, NULL, update_task); furi_thread_set_state_callback(thread, update_task_worker_thread_cb); - furi_thread_set_state_context(thread, update_task); #ifdef FURI_RAM_EXEC UNUSED(update_task_worker_backup_restore); furi_thread_set_callback(thread, update_task_worker_flash_writer); diff --git a/furi/core/thread.c b/furi/core/thread.c index 65787c0e0f..3990dd63d2 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -33,7 +33,7 @@ struct FuriThread { StaticTask_t container; StackType_t* stack_buffer; - FuriThreadState state; + volatile FuriThreadState state; int32_t ret; FuriThreadCallback callback; @@ -59,7 +59,6 @@ struct FuriThread { // this ensures that the size of this structure is minimal bool is_service; bool heap_trace_enabled; - volatile bool is_active; }; // IMPORTANT: container MUST be the FIRST struct member @@ -84,7 +83,7 @@ static void furi_thread_set_state(FuriThread* thread, FuriThreadState state) { furi_assert(thread); thread->state = state; if(thread->state_callback) { - thread->state_callback(state, thread->state_context); + thread->state_callback(thread, state, thread->state_context); } } @@ -124,7 +123,7 @@ static void furi_thread_body(void* context) { // flush stdout __furi_thread_stdout_flush(thread); - furi_thread_set_state(thread, FuriThreadStateStopped); + furi_thread_set_state(thread, FuriThreadStateStopping); vTaskDelete(NULL); furi_thread_catch(); @@ -207,7 +206,6 @@ void furi_thread_free(FuriThread* thread) { furi_check(thread->is_service == false); // Cannot free a non-joined thread furi_check(thread->state == FuriThreadStateStopped); - furi_check(!thread->is_active); furi_thread_set_name(thread, NULL); furi_thread_set_appid(thread, NULL); @@ -349,8 +347,6 @@ void furi_thread_start(FuriThread* thread) { uint32_t stack_depth = thread->stack_size / sizeof(StackType_t); - thread->is_active = true; - furi_check( xTaskCreateStatic( furi_thread_body, @@ -368,7 +364,7 @@ void furi_thread_cleanup_tcb_event(TaskHandle_t task) { // clear thread local storage vTaskSetThreadLocalStoragePointer(task, 0, NULL); furi_check(thread == (FuriThread*)task); - thread->is_active = false; + furi_thread_set_state(thread, FuriThreadStateStopped); } } @@ -383,8 +379,8 @@ bool furi_thread_join(FuriThread* thread) { // // If your thread exited, but your app stuck here: some other thread uses // all cpu time, which delays kernel from releasing task handle - while(thread->is_active) { - furi_delay_ms(10); + while(thread->state != FuriThreadStateStopped) { + furi_delay_tick(2); } return true; diff --git a/furi/core/thread.h b/furi/core/thread.h index d90ece85d6..c320fdbc10 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -21,7 +21,8 @@ extern "C" { * Many of the FuriThread functions MUST ONLY be called when the thread is STOPPED. */ typedef enum { - FuriThreadStateStopped, /**< Thread is stopped */ + FuriThreadStateStopped, /**< Thread is stopped and is safe to release */ + FuriThreadStateStopping, /**< Thread is stopping */ FuriThreadStateStarting, /**< Thread is starting */ FuriThreadStateRunning, /**< Thread is running */ } FuriThreadState; @@ -80,10 +81,11 @@ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); * * The function to be used as a state callback MUST follow this signature. * + * @param[in] pointer to the FuriThread instance that changed the state * @param[in] state identifier of the state the thread has transitioned to * @param[in,out] context pointer to a user-specified object */ -typedef void (*FuriThreadStateCallback)(FuriThreadState state, void* context); +typedef void (*FuriThreadStateCallback)(FuriThread* thread, FuriThreadState state, void* context); /** * @brief Signal handler callback function pointer type. diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e808f0748d..7e612de862 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,75.0,, +Version,+,76.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7317f7f047..02e184a8a2 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,75.0,, +Version,+,76.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, From a1590fc74a15a2d838ef9231d0070ac8f79ef862 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 8 Oct 2024 18:46:59 -0400 Subject: [PATCH 128/236] Fix memory leak in static encrypted attack --- .../main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 2691999b08..5ff014a1c7 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -158,6 +158,9 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { furi_string_get_cstr(cuid_dict_path), KeysDictModeOpenExisting, sizeof(MfClassicKey)); + + furi_string_free(cuid_dict_path); + if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { keys_dict_free(instance->nfc_dict_context.dict); state = DictAttackStateUserDictInProgress; From 344118c3463ecb0019812f75f2129c7d77ef9762 Mon Sep 17 00:00:00 2001 From: ted-logan Date: Wed, 9 Oct 2024 02:47:19 -0700 Subject: [PATCH 129/236] nfc/clipper: Update BART station codes (#3937) In the NFC Clipper card plugin, update the BART station codes for two newer East Bay stations (Milpitas, and Berryessa/North San Jose), and correct the station code for Castro Valley. These station ids come from visiting the stations and checking what id they presented as in the Clipper card data. --- applications/main/nfc/plugins/supported_cards/clipper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c index 04a2afcda3..35d0c70390 100644 --- a/applications/main/nfc/plugins/supported_cards/clipper.c +++ b/applications/main/nfc/plugins/supported_cards/clipper.c @@ -106,7 +106,7 @@ static const IdMapping bart_zones[] = { {.id = 0x0023, .name = "South Hayward"}, {.id = 0x0024, .name = "Union City"}, {.id = 0x0025, .name = "Fremont"}, - {.id = 0x0026, .name = "Daly City(2)?"}, + {.id = 0x0026, .name = "Castro Valley"}, {.id = 0x0027, .name = "Dublin/Pleasanton"}, {.id = 0x0028, .name = "South San Francisco"}, {.id = 0x0029, .name = "San Bruno"}, @@ -115,6 +115,8 @@ static const IdMapping bart_zones[] = { {.id = 0x002c, .name = "West Dublin/Pleasanton"}, {.id = 0x002d, .name = "OAK Airport"}, {.id = 0x002e, .name = "Warm Springs/South Fremont"}, + {.id = 0x002f, .name = "Milpitas"}, + {.id = 0x0030, .name = "Berryessa/North San Jose"}, }; static const size_t kNumBARTZones = COUNT(bart_zones); From b8438569762b2c715e33e4c2be12d8559e0f8bbc Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 9 Oct 2024 15:51:21 -0400 Subject: [PATCH 130/236] Fix memory leak, use COUNT_OF macro --- .../main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c | 1 + lib/nfc/protocols/mf_classic/mf_classic_poller.c | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 5ff014a1c7..0286cf7a5d 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -151,6 +151,7 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { if(!keys_dict_check_presence(furi_string_get_cstr(cuid_dict_path))) { state = DictAttackStateUserDictInProgress; + furi_string_free(cuid_dict_path); break; } diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index e8f660b161..7417322e92 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -20,8 +20,7 @@ const MfClassicBackdoorKeyPair mf_classic_backdoor_keys[] = { {{{0xa3, 0x16, 0x67, 0xa8, 0xce, 0xc1}}, MfClassicBackdoorAuth1}, // Fudan, Infineon, NXP {{{0x51, 0x8b, 0x33, 0x54, 0xe7, 0x60}}, MfClassicBackdoorAuth2}, // Fudan }; -const size_t mf_classic_backdoor_keys_count = - sizeof(mf_classic_backdoor_keys) / sizeof(mf_classic_backdoor_keys[0]); +const size_t mf_classic_backdoor_keys_count = COUNT_OF(mf_classic_backdoor_keys); const uint16_t valid_sums[] = {0, 32, 56, 64, 80, 96, 104, 112, 120, 128, 136, 144, 152, 160, 176, 192, 200, 224, 256}; From 3976f128dc86be161cb0bb62900e1259aa3a34e1 Mon Sep 17 00:00:00 2001 From: noproto Date: Wed, 9 Oct 2024 16:03:29 -0400 Subject: [PATCH 131/236] Use single call to free FuriString --- .../scenes/nfc_scene_mf_classic_dict_attack.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index 0286cf7a5d..ab1ecfdf02 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -141,17 +141,16 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicDictAttack); if(state == DictAttackStateCUIDDictInProgress) { - do { - size_t cuid_len = 0; - const uint8_t* cuid = nfc_device_get_uid(instance->nfc_device, &cuid_len); - FuriString* cuid_dict_path = furi_string_alloc_printf( - "%s/mf_classic_dict_%08lx.nfc", - EXT_PATH("nfc/assets"), - (uint32_t)bit_lib_bytes_to_num_be(cuid + (cuid_len - 4), 4)); + size_t cuid_len = 0; + const uint8_t* cuid = nfc_device_get_uid(instance->nfc_device, &cuid_len); + FuriString* cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", + EXT_PATH("nfc/assets"), + (uint32_t)bit_lib_bytes_to_num_be(cuid + (cuid_len - 4), 4)); + do { if(!keys_dict_check_presence(furi_string_get_cstr(cuid_dict_path))) { state = DictAttackStateUserDictInProgress; - furi_string_free(cuid_dict_path); break; } @@ -160,8 +159,6 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { KeysDictModeOpenExisting, sizeof(MfClassicKey)); - furi_string_free(cuid_dict_path); - if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { keys_dict_free(instance->nfc_dict_context.dict); state = DictAttackStateUserDictInProgress; @@ -170,6 +167,8 @@ static void nfc_scene_mf_classic_dict_attack_prepare_view(NfcApp* instance) { dict_attack_set_header(instance->dict_attack, "MF Classic CUID Dictionary"); } while(false); + + furi_string_free(cuid_dict_path); } if(state == DictAttackStateUserDictInProgress) { do { From 2c70dd51a1e6754188b76103e5b9a999b1857723 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Oct 2024 23:14:06 +0300 Subject: [PATCH 132/236] upd changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cbbbbeb72..ad4d3d04ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ ## Other changes * OFW PR 3931: Split BadUSB into BadUSB and BadBLE (by @Astrrra) * OFW PR 3933: furi_hal_random: Wait for ready state and no errors before sampling (by @n1kolasM) +* OFW: nfc/clipper: Update BART station codes +* OFW: FuriThread: Improve state callbacks +* OFW: Documentation: update and cleanup * OFW: Improve bit_buffer.h docs * OFW: Prevent idle priority threads from potentially starving the FreeRTOS idle task * OFW: IR universal remote additions From a7c0819034fdbe591d64d16b95ba1f88cff660eb Mon Sep 17 00:00:00 2001 From: noproto Date: Fri, 11 Oct 2024 10:01:30 -0400 Subject: [PATCH 133/236] Refactor enums to avoid redefinition --- applications/main/nfc/nfc_app_i.h | 6 +-- .../scenes/nfc_scene_mf_classic_dict_attack.c | 1 - applications/main/nfc/views/dict_attack.c | 6 +-- applications/main/nfc/views/dict_attack.h | 33 ++------------- .../protocols/mf_classic/mf_classic_poller.h | 41 +++++++++++++++++-- .../mf_classic/mf_classic_poller_i.h | 26 ------------ 6 files changed, 48 insertions(+), 65 deletions(-) diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 9656eae116..14e484622c 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -97,9 +97,9 @@ typedef struct { bool is_key_attack; uint8_t key_attack_current_sector; bool is_card_present; - uint8_t nested_phase; - uint8_t prng_type; - uint8_t backdoor; + MfClassicNestedPhase nested_phase; + MfClassicPrngType prng_type; + MfClassicBackdoor backdoor; uint16_t nested_target_key; uint16_t msb_count; bool enhanced_dict; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c index ab1ecfdf02..526a89a743 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_dict_attack.c @@ -2,7 +2,6 @@ #include #include -#include #define TAG "NfcMfClassicDictAttack" diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 5138dd912a..4efbe6d604 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -316,21 +316,21 @@ void dict_attack_reset_key_attack(DictAttack* instance) { instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true); } -void dict_attack_set_nested_phase(DictAttack* instance, uint8_t nested_phase) { +void dict_attack_set_nested_phase(DictAttack* instance, MfClassicNestedPhase nested_phase) { furi_assert(instance); with_view_model( instance->view, DictAttackViewModel * model, { model->nested_phase = nested_phase; }, true); } -void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type) { +void dict_attack_set_prng_type(DictAttack* instance, MfClassicPrngType prng_type) { furi_assert(instance); with_view_model( instance->view, DictAttackViewModel * model, { model->prng_type = prng_type; }, true); } -void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor) { +void dict_attack_set_backdoor(DictAttack* instance, MfClassicBackdoor backdoor) { furi_assert(instance); with_view_model( diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index 8dc9b9708a..b6c6fdbdc3 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -9,32 +10,6 @@ extern "C" { typedef struct DictAttack DictAttack; -typedef enum { - MfClassicNestedPhaseNone, - MfClassicNestedPhaseAnalyzePRNG, - MfClassicNestedPhaseDictAttack, - MfClassicNestedPhaseDictAttackResume, - MfClassicNestedPhaseCalibrate, - MfClassicNestedPhaseRecalibrate, - MfClassicNestedPhaseCollectNtEnc, - MfClassicNestedPhaseFinished, -} MfClassicNestedPhase; - -typedef enum { - MfClassicPrngTypeUnknown, // Tag not yet tested - MfClassicPrngTypeNoTag, // No tag detected during test - MfClassicPrngTypeWeak, // Weak PRNG, standard Nested - MfClassicPrngTypeHard, // Hard PRNG, Hardnested -} MfClassicPrngType; - -typedef enum { - MfClassicBackdoorUnknown, // Tag not yet tested - MfClassicBackdoorNone, // No observed backdoor - MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor - MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor - MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce) -} MfClassicBackdoor; - typedef enum { DictAttackEventSkipPressed, } DictAttackEvent; @@ -71,11 +46,11 @@ void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector); void dict_attack_reset_key_attack(DictAttack* instance); -void dict_attack_set_nested_phase(DictAttack* instance, uint8_t nested_phase); +void dict_attack_set_nested_phase(DictAttack* instance, MfClassicNestedPhase nested_phase); -void dict_attack_set_prng_type(DictAttack* instance, uint8_t prng_type); +void dict_attack_set_prng_type(DictAttack* instance, MfClassicPrngType prng_type); -void dict_attack_set_backdoor(DictAttack* instance, uint8_t backdoor); +void dict_attack_set_backdoor(DictAttack* instance, MfClassicBackdoor backdoor); void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 5c2550b7e2..7dfd3b6ab1 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -48,6 +48,41 @@ typedef enum { MfClassicPollerModeDictAttackEnhanced, /**< Poller enhanced dictionary attack mode. */ } MfClassicPollerMode; +/** + * @brief MfClassic poller nested attack phase. + */ +typedef enum { + MfClassicNestedPhaseNone, + MfClassicNestedPhaseAnalyzePRNG, + MfClassicNestedPhaseDictAttack, + MfClassicNestedPhaseDictAttackResume, + MfClassicNestedPhaseCalibrate, + MfClassicNestedPhaseRecalibrate, + MfClassicNestedPhaseCollectNtEnc, + MfClassicNestedPhaseFinished, +} MfClassicNestedPhase; + +/** + * @brief MfClassic pseudorandom number generator (PRNG) type. + */ +typedef enum { + MfClassicPrngTypeUnknown, // Tag not yet tested + MfClassicPrngTypeNoTag, // No tag detected during test + MfClassicPrngTypeWeak, // Weak PRNG, standard Nested + MfClassicPrngTypeHard, // Hard PRNG, Hardnested +} MfClassicPrngType; + +/** + * @brief MfClassic authentication backdoor type. + */ +typedef enum { + MfClassicBackdoorUnknown, // Tag not yet tested + MfClassicBackdoorNone, // No observed backdoor + MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor + MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor (sometimes static encrypted) + MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce) +} MfClassicBackdoor; + /** * @brief MfClassic poller request mode event data. * @@ -78,9 +113,9 @@ typedef struct { uint8_t sectors_read; /**< Number of sectors read. */ uint8_t keys_found; /**< Number of keys found. */ uint8_t current_sector; /**< Current sector number. */ - uint8_t nested_phase; /**< Nested attack phase. */ - uint8_t prng_type; /**< PRNG (weak or hard). */ - uint8_t backdoor; /**< Backdoor type. */ + MfClassicNestedPhase nested_phase; /**< Nested attack phase. */ + MfClassicPrngType prng_type; /**< PRNG (weak or hard). */ + MfClassicBackdoor backdoor; /**< Backdoor type. */ uint16_t nested_target_key; /**< Target key for nested attack. */ uint16_t msb_count; /**< Number of unique most significant bytes seen during Hardnested attack. */ diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 7b05ce2401..915c899c3e 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -48,32 +48,6 @@ typedef enum { MfClassicCardStateLost, } MfClassicCardState; -typedef enum { - MfClassicNestedPhaseNone, - MfClassicNestedPhaseAnalyzePRNG, - MfClassicNestedPhaseDictAttack, - MfClassicNestedPhaseDictAttackResume, - MfClassicNestedPhaseCalibrate, - MfClassicNestedPhaseRecalibrate, - MfClassicNestedPhaseCollectNtEnc, - MfClassicNestedPhaseFinished, -} MfClassicNestedPhase; - -typedef enum { - MfClassicPrngTypeUnknown, // Tag not yet tested - MfClassicPrngTypeNoTag, // No tag detected during test - MfClassicPrngTypeWeak, // Weak PRNG, standard Nested - MfClassicPrngTypeHard, // Hard PRNG, Hardnested -} MfClassicPrngType; - -typedef enum { - MfClassicBackdoorUnknown, // Tag not yet tested - MfClassicBackdoorNone, // No observed backdoor - MfClassicBackdoorAuth1, // Tag responds to v1 auth backdoor - MfClassicBackdoorAuth2, // Tag responds to v2 auth backdoor - MfClassicBackdoorAuth3, // Tag responds to v3 auth backdoor (static encrypted nonce) -} MfClassicBackdoor; - typedef struct { MfClassicKey key; MfClassicBackdoor type; From 676eab29f23f169ffbd6af841c89028d960f6dd4 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Fri, 11 Oct 2024 22:49:40 +0300 Subject: [PATCH 134/236] Function renamed accroding to review suggestions --- applications/services/gui/canvas.c | 5 +++-- applications/services/gui/canvas.h | 7 +++++-- targets/f7/api_symbols.csv | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index b310c93188..5c297b681a 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -517,17 +517,18 @@ void canvas_draw_xbm( canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap, IconRotation0); } -void canvas_draw_xbm_mirrored( +void canvas_draw_xbm_custom( Canvas* canvas, int32_t x, int32_t y, size_t width, size_t height, + IconRotation rotation, const uint8_t* bitmap_data) { furi_check(canvas); x += canvas->offset_x; y += canvas->offset_y; - canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, IconRotation180); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, rotation); } void canvas_draw_glyph(Canvas* canvas, int32_t x, int32_t y, uint16_t ch) { diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index be8334904d..1805312fa4 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -287,21 +287,24 @@ void canvas_draw_xbm( size_t height, const uint8_t* bitmap); -/** Draw mirrored XBM bitmap +/** Draw rotated XBM bitmap * * @param canvas Canvas instance * @param x x coordinate * @param y y coordinate * @param[in] width bitmap width * @param[in] height bitmap height + * @param[in] rotation bitmap rotation * @param bitmap pointer to XBM bitmap data */ -void canvas_draw_xbm_mirrored( + +void canvas_draw_xbm_custom( Canvas* canvas, int32_t x, int32_t y, size_t width, size_t height, + IconRotation rotation, const uint8_t* bitmap_data); /** Draw dot at x,y diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index ca74ece8a5..c88e85fbeb 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,75.3,, +Version,+,77.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -823,7 +823,7 @@ Function,+,canvas_draw_str,void,"Canvas*, int32_t, int32_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" -Function,+,canvas_draw_xbm_mirrored,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" +Function,+,canvas_draw_xbm_custom,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* From 9799a07db6c5c25b40a35eaffcde439a35530201 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:15:45 +0300 Subject: [PATCH 135/236] remove serial check ui in gangqi [ci skip] --- lib/subghz/protocols/gangqi.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 56d39c2cd2..0cb76393d8 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -488,23 +488,12 @@ void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output ((instance->generic.data >> 24) & 0xFF) - ((instance->generic.data >> 16) & 0xFF) - ((instance->generic.data >> 8) & 0xFF); - // Get 3 bytes sum - uint16_t sum_3bytes_serial = ((instance->generic.serial >> 16) & 0xFF) + - ((instance->generic.serial >> 8) & 0xFF) + - (instance->generic.serial & 0xFF); - // Returns true if serial is valid - bool serial_is_valid = - (((!(sum_3bytes_serial & 0x3)) && - ((0xB < sum_3bytes_serial) && (sum_3bytes_serial < 0x141))) && - (((instance->generic.serial >> 16) & 0xFF) <= 0x3)); - furi_string_cat_printf( output, "%s %db\r\n" "Key: 0x%X%08lX\r\n" "Serial: 0x%05lX CRC: 0x%02X\r\n" - "Btn: 0x%01X - %s\r\n" - "Serial is %s\r\n", + "Btn: 0x%01X - %s\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint8_t)(instance->generic.data >> 32), @@ -512,6 +501,5 @@ void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output instance->generic.serial, crc, instance->generic.btn, - subghz_protocol_gangqi_get_button_name(instance->generic.btn), - serial_is_valid ? "valid" : "invalid"); + subghz_protocol_gangqi_get_button_name(instance->generic.btn)); } From 5190aace88bba125383281f7f51816d911ad4ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 14 Oct 2024 14:39:09 +0100 Subject: [PATCH 136/236] Furi: A Lot of Fixes (#3942) - BT Service: cleanup code - Dialog: correct release order in file browser - Rpc: rollback to pre #3881 state - Kernel: fix inverted behavior in furi_kernel_is_running - Log: properly take mutex when kernel is not running - Thread: rework tread control block scrubbing procedure, ensure that we don't do stupid things in idle task, add new priority for init task - Timer: add control queue flush method, force flush on stop - Furi: system init task now performs thread scrubbing - BleGlue: add some extra checks - FreeRTOSConfig: fix bunch of issues that were preventing configuration from being properly applied and cleanup --- applications/services/bt/bt_service/bt.c | 20 +++--- .../dialogs/dialogs_module_file_browser.c | 5 +- applications/services/rpc/rpc.c | 69 ++++++++++--------- furi/core/kernel.c | 5 +- furi/core/log.c | 17 +++-- furi/core/thread.c | 44 ++++++++---- furi/core/thread.h | 21 +++--- furi/core/thread_i.h | 7 ++ furi/core/timer.c | 39 +++++++---- furi/core/timer.h | 9 ++- furi/furi.c | 7 ++ furi/furi.h | 2 + targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- targets/f7/ble_glue/ble_glue.c | 11 ++- targets/f7/ble_glue/gap.c | 28 ++++---- targets/f7/furi_hal/furi_hal_spi.c | 2 +- targets/f7/inc/FreeRTOSConfig.h | 29 ++++---- targets/f7/src/main.c | 5 +- 19 files changed, 205 insertions(+), 123 deletions(-) create mode 100644 furi/core/thread_i.h diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index d72e745ee3..2eeeab2868 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -430,13 +430,11 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { *message->profile_instance = NULL; } } - if(message->lock) api_lock_unlock(message->lock); } -static void bt_close_connection(Bt* bt, BtMessage* message) { +static void bt_close_connection(Bt* bt) { bt_close_rpc_connection(bt); furi_hal_bt_stop_advertising(); - if(message->lock) api_lock_unlock(message->lock); } static void bt_apply_settings(Bt* bt) { @@ -484,19 +482,13 @@ static void bt_load_settings(Bt* bt) { } static void bt_handle_get_settings(Bt* bt, BtMessage* message) { - furi_assert(message->lock); *message->data.settings = bt->bt_settings; - api_lock_unlock(message->lock); } static void bt_handle_set_settings(Bt* bt, BtMessage* message) { - furi_assert(message->lock); bt->bt_settings = *message->data.csettings; - bt_apply_settings(bt); bt_settings_save(&bt->bt_settings); - - api_lock_unlock(message->lock); } static void bt_handle_reload_keys_settings(Bt* bt) { @@ -548,6 +540,12 @@ int32_t bt_srv(void* p) { while(1) { furi_check( furi_message_queue_get(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + FURI_LOG_D( + TAG, + "call %d, lock 0x%p, result 0x%p", + message.type, + (void*)message.lock, + (void*)message.result); if(message.type == BtMessageTypeUpdateStatus) { // Update view ports bt_statusbar_update(bt); @@ -571,7 +569,7 @@ int32_t bt_srv(void* p) { } else if(message.type == BtMessageTypeSetProfile) { bt_change_profile(bt, &message); } else if(message.type == BtMessageTypeDisconnect) { - bt_close_connection(bt, &message); + bt_close_connection(bt); } else if(message.type == BtMessageTypeForgetBondedDevices) { bt_keys_storage_delete(bt->keys_storage); } else if(message.type == BtMessageTypeGetSettings) { @@ -581,6 +579,8 @@ int32_t bt_srv(void* p) { } else if(message.type == BtMessageTypeReloadKeysSettings) { bt_handle_reload_keys_settings(bt); } + + if(message.lock) api_lock_unlock(message.lock); } return 0; diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c index 12a7439e60..603c27cff8 100644 --- a/applications/services/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -54,11 +54,14 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow ret = file_browser_context->result; view_holder_set_view(view_holder, NULL); - view_holder_free(view_holder); file_browser_stop(file_browser); + file_browser_free(file_browser); + view_holder_free(view_holder); + api_lock_free(file_browser_context->lock); free(file_browser_context); + furi_record_close(RECORD_GUI); return ret; diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 08a2c3f6de..41d55841ef 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -67,7 +67,7 @@ static RpcSystemCallbacks rpc_systems[] = { struct RpcSession { Rpc* rpc; - FuriThreadId thread_id; + FuriThread* thread; RpcHandlerDict_t handlers; FuriStreamBuffer* stream; @@ -172,7 +172,7 @@ size_t rpc_session_feed( size_t bytes_sent = furi_stream_buffer_send(session->stream, encoded_bytes, size, timeout); - furi_thread_flags_set(session->thread_id, RpcEvtNewData); + furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtNewData); return bytes_sent; } @@ -220,7 +220,7 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { break; } else { /* Save disconnect flag and continue reading buffer */ - furi_thread_flags_set(session->thread_id, RpcEvtDisconnect); + furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); } } else if(flags & RpcEvtNewData) { // Just wake thread up @@ -347,32 +347,37 @@ static int32_t rpc_session_worker(void* context) { return 0; } -static void rpc_session_thread_release_callback( - FuriThread* thread, - FuriThreadState thread_state, - void* context) { - if(thread_state == FuriThreadStateStopped) { - RpcSession* session = (RpcSession*)context; +static void rpc_session_thread_pending_callback(void* context, uint32_t arg) { + UNUSED(arg); + RpcSession* session = (RpcSession*)context; - for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { - if(rpc_systems[i].free) { - (rpc_systems[i].free)(session->system_contexts[i]); - } - } - free(session->system_contexts); - free(session->decoded_message); - RpcHandlerDict_clear(session->handlers); - furi_stream_buffer_free(session->stream); - - furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); - if(session->terminated_callback) { - session->terminated_callback(session->context); + for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { + if(rpc_systems[i].free) { + (rpc_systems[i].free)(session->system_contexts[i]); } - furi_mutex_release(session->callbacks_mutex); + } + free(session->system_contexts); + free(session->decoded_message); + RpcHandlerDict_clear(session->handlers); + furi_stream_buffer_free(session->stream); + + furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); + if(session->terminated_callback) { + session->terminated_callback(session->context); + } + furi_mutex_release(session->callbacks_mutex); + + furi_mutex_free(session->callbacks_mutex); + furi_thread_join(session->thread); + furi_thread_free(session->thread); + free(session); +} - furi_mutex_free(session->callbacks_mutex); - furi_thread_free(thread); - free(session); +static void + rpc_session_thread_state_callback(FuriThread* thread, FuriThreadState state, void* context) { + UNUSED(thread); + if(state == FuriThreadStateStopped) { + furi_timer_pending_callback(rpc_session_thread_pending_callback, context, 0); } } @@ -404,14 +409,12 @@ RpcSession* rpc_session_open(Rpc* rpc, RpcOwner owner) { }; rpc_add_handler(session, PB_Main_stop_session_tag, &rpc_handler); - FuriThread* thread = - furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); - session->thread_id = furi_thread_get_id(thread); + session->thread = furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); - furi_thread_set_state_context(thread, session); - furi_thread_set_state_callback(thread, rpc_session_thread_release_callback); + furi_thread_set_state_context(session->thread, session); + furi_thread_set_state_callback(session->thread, rpc_session_thread_state_callback); - furi_thread_start(thread); + furi_thread_start(session->thread); return session; } @@ -423,7 +426,7 @@ void rpc_session_close(RpcSession* session) { rpc_session_set_send_bytes_callback(session, NULL); rpc_session_set_close_callback(session, NULL); rpc_session_set_buffer_is_empty_callback(session, NULL); - furi_thread_flags_set(session->thread_id, RpcEvtDisconnect); + furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); } void rpc_on_system_start(void* p) { diff --git a/furi/core/kernel.c b/furi/core/kernel.c index f3f84e692e..34c562bb34 100644 --- a/furi/core/kernel.c +++ b/furi/core/kernel.c @@ -33,7 +33,7 @@ bool furi_kernel_is_irq_or_masked(void) { } bool furi_kernel_is_running(void) { - return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING; + return xTaskGetSchedulerState() == taskSCHEDULER_RUNNING; } int32_t furi_kernel_lock(void) { @@ -129,6 +129,8 @@ uint32_t furi_kernel_get_tick_frequency(void) { void furi_delay_tick(uint32_t ticks) { furi_check(!furi_kernel_is_irq_or_masked()); + furi_check(furi_thread_get_current_id() != xTaskGetIdleTaskHandle()); + if(ticks == 0U) { taskYIELD(); } else { @@ -138,6 +140,7 @@ void furi_delay_tick(uint32_t ticks) { FuriStatus furi_delay_until_tick(uint32_t tick) { furi_check(!furi_kernel_is_irq_or_masked()); + furi_check(furi_thread_get_current_id() != xTaskGetIdleTaskHandle()); TickType_t tcnt, delay; FuriStatus stat; diff --git a/furi/core/log.c b/furi/core/log.c index f8110b46ac..fb0c967113 100644 --- a/furi/core/log.c +++ b/furi/core/log.c @@ -108,10 +108,17 @@ void furi_log_puts(const char* data) { } void furi_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...) { - if(level <= furi_log.log_level && - furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk) { - FuriString* string; - string = furi_string_alloc(); + do { + if(level > furi_log.log_level) { + break; + } + + if(furi_mutex_acquire(furi_log.mutex, furi_kernel_is_running() ? FuriWaitForever : 0) != + FuriStatusOk) { + break; + } + + FuriString* string = furi_string_alloc(); const char* color = _FURI_LOG_CLR_RESET; const char* log_letter = " "; @@ -157,7 +164,7 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form furi_log_puts("\r\n"); furi_mutex_release(furi_log.mutex); - } + } while(0); } void furi_log_print_raw_format(FuriLogLevel level, const char* format, ...) { diff --git a/furi/core/thread.c b/furi/core/thread.c index 3990dd63d2..fd576ea72b 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -1,6 +1,7 @@ -#include "thread.h" +#include "thread_i.h" #include "thread_list_i.h" #include "kernel.h" +#include "message_queue.h" #include "memmgr.h" #include "memmgr_heap.h" #include "check.h" @@ -67,6 +68,8 @@ static_assert(offsetof(FuriThread, container) == 0); // Our idle priority should be equal to the one from FreeRTOS static_assert(FuriThreadPriorityIdle == tskIDLE_PRIORITY); +static FuriMessageQueue* furi_thread_scrub_message_queue = NULL; + static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); static int32_t __furi_thread_stdout_flush(FuriThread* thread); @@ -125,7 +128,9 @@ static void furi_thread_body(void* context) { furi_thread_set_state(thread, FuriThreadStateStopping); - vTaskDelete(NULL); + furi_message_queue_put(furi_thread_scrub_message_queue, &thread, FuriWaitForever); + + vTaskSuspend(NULL); furi_thread_catch(); } @@ -159,6 +164,31 @@ static void furi_thread_init_common(FuriThread* thread) { } } +void furi_thread_init(void) { + furi_thread_scrub_message_queue = furi_message_queue_alloc(8, sizeof(FuriThread*)); +} + +void furi_thread_scrub(void) { + FuriThread* thread_to_scrub = NULL; + while(true) { + furi_check( + furi_message_queue_get( + furi_thread_scrub_message_queue, &thread_to_scrub, FuriWaitForever) == + FuriStatusOk); + + TaskHandle_t task = (TaskHandle_t)thread_to_scrub; + + // Delete task: FreeRTOS will remove task from all lists where it may be + vTaskDelete(task); + // Sanity check: ensure that local storage is ours and clear it + furi_check(pvTaskGetThreadLocalStoragePointer(task, 0) == thread_to_scrub); + vTaskSetThreadLocalStoragePointer(task, 0, NULL); + + // Deliver thread stopped callback + furi_thread_set_state(thread_to_scrub, FuriThreadStateStopped); + } +} + FuriThread* furi_thread_alloc(void) { FuriThread* thread = malloc(sizeof(FuriThread)); @@ -358,16 +388,6 @@ void furi_thread_start(FuriThread* thread) { &thread->container) == (TaskHandle_t)thread); } -void furi_thread_cleanup_tcb_event(TaskHandle_t task) { - FuriThread* thread = pvTaskGetThreadLocalStoragePointer(task, 0); - if(thread) { - // clear thread local storage - vTaskSetThreadLocalStoragePointer(task, 0, NULL); - furi_check(thread == (FuriThread*)task); - furi_thread_set_state(thread, FuriThreadStateStopped); - } -} - bool furi_thread_join(FuriThread* thread) { furi_check(thread); // Cannot join a service thread diff --git a/furi/core/thread.h b/furi/core/thread.h index c320fdbc10..ed7aa4553b 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -21,10 +21,10 @@ extern "C" { * Many of the FuriThread functions MUST ONLY be called when the thread is STOPPED. */ typedef enum { - FuriThreadStateStopped, /**< Thread is stopped and is safe to release */ - FuriThreadStateStopping, /**< Thread is stopping */ - FuriThreadStateStarting, /**< Thread is starting */ - FuriThreadStateRunning, /**< Thread is running */ + FuriThreadStateStopped, /**< Thread is stopped and is safe to release. Event delivered from system init thread(TCB cleanup routine). It is safe to release thread instance. */ + FuriThreadStateStopping, /**< Thread is stopping. Event delivered from child thread. */ + FuriThreadStateStarting, /**< Thread is starting. Event delivered from parent(self) thread. */ + FuriThreadStateRunning, /**< Thread is running. Event delivered from child thread. */ } FuriThreadState; /** @@ -32,6 +32,7 @@ typedef enum { */ typedef enum { FuriThreadPriorityIdle = 0, /**< Idle priority */ + FuriThreadPriorityInit = 4, /**< Init System Thread Priority */ FuriThreadPriorityLowest = 14, /**< Lowest */ FuriThreadPriorityLow = 15, /**< Low */ FuriThreadPriorityNormal = 16, /**< Normal, system default */ @@ -77,13 +78,15 @@ typedef int32_t (*FuriThreadCallback)(void* context); typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); /** - * @brief State change callback function pointer type. + * @brief State change callback function pointer type. * - * The function to be used as a state callback MUST follow this signature. + * The function to be used as a state callback MUST follow this + * signature. * - * @param[in] pointer to the FuriThread instance that changed the state - * @param[in] state identifier of the state the thread has transitioned to - * @param[in,out] context pointer to a user-specified object + * @param[in] thread to the FuriThread instance that changed the state + * @param[in] state identifier of the state the thread has transitioned + * to + * @param[in,out] context pointer to a user-specified object */ typedef void (*FuriThreadStateCallback)(FuriThread* thread, FuriThreadState state, void* context); diff --git a/furi/core/thread_i.h b/furi/core/thread_i.h new file mode 100644 index 0000000000..c6b12a7802 --- /dev/null +++ b/furi/core/thread_i.h @@ -0,0 +1,7 @@ +#pragma once + +#include "thread.h" + +void furi_thread_init(void); + +void furi_thread_scrub(void); diff --git a/furi/core/timer.c b/furi/core/timer.c index 01adbeb89e..ddd82e3319 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -17,12 +17,24 @@ static_assert(offsetof(FuriTimer, container) == 0); #define TIMER_DELETED_EVENT (1U << 0) -static void TimerCallback(TimerHandle_t hTimer) { +static void furi_timer_callback(TimerHandle_t hTimer) { FuriTimer* instance = pvTimerGetTimerID(hTimer); furi_check(instance); instance->cb_func(instance->cb_context); } +static void furi_timer_flush_epilogue(void* context, uint32_t arg) { + furi_assert(context); + UNUSED(arg); + + EventGroupHandle_t hEvent = context; + + // See https://github.com/FreeRTOS/FreeRTOS-Kernel/issues/1142 + vTaskSuspendAll(); + xEventGroupSetBits(hEvent, TIMER_DELETED_EVENT); + (void)xTaskResumeAll(); +} + FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* context) { furi_check((furi_kernel_is_irq_or_masked() == 0U) && (func != NULL)); @@ -33,23 +45,13 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co const UBaseType_t reload = (type == FuriTimerTypeOnce ? pdFALSE : pdTRUE); const TimerHandle_t hTimer = xTimerCreateStatic( - NULL, portMAX_DELAY, reload, instance, TimerCallback, &instance->container); + NULL, portMAX_DELAY, reload, instance, furi_timer_callback, &instance->container); furi_check(hTimer == (TimerHandle_t)instance); return instance; } -static void furi_timer_epilogue(void* context, uint32_t arg) { - furi_assert(context); - UNUSED(arg); - - EventGroupHandle_t hEvent = context; - vTaskSuspendAll(); - xEventGroupSetBits(hEvent, TIMER_DELETED_EVENT); - (void)xTaskResumeAll(); -} - void furi_timer_free(FuriTimer* instance) { furi_check(!furi_kernel_is_irq_or_masked()); furi_check(instance); @@ -57,16 +59,21 @@ void furi_timer_free(FuriTimer* instance) { TimerHandle_t hTimer = (TimerHandle_t)instance; furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); + furi_timer_flush(); + + free(instance); +} + +void furi_timer_flush(void) { StaticEventGroup_t event_container = {}; EventGroupHandle_t hEvent = xEventGroupCreateStatic(&event_container); - furi_check(xTimerPendFunctionCall(furi_timer_epilogue, hEvent, 0, portMAX_DELAY) == pdPASS); + furi_check( + xTimerPendFunctionCall(furi_timer_flush_epilogue, hEvent, 0, portMAX_DELAY) == pdPASS); furi_check( xEventGroupWaitBits(hEvent, TIMER_DELETED_EVENT, pdFALSE, pdTRUE, portMAX_DELAY) == TIMER_DELETED_EVENT); vEventGroupDelete(hEvent); - - free(instance); } FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { @@ -112,6 +119,8 @@ FuriStatus furi_timer_stop(FuriTimer* instance) { furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); + furi_timer_flush(); + return FuriStatusOk; } diff --git a/furi/core/timer.h b/furi/core/timer.h index 00056db18e..9d8df3dc62 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -35,6 +35,12 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co */ void furi_timer_free(FuriTimer* instance); +/** Flush timer task control message queue + * + * Ensures that all commands before this point was processed. + */ +void furi_timer_flush(void); + /** Start timer * * @warning This is asynchronous call, real operation will happen as soon as @@ -61,8 +67,7 @@ FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks); /** Stop timer * - * @warning This is asynchronous call, real operation will happen as soon as - * timer service process this request. + * @warning This is synchronous call that will be blocked till timer queue processed. * * @param instance The pointer to FuriTimer instance * diff --git a/furi/furi.c b/furi/furi.c index f4e64ee099..bc7452f130 100644 --- a/furi/furi.c +++ b/furi/furi.c @@ -1,5 +1,7 @@ #include "furi.h" +#include "core/thread_i.h" + #include #include @@ -7,6 +9,7 @@ void furi_init(void) { furi_check(!furi_kernel_is_irq_or_masked()); furi_check(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED); + furi_thread_init(); furi_log_init(); furi_record_init(); } @@ -18,3 +21,7 @@ void furi_run(void) { /* Start the kernel scheduler */ vTaskStartScheduler(); } + +void furi_background(void) { + furi_thread_scrub(); +} diff --git a/furi/furi.h b/furi/furi.h index d75debe987..6ddf285775 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -35,6 +35,8 @@ void furi_init(void); void furi_run(void); +void furi_background(void); + #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7e612de862..2b04e3e403 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,76.0,, +Version,+,77.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1102,6 +1102,7 @@ Function,-,ftello,off_t,FILE* Function,-,ftrylockfile,int,FILE* Function,-,funlockfile,void,FILE* Function,-,funopen,FILE*,"const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,-,furi_background,void, Function,+,furi_delay_ms,void,uint32_t Function,+,furi_delay_tick,void,uint32_t Function,+,furi_delay_until_tick,FuriStatus,uint32_t @@ -1672,6 +1673,7 @@ Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" +Function,+,furi_timer_flush,void, Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 02e184a8a2..71d9213d85 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,76.0,, +Version,+,77.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1207,6 +1207,7 @@ Function,-,ftello,off_t,FILE* Function,-,ftrylockfile,int,FILE* Function,-,funlockfile,void,FILE* Function,-,funopen,FILE*,"const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,-,furi_background,void, Function,+,furi_delay_ms,void,uint32_t Function,+,furi_delay_tick,void,uint32_t Function,+,furi_delay_until_tick,FuriStatus,uint32_t @@ -1886,6 +1887,7 @@ Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" +Function,+,furi_timer_flush,void, Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* diff --git a/targets/f7/ble_glue/ble_glue.c b/targets/f7/ble_glue/ble_glue.c index 73bb41badc..fe101b2c93 100644 --- a/targets/f7/ble_glue/ble_glue.c +++ b/targets/f7/ble_glue/ble_glue.c @@ -87,6 +87,8 @@ void ble_glue_init(void) { TL_Init(); ble_glue->shci_mtx = furi_mutex_alloc(FuriMutexTypeNormal); + // Take mutex, SHCI will release it in most unusual way later + furi_check(furi_mutex_acquire(ble_glue->shci_mtx, FuriWaitForever) == FuriStatusOk); // FreeRTOS system task creation ble_event_thread_start(); @@ -248,7 +250,9 @@ void ble_glue_stop(void) { ble_event_thread_stop(); // Free resources furi_mutex_free(ble_glue->shci_mtx); + ble_glue->shci_mtx = NULL; furi_timer_free(ble_glue->hardfault_check_timer); + ble_glue->hardfault_check_timer = NULL; ble_glue_clear_shared_memory(); free(ble_glue); @@ -309,10 +313,13 @@ BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) { static void ble_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { switch(status) { case SHCI_TL_CmdBusy: - furi_mutex_acquire(ble_glue->shci_mtx, FuriWaitForever); + furi_check( + furi_mutex_acquire( + ble_glue->shci_mtx, furi_kernel_is_running() ? FuriWaitForever : 0) == + FuriStatusOk); break; case SHCI_TL_CmdAvailable: - furi_mutex_release(ble_glue->shci_mtx); + furi_check(furi_mutex_release(ble_glue->shci_mtx) == FuriStatusOk); break; default: break; diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 9cd33456b4..732440ccf9 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -129,7 +129,7 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; furi_check(gap); - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); switch(event_pckt->evt) { case HCI_DISCONNECTION_COMPLETE_EVT_CODE: { @@ -304,7 +304,7 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { break; } - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); return BleEventFlowEnable; } @@ -490,7 +490,7 @@ static void gap_advertise_stop(void) { } void gap_start_advertising(void) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); if(gap->state == GapStateIdle) { gap->state = GapStateStartingAdv; FURI_LOG_I(TAG, "Start advertising"); @@ -498,18 +498,18 @@ void gap_start_advertising(void) { GapCommand command = GapCommandAdvFast; furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); } - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); } void gap_stop_advertising(void) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); if(gap->state > GapStateIdle) { FURI_LOG_I(TAG, "Stop advertising"); gap->enable_adv = false; GapCommand command = GapCommandAdvStop; furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); } - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); } static void gap_advetise_timer_callback(void* context) { @@ -566,9 +566,9 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { GapState gap_get_state(void) { GapState state; if(gap) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); state = gap->state; - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); } else { state = GapStateUninitialized; } @@ -577,17 +577,21 @@ GapState gap_get_state(void) { void gap_thread_stop(void) { if(gap) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); gap->enable_adv = false; GapCommand command = GapCommandKillThread; furi_message_queue_put(gap->command_queue, &command, FuriWaitForever); - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); furi_thread_join(gap->thread); furi_thread_free(gap->thread); + gap->thread = NULL; // Free resources furi_mutex_free(gap->state_mutex); + gap->state_mutex = NULL; furi_message_queue_free(gap->command_queue); + gap->command_queue = NULL; furi_timer_free(gap->advertise_timer); + gap->advertise_timer = NULL; ble_event_dispatcher_reset(); free(gap); @@ -604,7 +608,7 @@ static int32_t gap_app(void* context) { FURI_LOG_E(TAG, "Message queue get error: %d", status); continue; } - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); if(command == GapCommandKillThread) { break; } @@ -615,7 +619,7 @@ static int32_t gap_app(void* context) { } else if(command == GapCommandAdvStop) { gap_advertise_stop(); } - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); } return 0; diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index 49bcd48a1e..2a7cb7c25f 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -202,7 +202,7 @@ bool furi_hal_spi_bus_trx_dma( furi_check(size > 0); // If scheduler is not running, use blocking mode - if(furi_kernel_is_running()) { + if(!furi_kernel_is_running()) { return furi_hal_spi_bus_trx(handle, tx_buffer, rx_buffer, size, timeout_ms); } diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 82cda2c6da..357019ea23 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -84,6 +84,7 @@ to exclude the API function. */ #define INCLUDE_xTaskGetCurrentTaskHandle 1 #define INCLUDE_xTaskGetSchedulerState 1 #define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskGetIdleTaskHandle 1 /* Workaround for various notification issues: * - First one used by system primitives @@ -129,25 +130,11 @@ See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY \ (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) -/* Normal assert() semantics without relying on the provision of an assert.h -header file. */ -#ifdef DEBUG -#include -#define configASSERT(x) \ - if((x) == 0) { \ - furi_crash("FreeRTOS Assert"); \ - } -#endif - /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS standard names. */ #define vPortSVCHandler SVC_Handler #define xPortPendSVHandler PendSV_Handler -#define USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION 1 -#define configOVERRIDE_DEFAULT_TICK_CONFIGURATION \ - 1 /* required only for Keil but does not hurt otherwise */ - #define traceTASK_SWITCHED_IN() \ extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack); \ @@ -157,6 +144,14 @@ standard names. */ // referencing `FreeRTOS_errno' here vvvvv because FreeRTOS calls our hook _before_ copying the value into the TCB, hence a manual write to the TCB would get overwritten #define traceTASK_SWITCHED_OUT() FreeRTOS_errno = errno -#define portCLEAN_UP_TCB(pxTCB) \ - extern void furi_thread_cleanup_tcb_event(TaskHandle_t task); \ - furi_thread_cleanup_tcb_event(pxTCB) +/* Normal assert() semantics without relying on the provision of an assert.h +header file. */ +#ifdef DEBUG +#define configASSERT(x) \ + if((x) == 0) { \ + furi_crash("FreeRTOS Assert"); \ + } +#endif + +// Must be last line of config because of recursion +#include diff --git a/targets/f7/src/main.c b/targets/f7/src/main.c index 77961f0eff..21775f19a2 100644 --- a/targets/f7/src/main.c +++ b/targets/f7/src/main.c @@ -15,6 +15,8 @@ int32_t init_task(void* context) { // Init flipper flipper_init(); + furi_background(); + return 0; } @@ -25,7 +27,8 @@ int main(void) { // Flipper critical FURI HAL furi_hal_init_early(); - FuriThread* main_thread = furi_thread_alloc_ex("Init", 4096, init_task, NULL); + FuriThread* main_thread = furi_thread_alloc_ex("InitSrv", 1024, init_task, NULL); + furi_thread_set_priority(main_thread, FuriThreadPriorityInit); #ifdef FURI_RAM_EXEC // Prevent entering sleep mode when executed from RAM From 421bd3e1f9484ce1100fcba6a50f12cc8fbe74fc Mon Sep 17 00:00:00 2001 From: Ruslan Nadyrshin <110516632+rnadyrshin@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:47:12 +0400 Subject: [PATCH 137/236] Wi-Fi Devboard documentation rework (#3944) * New step-by-step documentation structure for Wi-Fi Devboard * Added a description of working under Windows * Added a description of switching Devboard operation mode (Black Magic, DAPLink) * The images for the documentation are uploaded to the CDN * The text in the sidebar, near the dolphin logo, changed from blue to black/white Co-authored-by: knrn64 <25254561+knrn64@users.noreply.github.com> Co-authored-by: Aleksandr Kutuzov --- .../devboard/Debugging via the Devboard.md | 88 ++++++++++ .../devboard/Devboard debug modes.md | 33 ++++ .../Firmware update on Developer Board.md | 114 ++++++------ .../Get started with the Dev Board.md | 162 ++++-------------- .../Reading logs via the Dev Board.md | 18 +- .../USB connection to the Devboard.md | 22 +++ .../Wi-Fi connection to the Devboard.md | 60 +++++++ documentation/doxygen/dev_board.dox | 33 +++- documentation/doxygen/header.html | 2 +- documentation/fbt.md | 2 +- 10 files changed, 328 insertions(+), 206 deletions(-) create mode 100644 documentation/devboard/Debugging via the Devboard.md create mode 100644 documentation/devboard/Devboard debug modes.md create mode 100644 documentation/devboard/USB connection to the Devboard.md create mode 100644 documentation/devboard/Wi-Fi connection to the Devboard.md diff --git a/documentation/devboard/Debugging via the Devboard.md b/documentation/devboard/Debugging via the Devboard.md new file mode 100644 index 0000000000..49888cdbcb --- /dev/null +++ b/documentation/devboard/Debugging via the Devboard.md @@ -0,0 +1,88 @@ +# Debugging via the Devboard {#dev_board_debugging_guide} + +On this page, you'll learn about how debugging via the Wi-Fi Developer Board works. To illustrate this process, we'll start a debug session for Flipper Zero's firmware in VS Code using the native Flipper Build Tool. + +*** + +## Overview + +The Developer Board acts as the debug probe, which provides a bridge between the IDE (integrated development environment) with a debugger running on a host computer and the target microcontroller (in your Flipper Zero). The user controls the debugging process on the computer connected to the Developer Board via [Wi-Fi](#dev_board_wifi_connection) or [USB cable](#dev_board_usb_connection). + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_hardware_CDN.jpg width=700 + +Data exchange between the Wi-Fi Developer Board and your Flipper Zero is conducted via the Serial Wire Debug interface. The following GPIO pins serve this purpose: + +- **Pin 10:** Serial Wire Clock (SWCLK) + +- **Pin 12:** Serial Wire Debug Data I/O (SWDIO) + +To learn more about Flipper Zero pinout, visit [GPIO & modules in Flipper Docs](https://docs.flipper.net/gpio-and-modules). + +*** + +## Prerequisites + +### Step 1. Installing Git + +You'll need Git installed on your computer to clone the firmware repository. If you don't have Git, install it following the [official installation guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). + +### Step 2. Building the firmware + +Before starting debugging, you need to clone and build Flipper Zero firmware: + +1. Open the **Terminal** (on Linux & macOS) or **PowerShell** (on Windows) in the directory where you want to store the firmware source code. + +2. Clone the firmware repository: + + ``` + git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git + cd flipperzero-firmware + ``` + +3. Run the **Flipper Build Tool (FBT)** to build the firmware: + + ``` + ./fbt + ``` + +*** + +## Debugging the firmware + +From the **flipperzero-firmware** directory that you cloned earlier, run the following command: + +``` +./fbt flash +``` + +This will upload the firmware you've just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware. We recommend using **VS Code** with the recommended extensions (as described below), and we have pre-made configurations for it. + +To debug in **VS Code**, do the following: + +1. In VS Code, open the **flipperzero-firmware** directory. + +2. You should see a notification about recommended extensions. Install them. + + If there were no notifications, open the **Extensions** tab, enter `@recommended` in the search bar, and install the workspace recommendations. + +3. Run the `./fbt vscode_dist` command. This will generate the VS Code configuration files needed for debugging. + +4. In VS Code, open the **Run and Debug** tab and select a debugger from the dropdown menu: + + - **Attach FW (blackmagic):** Can be used via **Wi-Fi** or **USB** + - **Attach FW (DAP):** Can be used via **USB** only + + Note that when debugging via USB, you need to make sure the selected debugger matches the debug mode on your Devboard. To check the debug mode on your Devboard, access the Devboard's web interface as described [here](#dev_board_wifi_connection) and check the **USB mode** field. If you want to use a different debug mode, enable this mode by following the steps in [Devboard debug modes](#dev_board_debug_modes). + +5. If needed, flash your Flipper Zero with the `./fbt flash` command, then click the ▷ **Start Debugging** button in the debug sidebar to start the debugging session. + +6. Note that starting a debug session halts the execution of the firmware, so you'll need to click the I▷ **Continue** button on the toolbar at the top of your VS Code window to continue execution. + +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_VS_Code.jpg width=900 + +> [!note] +> If you want to use a different debug mode on your Developer Board, visit [Devboard debug modes](#dev_board_debug_modes). +> +> If you want to read logs via the Developer Board, see [Reading logs via the Devboard](#dev_board_reading_logs). +> +> To learn about debugging in VS Code, see [VS Code official guide](https://code.visualstudio.com/docs/editor/debugging). diff --git a/documentation/devboard/Devboard debug modes.md b/documentation/devboard/Devboard debug modes.md new file mode 100644 index 0000000000..a6550cbbbe --- /dev/null +++ b/documentation/devboard/Devboard debug modes.md @@ -0,0 +1,33 @@ +# Devboard debug modes {#dev_board_debug_modes} + +The Wi-Fi Devboard for Flipper Zero supports **Black Magic** and **DAPLink** debug modes, and you can switch between them depending on your needs. Note that available modes depend on connection: + +- **Wi-Fi:** Only **Black Magic** mode is available. +- **USB:** Switch between **Black Magic** (default) and **DAPLink**. Learn more about switching debug modes for USB connection below. + +> [!note] +> Black Magic mode doesn't support RTOS threads, but you can still perform other debugging operations. + +*** + +## Switching debug modes for USB connection + +Switching debug modes for working via USB has to be done wirelessly (yes, you read that correctly). Additionally, depending on how the Devboard wireless connection is configured, you may need to follow different steps for **Wi-Fi access point mode** or **Wi-Fi client mode**: + +1. If the Devboard isn't connected to your Flipper Zero, turn off your Flipper Zero and connect the Developer Board, then turn the device back on. + +2. Access the Devboard's web interface: + + - [Wi-Fi access point mode](#wifi-access-point) + + - [Wi-Fi client mode](#wifi-client-mode) + +3. In the **WiFi** tab, click the **USB mode** option and select **BlackMagicProbe** or **DapLink**. + +4. Click **SAVE**, then click **REBOOT** to apply the changes. + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_switching_modes_CDN.jpg width=700 + +> [!note] +> After switching debug modes on your Devboard, remember to select the same debugger in **VS Code** in the **Run and Debug** tab, and click the ▷ **Start Debugging** button. + diff --git a/documentation/devboard/Firmware update on Developer Board.md b/documentation/devboard/Firmware update on Developer Board.md index f6a81d97b6..0b88e52be5 100644 --- a/documentation/devboard/Firmware update on Developer Board.md +++ b/documentation/devboard/Firmware update on Developer Board.md @@ -1,122 +1,112 @@ # Firmware update on Developer Board {#dev_board_fw_update} -It's important to regularly update your Developer Board to ensure that you have access to the latest features and bug fixes. This tutorial will guide you through the necessary steps to update the firmware of your Developer Board. +It's important to regularly update your Developer Board to ensure that you have access to the latest features and bug fixes. This page will guide you through the necessary steps to update the firmware of your Developer Board. -This tutorial assumes that you're familiar with the basics of the command line. If you’re not, please refer to the [Windows](https://www.digitalcitizen.life/command-prompt-how-use-basic-commands/) or [MacOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials. +> [!note] +> This guide assumes that you're familiar with the basics of the command line. If you're new to it, we recommend checking out these [Windows](https://learn.microsoft.com/en-us/powershell/scripting/learn/ps101/01-getting-started?view=powershell-7.4) or [macOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials. *** -## Installing the micro Flipper Build Tool +## Step 1. Install the micro Flipper Build Tool -Micro Flipper Build Tool (uFBT) is a cross-platform tool that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, and creating VS Code development configurations. + is a cross-platform tool developed and supported by our team that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, creating VS Code development configurations, and flashing firmware to the Wi-Fi Developer Board. -Install uFBT on your computer by running the following command in the Terminal: +**On Linux & macOS:** -**For Linux & macOS:** +Run the following command in the Terminal: -```text +``` python3 -m pip install --upgrade ufbt ``` -**For Windows:** +**On Windows:** -```text -py -m pip install --upgrade ufbt -``` +1. Download the latest version of Python on +2. Run the following command in the PowerShell -If you want to learn more about uFBT, visit [the project's page](https://pypi.org/project/ufbt/). + ``` + py -m pip install --upgrade ufbt + ``` *** -## Connecting the Developer Board to your computer - -1. List all of the serial devices on your computer. - - **Windows** - - On Windows, go to Device Manager and expand the Ports (COM & LPT) section. +## Step 2. Connect the Devboard to PC - **macOS** +To update the firmware, you need to switch your Developer Board to Bootloader mode, connect to a PC via a USB cable, and make sure that the PC detects the Developer Board: - On macOS, you can run the following command in the Terminal: - - ```text - ls /dev/cu.* - ``` +1. List all of the serial devices on your computer. - **Linux** + - **macOS:** Run the `ls /dev/cu.*` command in the Terminal. - On Linux, you can run the following command in the Terminal: + - **Linux:** Run the `ls /dev/tty*` command in the Terminal. - ```text - ls /dev/tty* - ``` - - View the devices in the list. + - **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section. 2. Connect the Developer Board to your computer using a USB-C cable. -![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/Aq7gfMI-m_5H6sGGjwb4I_monosnap-miro-2023-07-19-19-47-39.jpg) - -3. Switch your Developer Board to Bootloader mode: - 3.1. Press and hold the **BOOT** button. + \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_update_wired_connection.jpg width=700 - 3.2. Press the **RESET** button while holding the **BOOT** button. - - 3.3. Release the **BOOT** button.\ -![You can easily switch the Dev Board to Bootloader mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KynP9iT6sJ3mXLaLyI82__image.png) +3. Switch your Developer Board to Bootloader mode: -4. Repeat Step 1 and view the name of your Developer Board that appeared in the list. + 1. Press and hold the **BOOT** button. + 2. Press the **RESET** button while holding the **BOOT** button. + 3. Release the **BOOT** button. - For example, on macOS: + \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_to_bootloader.png width=700 - ```text - /dev/cu.usbmodem01 - ``` +4. Repeat **Step 1** and view the name of your Developer Board that appeared in the list. *** -## Flashing the firmware +## Step 3. Flash the firmware -To flash the firmware onto your Developer Board, run the following command in the terminal: +**On Linux & macOS:** -```text +``` python3 -m ufbt devboard_flash ``` +**On Windows:** Run the following command in the PowerShell: + +``` +py -m ufbt devboard_flash +``` + You should see the following message: `WiFi board flashed successfully`. -## If flashing failed +### If flashing failed -If you get an error message during the flashing process, such as this: +Occasionally, you might get an error message during the flashing process, such as: -```text +``` A fatal error occurred: Serial data stream stopped: Possible serial noise or corruption. ``` -Or this: +*or* -```text +``` FileNotFoundError: [Errno 2] No such file or directory: '/dev/cu.usbmodem01' ``` -Try doing the following: +To fix it, try doing the following: -* Disconnect the Developer Board from your computer, then reconnect it. +- Disconnect the Developer Board from your computer, then reconnect it. After that, switch your Developer Board to Bootloader mode once again, as described in -* Use a different USB port on your computer. +- Use a different USB port on your computer. -* Use a different USB-C cable. +- Use a different USB-C cable. *** -## Finishing the installation - -After flashing the firmware: +## Step 4. Finish the installation 1. Reboot the Developer Board by pressing the **RESET** button. -![Reset the Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/rcQeKARgrVwa51tLoo-qY_monosnap-miro-2023-07-20-18-29-33.jpg) + + \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_after_flashing.jpg width=700 2. Disconnect and reconnect the USB-C cable. -The Developer Board should appear as a serial device on your computer. Now, you can use it with the Black Magic Debug client of your choice. + You've successfully updated the firmware of your Developer Board! + +If you followed the **Get started with the Devboard** guide, you're ready for the next step: [Step 3. Plug the Devboard into Flipper Zero](#dev_board_get_started_step-3). + diff --git a/documentation/devboard/Get started with the Dev Board.md b/documentation/devboard/Get started with the Dev Board.md index 04fa9d3589..141bf64118 100644 --- a/documentation/devboard/Get started with the Dev Board.md +++ b/documentation/devboard/Get started with the Dev Board.md @@ -1,178 +1,80 @@ # Get started with the Dev Board {#dev_board_get_started} -The Wi-Fi Developer Board serves as a tool to debug the Flipper Zero firmware. To debug the firmware, the initial step involves compiling the firmware from its source code. This process enables the debugging functionality within the firmware and generates all the necessary files required for debugging purposes. +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_box_CDN.jpg width=700 -> **NOTE:** Building and debugging the Flipper Zero firmware is fully supported on MacOS and Linux. Support for Windows is in beta test. +Before you start using your Devboard, you need to prepare your Flipper Zero and Devboard for debugging. In this guide, we'll walk you through all the necessary steps and provide links to explore the Devboard's capabilities further. *** -## Updating the firmware of your Developer Board +## Step 1. Enable Debug Mode on your Flipper Zero -Update the firmware of your Developer Board before using it. For more information, visit [Firmware update on Developer Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/update). +Since the main purpose of the Developer board is to debug applications on Flipper Zero, you first need to enable Debug Mode. To do so, go to **Settings → System** and set **Debug** to **ON**. -*** - -## Installing Git - -You'll need Git installed on your computer to clone the firmware repository. If you don't have Git, install it by doing the following: - -* **MacOS** - - On MacOS, install the **Xcode Command Line Tools** package, which includes Git as one of the pre-installed command-line utilities, by running in the Terminal the following command: +\image html https://cdn.flipperzero.one/Flipper_Zero_enamble_debug_CDN.jpg width=700 - ```text - xcode-select --install - ``` +> [!note] +> Debug Mode needs to be re-enabled after each update of Flipper Zero's firmware. -* **Linux** +Debug Mode allows you to debug your apps for Flipper Zero, as well as access debugging options in apps via the user interface and CLI. To learn more about Flipper Zero CLI, visit [Command-line interface in Flipper Docs](https://docs.flipper.net/development/cli). - On Linux, you can install Git using your package manager. For example, on Ubuntu, run in the Terminal the following command: - - ```text - sudo apt install git - ``` - -For other distributions, refer to your package manager documentation. +\image html https://cdn.flipperzero.one/Flipper_Zero_Command_Line_Interface_CDN.jpg width=700 *** -## Building the firmware - -First, clone the firmware repository: - -```text -git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git -cd flipperzero-firmware -``` - -Then, run the **Flipper Build Tool** (FBT) to build the firmware: +## Step 2. Update firmware on the Developer Board -```text -./fbt -``` +The Developer Board comes with stock firmware that may not include all the latest features and bug fixes. To ensure optimal performance, please update your board's firmware using the instructions in [Firmware update on Devboard](#dev_board_fw_update). *** -## Connecting the Developer Board - -The Developer Board can work in the **Wired** mode and two **Wireless** modes: **Wi-Fi access point (AP)** mode and **Wi-Fi client (STA)** mode. The Wired mode is the simplest to set up, but requires a USB Type-C cable. The Wireless modes are more complex to set up, but they allow you to debug your Flipper Zero wirelessly. - - > **NOTE:** Use the following credentials when connecting to the Developer Board in **Wi-Fi access point** mode:\n - Name: **blackmagic**\n - Password: **iamwitcher** - -## Wired - -![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/jZdVlRTPVdSQVegzCyXp7_monosnap-miro-2023-06-22-16-28-06.jpg) - -To connect the Developer Board in **Wired** mode, do the following: - -1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. - -2. On your computer, open the **Terminal** and run the following: - - * **MacOS** - - ```text - ls /dev/cu.* - ``` - - * **Linux** +## Step 3. Plug the Devboard into Flipper Zero {#dev_board_get_started_step-3} - ```text - ls /dev/tty* - ``` +Once your Developer Board firmware is up to date, you can proceed to plug it into your Flipper Zero. Two important things to keep in mind: - Note the list of devices. +1. **Power off your Flipper Zero before plugging in the Developer Board.** -3. Connect the Developer Board to your computer via a USB-C cable. + If you skip this step, you may corrupt the data stored on the microSD card. Connecting external modules with a large capacitive load may affect the microSD card's power supply since both the microSD card and external module are powered from the same 3.3 V power source inside Flipper Zero. -4. Rerun the command. Two new devices have to appear: this is the Developer Board. +2. **Make sure the Developer Board is inserted all the way in.** - > **NOTE:** If the Developer Board doesn't appear in the list of devices, try using a different cable, USB port, or computer. - > - > **NOTE:** Flipper Zero logs can only be viewed when the Developer Board is connected via USB. The option to view logs over Wi-Fi will be added in future updates. For more information, visit [Reading logs via the Dev Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/reading-logs). + If your Flipper Zero isn't in a silicone case, insert the module all the way in so there is no gap between your Flipper Zero and the Devboard. You may need to apply more force to insert it completely. After that, press and hold the **BACK** button to power on your Flipper Zero. -## Wireless + \image html https://cdn.flipperzero.one/Flipper_Zero_external_module_without_case_CDN.jpg width=700 -### Wi-Fi access point (AP) mode + If your Flipper Zero is in a silicone case, insert the module all the way in so there is no gap in the middle between the silicone case and the module. After that, press and hold the **BACK** button to power on your Flipper Zero. -![The Developer Board in Wi-Fi access point mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/tKRTMHAuruiLSEce2a8Ve_monosnap-miro-2023-06-22-16-39-17.jpg) + \image html https://cdn.flipperzero.one/Flipper_Zero_external_module_with_case_CDN.jpg width=700 -Out of the box, the Developer Board is configured to work as a **Wi-Fi access point**. This means it'll create its own Wi-Fi network to which you can connect. If your Developer Board doesn't create a Wi-Fi network, it is probably configured to work in **Wi-Fi client** mode. To reset your Developer Board back to **Wi-Fi access point** mode, press and hold the **BOOT** button for 10 seconds, then wait for the module to reboot. - -![You can reconfigure the Developer Board mode by pressing and holding the BOOT button](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/57eELJsAwMxeZCEA1NMJw_monosnap-miro-2023-06-22-20-33-27.jpg) - -To connect the Developer Board in **Wi-Fi access point** mode, do the following: - -1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. - -2. Open Wi-Fi settings on your client device (phone, laptop, or other). - -3. Connect to the network: - - * Name: **blackmagic** - - * Password: **iamwitcher** - -4. To configure the Developer Board, open a browser and go to `http://192.168.4.1`. - -### Wi-Fi client (STA) mode - -![The Developer Board in Wi-Fi client mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/xLQpFyYPfUS5Cx0uQhrNd_monosnap-miro-2023-06-23-12-34-36.jpg) - -To connect the Developer Board in **Wi-Fi client** mode, you need to configure it to connect to your Wi-Fi network by doing the following: - -1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. - -2. Connect to the Developer Board in **Wi-Fi access point** mode. - -3. In a browser, go to the configuration page on `http://192.168.4.1`. - -4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby networks. - -5. Save the configuration and reboot the Developer Board. - -![In the Wi-Fi tab, you can set the Developer Board mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/klbLVj8lz2bEvm7j4wRaj_monosnap-miro-2023-06-23-13-06-32.jpg) +*** -After rebooting, the Developer Board connects to your Wi-Fi network. You can connect to the device using the mDNS name **blackmagic.local** or the IP address it got from your router (you'll have to figure this out yourself, every router is different). +## Step 4. Connect to a computer -After connecting to your debugger via , you can find its IP address in the **SYS** tab. You can also change the debugger's mode to **AP** or **STA** there. +Now, you can connect the Developer Board to your computer via USB or Wi-Fi, depending on your needs. We described both methods in separate documents: -![In the SYS tab, you can view the IP address of your Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/5XbUptlfqzlV0p6hRUqiG_monosnap-miro-2023-06-22-18-11-30.jpg) +- **[Via USB cable](#dev_board_usb_connection)** for debugging in DAP Link or Black Magic mode, and reading logs. +- [Via Wi-Fi](#dev_board_wifi_connection) for debugging in Black Magic mode. *** -## Debugging the firmware +## Next steps + +You are ready to debug now! To further explore what you can do with the Devboard, check out these pages: -Open the **Terminal** in the **flipperzero-firmware** directory that you cloned earlier and run the following command: +- [Debugging via the Devboard](#dev_board_debugging_guide) +- [Devboard debug modes](#dev_board_debug_modes) +- [Reading logs via the Devboard](#dev_board_reading_logs) -```text -./fbt flash -``` +These guides should help you get started with your Devboard. If you have any questions or you want to share your experience, don't hesitate to join our community on [Reddit](https://www.reddit.com/r/flipperzero/) and [Discord](https://discord.com/invite/flipper), where we have a dedicated #wifi-devboard channel. -This will upload the firmware you've just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware using the [GDB](https://www.gnu.org/software/gdb/) debugger. We recommend using **VSCode** with the recommended extensions, and we have pre-made configurations for it. -To debug in **VSCode**, do the following: -1. In VSCode, open the **flipperzero-firmware** directory. -2. You should see a notification about recommended extensions. Install them. - If there were no notifications, open the **Extensions** tab, enter `@recommended` in the search bar, and install the workspace recommendations. -3. In the **Terminal**, run the `./fbt vscode_dist` command. This will generate the VSCode configuration files needed for debugging. -4. In VSCode, open the **Run and Debug** tab and select **Attach FW (blackmagic)** from the dropdown menu. -5. If needed, flash your Flipper Zero with the `./fbt flash` command, then click the **Play** button in the debug sidebar to start the debugging session. -6. Note that starting a debug session halts the execution of the firmware, so you'll need to click the **Continue** button on the toolbar at the top of your VSCode window to continue execution. -![Click Continue in the toolbar to continue execution of the firmware](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/lp8ygGaZ3DvWD3OSI9yGO_monosnap-miro-2023-06-23-17-58-09.jpg) -To learn about debugging, visit the following pages: -* [Debugging with GDB](https://sourceware.org/gdb/current/onlinedocs/gdb.pdf) -* [Debugging in VS Code](https://code.visualstudio.com/docs/editor/debugging) diff --git a/documentation/devboard/Reading logs via the Dev Board.md b/documentation/devboard/Reading logs via the Dev Board.md index c2daf83ace..56d0c87ef8 100644 --- a/documentation/devboard/Reading logs via the Dev Board.md +++ b/documentation/devboard/Reading logs via the Dev Board.md @@ -8,9 +8,9 @@ The Developer Board allows you to read Flipper Zero logs via UART. Unlike readin ## Setting the log level -Depending on your needs, you can set the log level by going to **Main Menu → Settings → Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). +Depending on your needs, you can set the log level by going to **Main Menu → Settings → Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipper.net/basics/settings#d5TAt). -![You can manually set the preferred log level](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/INzQMw8QUsG9PXi30WFS0_monosnap-miro-2023-07-11-13-29-47.jpg) +\image html https://cdn.flipperzero.one/Flipper_Zero_log_level.jpg "You can manually set the preferred log level" width=700 *** @@ -18,9 +18,9 @@ Depending on your needs, you can set the log level by going to **Main Menu → S Depending on your operating system, you need to install an additional application on your computer to read logs via the Developer Board: -### MacOS +### macOS -On MacOS, you need to install the **minicom** communication program by doing the following: +On macOS, you need to install the **minicom** communication program by doing the following: 1. [Install Homebrew](https://brew.sh/) by running the following command in the Terminal: @@ -47,7 +47,7 @@ After installation of minicom on your macOS computer, you can connect to the Dev Note the list of devices. 3. Connect the developer board to your computer using a USB Type-C cable. -![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700 4. Rerun the command. Two new devices have to appear: this is the Developer Board. @@ -100,7 +100,7 @@ After installation of minicom on your Linux computer, you can connect to the Dev Note the list of devices. 3. Connect the developer board to your computer using a USB Type-C cable. -![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700 4. Rerun the command. Two new devices have to appear: this is the Developer Board. @@ -143,17 +143,17 @@ On Windows, do the following: 2. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on. 3. Connect the developer board to your computer using a USB Type-C cable. -![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700 4. Find the serial port that the developer board is connected to by going to **Device Manager → Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board. -![Find the serial port in your Device Manager](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KKLQJK1lvqmI5iab3d__C_image.png) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_Device_Manager.png width=700 5. Run the PuTTY application and select **Serial** as the connection type. 6. Enter the port number you found in the previous step into the **Serial line** field. 7. Set the **Speed** parameter to **230400** and click **Open**. -![Set speed to 230400](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/ROBSJyfQ_CXiy4GUZcPbs_monosnap-miro-2023-07-12-13-56-47.jpg) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_PuTTy.jpg width=700 8. View logs of your Flipper Zero in the PuTTY terminal window. diff --git a/documentation/devboard/USB connection to the Devboard.md b/documentation/devboard/USB connection to the Devboard.md new file mode 100644 index 0000000000..af0d9113d7 --- /dev/null +++ b/documentation/devboard/USB connection to the Devboard.md @@ -0,0 +1,22 @@ +# USB connection to the Devboard {#dev_board_usb_connection} + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_USB_connection_CDN.jpg width=700 + +To connect to the Developer Board via USB, do the following: + +1. If the Devboard isn't connected to your Flipper Zero, turn off your Flipper Zero and connect the Developer Board to it. Then, turn your Flipper Zero back on. + +2. On your computer, check the list of serial devices. + + - **macOS:** On your computer, run `ls /dev/cu.*` in the Terminal. + + - **Linux:** On your computer, run `ls /dev/tty*` in the Terminal. + + - **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section. + +3. Connect the Devboard to your computer via a USB-C cable. + +4. Repeat **Step 2**. Two new devices will appear — this is the Developer Board. + +> [!warning] +> If the Developer Board doesn't appear in the list of devices, try using a different cable, USB port, or computer. \ No newline at end of file diff --git a/documentation/devboard/Wi-Fi connection to the Devboard.md b/documentation/devboard/Wi-Fi connection to the Devboard.md new file mode 100644 index 0000000000..fd3bc32496 --- /dev/null +++ b/documentation/devboard/Wi-Fi connection to the Devboard.md @@ -0,0 +1,60 @@ +# Wi-Fi connection to the Devboard {#dev_board_wifi_connection} + +You can connect to the Developer Board wirelessly in two ways: + +- **Wi-Fi access point mode (default):** The Devboard creates its own Wi-Fi network, which you can connect to in order to access its web interface and debug via Wi-Fi. The downside is that you will need to disconnect from your current Wi-Fi network, resulting in a loss of internet connection. + +- **Wi-Fi client mode:** You can connect to the Devboard through an existing Wi-Fi network, allowing you to access the Devboard web interface and debug via Wi-Fi without losing your internet connection. + +Let's go over both of these modes below. + +*** + +## Wi-Fi access point (AP) mode {#wifi-access-point} + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_Access_Point_CDN.jpg width=700 + +Out of the box, the Developer Board is configured to work as a Wi-Fi access point. To connect the Developer Board in this mode, do the following: + +1. Plug the Wi-Fi Devboard into your Flipper Zero by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. + +2. Open Wi-Fi settings on your client device (phone, laptop, or other). + +3. Connect to the network: + + Name: `blackmagic` + Password: `iamwitcher` + + If your computer fails to find the **blackmagic** network, read the [troubleshooting section](#wifi-access-point_troubleshooting) below. + +4. To access the Devboard's web interface, open a browser and go to or . + +### If your computer fails to find the black magic network {#wifi-access-point_troubleshooting} + +- Reset Wi-Fi connection on your computer. + +- The Developer Board is probably configured to work in Wi-Fi client mode. → Reset your Developer Board settings to default by pressing and holding the **BOOT** button for **10 seconds**, then wait for the Devboard to reboot. After the reset, the Devboard will work in Wi-Fi access point mode. + +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot.jpg width=700 + +*** + +## Wi-Fi client (STA) mode {#wifi-client-mode} + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_STA_CDN.jpg width=700 + +To connect the Developer Board in **Wi-Fi client** mode, you need to configure it to connect to your Wi-Fi network by doing the following: + +1. Plug the Wi-Fi Devboard into your Flipper Zero by turning off your Flipper Zero and connecting the Developer Board, and then turning the device back on. + +2. Connect to the Developer Board in [Wi-Fi access point](#wifi-access-point) mode. + +3. In a browser, go to the Devboard's web interface at or . + +4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby 2.4 GHz networks (5 GHz networks aren't supported). + +5. Save the configuration and reboot the Developer Board. + + \image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_connect_to_WiFi_CDN.jpg width=700 + +6. Now, you can access the Devboard's web interface at [http://blackmagic.local](https://blackmagic.local) via the existing Wi-Fi network without losing connection to the internet. diff --git a/documentation/doxygen/dev_board.dox b/documentation/doxygen/dev_board.dox index 6caa44c705..8d5a2bf35d 100644 --- a/documentation/doxygen/dev_board.dox +++ b/documentation/doxygen/dev_board.dox @@ -1,10 +1,37 @@ /** -@page dev_board Developer Board +@page dev_board Wi-Fi Developer Board -[ESP32-based development board](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/wifi-devboard). +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_laptop_CDN.jpg width=700 + +Wi-Fi-enabled Developer Board brings debugging and firmware update capabilities to your Flipper Zero. The Developer Board is based on the ESP32-S2 MCU with custom firmware incorporating Black Magic Debug and CMSIS-DAP, and is built with ESP-IDF. It can flash and debug various microprocessors and microcontrollers (including the one used in your Flipper Zero) via Wi-Fi or USB cable. + +The Developer Board provides a debug interface, allowing developers to halt program execution, set breakpoints, inspect variables and memory, and step through code execution. + +
+Get your Wi-Fi Developer Board +
+
+ +Check out these guides to get started with the Devboard: - @subpage dev_board_get_started — Quick start for new users +- @subpage dev_board_fw_update — Keep the Developer Board up to date +- @subpage dev_board_usb_connection — Instructions for Windows, macOS and Linux +- @subpage dev_board_wifi_connection — Instructions for Windows, macOS and Linux +- @subpage dev_board_debugging_guide — Learn how it works +- @subpage dev_board_debug_modes — Available modes and how to switch between them - @subpage dev_board_reading_logs — Find out what is currently happening on the system -- @subpage dev_board_fw_update — Keep the developer board up to date + +## Hardware + +The Developer Board is equipped with an [ESP32-S2-WROVER](https://www.espressif.com/en/products/socs/esp32-s2) module, which includes built-in Wi-Fi capabilities. It also offers GPIO pins for easy connectivity to various targets. Additionally, the Developer Board features a USB Type-C connector for data transfer and power supply. For user interaction, the Developer Board has tactile switches. + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_hardware_CDN.jpg width=700 + +## Additional resources + +To learn more about the Wi-Fi Developer Board hardware, visit [Schematics in Flipper Docs](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/schematics). + +For additional information about Flipper Zero GPIO pins, visit [GPIO & modules in Flipper Docs](https://docs.flipperzero.one/gpio-and-modules). */ diff --git a/documentation/doxygen/header.html b/documentation/doxygen/header.html index 5cc0aba38f..e987e39aae 100644 --- a/documentation/doxygen/header.html +++ b/documentation/doxygen/header.html @@ -52,7 +52,7 @@ -
$projectname $projectnumber +
$projectname $projectnumber
$projectbrief
diff --git a/documentation/fbt.md b/documentation/fbt.md index 59f6aa154e..2635a43fc6 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -44,7 +44,7 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio ## VSCode integration -`fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. +`fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. To use language servers other than the default VS Code C/C++ language server, use `./fbt vscode_dist LANG_SERVER=` instead. Currently `fbt` supports the default language server (`cpptools`) and `clangd`. From d9d3867ce19fbfba92647187ef2820eb234020de Mon Sep 17 00:00:00 2001 From: SUMUKH <130692934+sumukhj1219@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:03:51 +0530 Subject: [PATCH 138/236] Fixes Mouse Clicker Should have a "0" value setting for "as fast as possible" #3876 (#3894) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixes mouse clicking rate * Hid: limit max clicks to 100/s, rewrite code to make it more robust Co-authored-by: あく --- .../system/hid_app/views/hid_mouse_clicker.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index 0bb815249d..e289b71796 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -7,7 +7,7 @@ #define TAG "HidMouseClicker" #define DEFAULT_CLICK_RATE 1 -#define MAXIMUM_CLICK_RATE 60 +#define MAXIMUM_CLICK_RATE 100 struct HidMouseClicker { View* view; @@ -34,7 +34,9 @@ static void hid_mouse_clicker_start_or_restart_timer(void* context) { HidMouseClickerModel * model, { furi_timer_start( - hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate); + hid_mouse_clicker->timer, + furi_kernel_get_tick_frequency() / + ((model->rate) ? model->rate : MAXIMUM_CLICK_RATE)); }, true); } @@ -75,7 +77,11 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { // Clicks/s char label[20]; - snprintf(label, sizeof(label), "%d clicks/s", model->rate); + if(model->rate) { + snprintf(label, sizeof(label), "%d clicks/s", model->rate); + } else { + snprintf(label, sizeof(label), "max clicks/s"); + } elements_multiline_text_aligned(canvas, 28, 37, AlignCenter, AlignBottom, label); canvas_draw_icon(canvas, 25, 20, &I_ButtonUp_7x4); @@ -139,7 +145,7 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { consumed = true; break; case InputKeyDown: - if(model->rate > 1) { + if(model->rate > 0) { model->rate--; } rate_changed = true; From 0f831412fa617ae51afc5375b801c3b5c7543834 Mon Sep 17 00:00:00 2001 From: porta Date: Mon, 14 Oct 2024 17:50:18 +0300 Subject: [PATCH 139/236] [FL-3909] CLI improvements, part I (#3928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: cli top blinking * feat: clear prompt on down key * feat: proper-er ansi escape sequence handling * ci: fix compact build error * Make PVS happy * style: remove magic numbers * style: review suggestions Co-authored-by: あく --- applications/main/subghz/subghz_cli.c | 9 +- applications/main/subghz/subghz_cli.h | 1 + applications/services/cli/cli.c | 194 ++++++++++++----- applications/services/cli/cli.h | 16 +- applications/services/cli/cli_ansi.c | 76 +++++++ applications/services/cli/cli_ansi.h | 94 ++++++++ applications/services/cli/cli_commands.c | 227 +++++++++++++++++--- applications/services/crypto/crypto_cli.c | 9 +- applications/services/storage/storage_cli.c | 3 +- 9 files changed, 528 insertions(+), 101 deletions(-) create mode 100644 applications/services/cli/cli_ansi.c create mode 100644 applications/services/cli/cli_ansi.h diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 6375f2eee4..bce88b7a35 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -999,13 +999,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { chat_event = subghz_chat_worker_get_event_chat(subghz_chat); switch(chat_event.event) { case SubGhzChatEventInputData: - if(chat_event.c == CliSymbolAsciiETX) { + if(chat_event.c == CliKeyETX) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); break; - } else if( - (chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) { + } else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) { size_t len = furi_string_utf8_length(input); if(len > furi_string_utf8_length(name)) { printf("%s", "\e[D\e[1P"); @@ -1027,7 +1026,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } furi_string_set(input, sysmsg); } - } else if(chat_event.c == CliSymbolAsciiCR) { + } else if(chat_event.c == CliKeyCR) { printf("\r\n"); furi_string_push_back(input, '\r'); furi_string_push_back(input, '\n'); @@ -1041,7 +1040,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { furi_string_printf(input, "%s", furi_string_get_cstr(name)); printf("%s", furi_string_get_cstr(input)); fflush(stdout); - } else if(chat_event.c == CliSymbolAsciiLF) { + } else if(chat_event.c == CliKeyLF) { //cut out the symbol \n } else { putc(chat_event.c, stdout); diff --git a/applications/main/subghz/subghz_cli.h b/applications/main/subghz/subghz_cli.h index f6388218f4..18c84c3e0f 100644 --- a/applications/main/subghz/subghz_cli.h +++ b/applications/main/subghz/subghz_cli.h @@ -1,5 +1,6 @@ #pragma once #include +#include void subghz_on_system_start(void); diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 0d8f52c04e..6f18ee9731 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -1,12 +1,15 @@ #include "cli_i.h" #include "cli_commands.h" #include "cli_vcp.h" +#include "cli_ansi.h" #include #include #define TAG "CliSrv" #define CLI_INPUT_LEN_LIMIT 256 +#define CLI_PROMPT ">: " // qFlipper does not recognize us if we use escape sequences :( +#define CLI_PROMPT_LENGTH 3 // printable characters Cli* cli_alloc(void) { Cli* cli = malloc(sizeof(Cli)); @@ -85,7 +88,7 @@ bool cli_cmd_interrupt_received(Cli* cli) { char c = '\0'; if(cli_is_connected(cli)) { if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) { - return c == CliSymbolAsciiETX; + return c == CliKeyETX; } } else { return true; @@ -102,7 +105,8 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) { } void cli_motd(void) { - printf("\r\n" + printf(ANSI_FLIPPER_BRAND_ORANGE + "\r\n" " _.-------.._ -,\r\n" " .-\"```\"--..,,_/ /`-, -, \\ \r\n" " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" @@ -116,12 +120,11 @@ void cli_motd(void) { " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" - "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" - "\r\n" - "Welcome to Flipper Zero Command Line Interface!\r\n" + "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" ANSI_RESET + "\r\n" ANSI_FG_BR_WHITE "Welcome to " ANSI_FLIPPER_BRAND_ORANGE + "Flipper Zero" ANSI_FG_BR_WHITE " Command Line Interface!\r\n" "Read the manual: https://docs.flipper.net/development/cli\r\n" - "Run `help` or `?` to list available commands\r\n" - "\r\n"); + "Run `help` or `?` to list available commands\r\n" ANSI_RESET "\r\n"); const Version* firmware_version = furi_hal_version_get_firmware_version(); if(firmware_version) { @@ -142,7 +145,7 @@ void cli_nl(Cli* cli) { void cli_prompt(Cli* cli) { UNUSED(cli); - printf("\r\n>: %s", furi_string_get_cstr(cli->line)); + printf("\r\n" CLI_PROMPT "%s", furi_string_get_cstr(cli->line)); fflush(stdout); } @@ -165,7 +168,7 @@ static void cli_handle_backspace(Cli* cli) { cli->cursor_position--; } else { - cli_putc(cli, CliSymbolAsciiBell); + cli_putc(cli, CliKeyBell); } } @@ -241,7 +244,7 @@ static void cli_handle_enter(Cli* cli) { printf( "`%s` command not found, use `help` or `?` to list all available commands", furi_string_get_cstr(command)); - cli_putc(cli, CliSymbolAsciiBell); + cli_putc(cli, CliKeyBell); } cli_reset(cli); @@ -305,8 +308,85 @@ static void cli_handle_autocomplete(Cli* cli) { cli_prompt(cli); } -static void cli_handle_escape(Cli* cli, char c) { - if(c == 'A') { +typedef enum { + CliCharClassWord, + CliCharClassSpace, + CliCharClassOther, +} CliCharClass; + +/** + * @brief Determines the class that a character belongs to + * + * The return value of this function should not be used on its own; it should + * only be used for comparing it with other values returned by this function. + * This function is used internally in `cli_skip_run`. + */ +static CliCharClass cli_char_class(char c) { + if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { + return CliCharClassWord; + } else if(c == ' ') { + return CliCharClassSpace; + } else { + return CliCharClassOther; + } +} + +typedef enum { + CliSkipDirectionLeft, + CliSkipDirectionRight, +} CliSkipDirection; + +/** + * @brief Skips a run of a class of characters + * + * @param string Input string + * @param original_pos Position to start the search at + * @param direction Direction in which to perform the search + * @returns The position at which the run ends + */ +static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) { + if(furi_string_size(string) == 0) return original_pos; + if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos; + if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string)) + return original_pos; + + int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0; + int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1; + int32_t position = original_pos; + CliCharClass start_class = + cli_char_class(furi_string_get_char(string, position + look_offset)); + + while(true) { + position += increment; + if(position < 0) break; + if(position >= (int32_t)furi_string_size(string)) break; + if(cli_char_class(furi_string_get_char(string, position + look_offset)) != start_class) + break; + } + + return MAX(0, position); +} + +void cli_process_input(Cli* cli) { + CliKeyCombo combo = cli_read_ansi_key_combo(cli); + FURI_LOG_T(TAG, "code=0x%02x, mod=0x%x\r\n", combo.key, combo.modifiers); + + if(combo.key == CliKeyTab) { + cli_handle_autocomplete(cli); + + } else if(combo.key == CliKeySOH) { + furi_delay_ms(33); // We are too fast, Minicom is not ready yet + cli_motd(); + cli_prompt(cli); + + } else if(combo.key == CliKeyETX) { + cli_reset(cli); + cli_prompt(cli); + + } else if(combo.key == CliKeyEOT) { + cli_reset(cli); + + } else if(combo.key == CliKeyUp && combo.modifiers == CliModKeyNo) { // Use previous command if line buffer is empty if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) { // Set line buffer and cursor position @@ -315,67 +395,85 @@ static void cli_handle_escape(Cli* cli, char c) { // Show new line to user printf("%s", furi_string_get_cstr(cli->line)); } - } else if(c == 'B') { - } else if(c == 'C') { + + } else if(combo.key == CliKeyDown && combo.modifiers == CliModKeyNo) { + // Clear input buffer + furi_string_reset(cli->line); + cli->cursor_position = 0; + printf("\r" CLI_PROMPT "\e[0K"); + + } else if(combo.key == CliKeyRight && combo.modifiers == CliModKeyNo) { + // Move right if(cli->cursor_position < furi_string_size(cli->line)) { cli->cursor_position++; printf("\e[C"); } - } else if(c == 'D') { + + } else if(combo.key == CliKeyLeft && combo.modifiers == CliModKeyNo) { + // Move left if(cli->cursor_position > 0) { cli->cursor_position--; printf("\e[D"); } - } - fflush(stdout); -} -void cli_process_input(Cli* cli) { - char in_chr = cli_getc(cli); - size_t rx_len; + } else if(combo.key == CliKeyHome && combo.modifiers == CliModKeyNo) { + // Move to beginning of line + cli->cursor_position = 0; + printf("\e[%uG", CLI_PROMPT_LENGTH + 1); // columns start at 1 \(-_-)/ - if(in_chr == CliSymbolAsciiTab) { - cli_handle_autocomplete(cli); - } else if(in_chr == CliSymbolAsciiSOH) { - furi_delay_ms(33); // We are too fast, Minicom is not ready yet - cli_motd(); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiETX) { - cli_reset(cli); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiEOT) { - cli_reset(cli); - } else if(in_chr == CliSymbolAsciiEsc) { - rx_len = cli_read(cli, (uint8_t*)&in_chr, 1); - if((rx_len > 0) && (in_chr == '[')) { - cli_read(cli, (uint8_t*)&in_chr, 1); - cli_handle_escape(cli, in_chr); - } else { - cli_putc(cli, CliSymbolAsciiBell); - } - } else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) { + } else if(combo.key == CliKeyEnd && combo.modifiers == CliModKeyNo) { + // Move to end of line + cli->cursor_position = furi_string_size(cli->line); + printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1); + + } else if( + combo.modifiers == CliModKeyCtrl && + (combo.key == CliKeyLeft || combo.key == CliKeyRight)) { + // Skip run of similar chars to the left or right + CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft : + CliSkipDirectionRight; + cli->cursor_position = cli_skip_run(cli->line, cli->cursor_position, direction); + printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1); + + } else if(combo.key == CliKeyBackspace || combo.key == CliKeyDEL) { cli_handle_backspace(cli); - } else if(in_chr == CliSymbolAsciiCR) { + + } else if(combo.key == CliKeyETB) { // Ctrl + Backspace + // Delete run of similar chars to the left + size_t run_start = cli_skip_run(cli->line, cli->cursor_position, CliSkipDirectionLeft); + furi_string_replace_at(cli->line, run_start, cli->cursor_position - run_start, ""); + cli->cursor_position = run_start; + printf( + "\e[%zuG%s\e[0K\e[%zuG", // move cursor, print second half of line, erase remains, move cursor again + CLI_PROMPT_LENGTH + cli->cursor_position + 1, + furi_string_get_cstr(cli->line) + run_start, + CLI_PROMPT_LENGTH + run_start + 1); + + } else if(combo.key == CliKeyCR) { cli_handle_enter(cli); + } else if( - (in_chr >= 0x20 && in_chr < 0x7F) && //-V560 + (combo.key >= 0x20 && combo.key < 0x7F) && //-V560 (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { if(cli->cursor_position == furi_string_size(cli->line)) { - furi_string_push_back(cli->line, in_chr); - cli_putc(cli, in_chr); + furi_string_push_back(cli->line, combo.key); + cli_putc(cli, combo.key); } else { // Insert character to line buffer - const char in_str[2] = {in_chr, 0}; + const char in_str[2] = {combo.key, 0}; furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str); // Print character in replace mode - printf("\e[4h%c\e[4l", in_chr); + printf("\e[4h%c\e[4l", combo.key); fflush(stdout); } cli->cursor_position++; + } else { - cli_putc(cli, CliSymbolAsciiBell); + cli_putc(cli, CliKeyBell); } + + fflush(stdout); } void cli_add_command( diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index bb84670a73..c91f71c447 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -10,26 +10,12 @@ extern "C" { #endif -typedef enum { - CliSymbolAsciiSOH = 0x01, - CliSymbolAsciiETX = 0x03, - CliSymbolAsciiEOT = 0x04, - CliSymbolAsciiBell = 0x07, - CliSymbolAsciiBackspace = 0x08, - CliSymbolAsciiTab = 0x09, - CliSymbolAsciiLF = 0x0A, - CliSymbolAsciiCR = 0x0D, - CliSymbolAsciiEsc = 0x1B, - CliSymbolAsciiUS = 0x1F, - CliSymbolAsciiSpace = 0x20, - CliSymbolAsciiDel = 0x7F, -} CliSymbols; - typedef enum { CliCommandFlagDefault = 0, /**< Default, loader lock is used */ CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */ CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ + CliCommandFlagHidden = (1 << 2), /**< Not shown in `help` */ } CliCommandFlag; #define RECORD_CLI "cli" diff --git a/applications/services/cli/cli_ansi.c b/applications/services/cli/cli_ansi.c new file mode 100644 index 0000000000..d27c20bad0 --- /dev/null +++ b/applications/services/cli/cli_ansi.c @@ -0,0 +1,76 @@ +#include "cli_ansi.h" + +/** + * @brief Converts a single character representing a special key into the enum + * representation + */ +static CliKey cli_ansi_key_from_mnemonic(char c) { + switch(c) { + case 'A': + return CliKeyUp; + case 'B': + return CliKeyDown; + case 'C': + return CliKeyRight; + case 'D': + return CliKeyLeft; + case 'F': + return CliKeyEnd; + case 'H': + return CliKeyHome; + default: + return CliKeyUnrecognized; + } +} + +CliKeyCombo cli_read_ansi_key_combo(Cli* cli) { + char ch = cli_getc(cli); + + if(ch != CliKeyEsc) + return (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = ch, + }; + + ch = cli_getc(cli); + + // ESC ESC -> ESC + if(ch == '\e') + return (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = '\e', + }; + + // ESC -> Alt + + if(ch != '[') + return (CliKeyCombo){ + .modifiers = CliModKeyAlt, + .key = cli_getc(cli), + }; + + ch = cli_getc(cli); + + // ESC [ 1 + if(ch == '1') { + // ESC [ 1 ; + if(cli_getc(cli) == ';') { + CliModKey modifiers = (cli_getc(cli) - '0'); // convert following digit to a number + modifiers &= ~1; + return (CliKeyCombo){ + .modifiers = modifiers, + .key = cli_ansi_key_from_mnemonic(cli_getc(cli)), + }; + } + + return (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = CliKeyUnrecognized, + }; + } + + // ESC [ + return (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = cli_ansi_key_from_mnemonic(ch), + }; +} diff --git a/applications/services/cli/cli_ansi.h b/applications/services/cli/cli_ansi.h new file mode 100644 index 0000000000..110d8a5fcd --- /dev/null +++ b/applications/services/cli/cli_ansi.h @@ -0,0 +1,94 @@ +#pragma once + +#include "cli.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ANSI_RESET "\e[0m" +#define ANSI_BOLD "\e[1m" +#define ANSI_FAINT "\e[2m" + +#define ANSI_FG_BLACK "\e[30m" +#define ANSI_FG_RED "\e[31m" +#define ANSI_FG_GREEN "\e[32m" +#define ANSI_FG_YELLOW "\e[33m" +#define ANSI_FG_BLUE "\e[34m" +#define ANSI_FG_MAGENTA "\e[35m" +#define ANSI_FG_CYAN "\e[36m" +#define ANSI_FG_WHITE "\e[37m" +#define ANSI_FG_BR_BLACK "\e[90m" +#define ANSI_FG_BR_RED "\e[91m" +#define ANSI_FG_BR_GREEN "\e[92m" +#define ANSI_FG_BR_YELLOW "\e[93m" +#define ANSI_FG_BR_BLUE "\e[94m" +#define ANSI_FG_BR_MAGENTA "\e[95m" +#define ANSI_FG_BR_CYAN "\e[96m" +#define ANSI_FG_BR_WHITE "\e[97m" + +#define ANSI_BG_BLACK "\e[40m" +#define ANSI_BG_RED "\e[41m" +#define ANSI_BG_GREEN "\e[42m" +#define ANSI_BG_YELLOW "\e[43m" +#define ANSI_BG_BLUE "\e[44m" +#define ANSI_BG_MAGENTA "\e[45m" +#define ANSI_BG_CYAN "\e[46m" +#define ANSI_BG_WHITE "\e[47m" +#define ANSI_BG_BR_BLACK "\e[100m" +#define ANSI_BG_BR_RED "\e[101m" +#define ANSI_BG_BR_GREEN "\e[102m" +#define ANSI_BG_BR_YELLOW "\e[103m" +#define ANSI_BG_BR_BLUE "\e[104m" +#define ANSI_BG_BR_MAGENTA "\e[105m" +#define ANSI_BG_BR_CYAN "\e[106m" +#define ANSI_BG_BR_WHITE "\e[107m" + +#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m" + +typedef enum { + CliKeyUnrecognized = 0, + + CliKeySOH = 0x01, + CliKeyETX = 0x03, + CliKeyEOT = 0x04, + CliKeyBell = 0x07, + CliKeyBackspace = 0x08, + CliKeyTab = 0x09, + CliKeyLF = 0x0A, + CliKeyCR = 0x0D, + CliKeyETB = 0x17, + CliKeyEsc = 0x1B, + CliKeyUS = 0x1F, + CliKeySpace = 0x20, + CliKeyDEL = 0x7F, + + CliKeySpecial = 0x80, + CliKeyLeft, + CliKeyRight, + CliKeyUp, + CliKeyDown, + CliKeyHome, + CliKeyEnd, +} CliKey; + +typedef enum { + CliModKeyNo = 0, + CliModKeyAlt = 2, + CliModKeyCtrl = 4, + CliModKeyMeta = 8, +} CliModKey; + +typedef struct { + CliModKey modifiers; + CliKey key; +} CliKeyCombo; + +/** + * @brief Reads a key or key combination + */ +CliKeyCombo cli_read_ansi_key_combo(Cli* cli); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index c3539813b1..cb813840a4 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -1,5 +1,6 @@ #include "cli_commands.h" #include "cli_command_gpio.h" +#include "cli_ansi.h" #include #include @@ -10,6 +11,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -52,37 +54,196 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) { } } -void cli_command_help(Cli* cli, FuriString* args, void* context) { +// Lil Easter egg :> +void cli_command_neofetch(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); UNUSED(args); UNUSED(context); + + static const char* const neofetch_logo[] = { + " _.-------.._ -,", + " .-\"```\"--..,,_/ /`-, -, \\ ", + " .:\" /:/ /'\\ \\ ,_..., `. | |", + " / ,----/:/ /`\\ _\\~`_-\"` _;", + " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ", + " | | | 0 | | .-' ,/` /", + " | ,..\\ \\ ,.-\"` ,/` /", + "; : `/`\"\"\\` ,/--==,/-----,", + "| `-...| -.___-Z:_______J...---;", + ": ` _-'", + }; +#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE + + // Determine logo parameters + size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0; + for(size_t i = 0; i < logo_height; i++) + logo_width = MAX(logo_width, strlen(neofetch_logo[i])); + logo_width += 4; // space between logo and info + + // Format hostname delimiter + const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr()); + char delimiter[64]; + memset(delimiter, '-', size_of_hostname); + delimiter[size_of_hostname] = '\0'; + + // Get heap info + size_t heap_total = memmgr_get_total_heap(); + size_t heap_used = heap_total - memmgr_get_free_heap(); + uint16_t heap_percent = (100 * heap_used) / heap_total; + + // Get storage info + Storage* storage = furi_record_open(RECORD_STORAGE); + uint64_t ext_total, ext_free, ext_used, ext_percent; + storage_common_fs_info(storage, "/ext", &ext_total, &ext_free); + ext_used = ext_total - ext_free; + ext_percent = (100 * ext_used) / ext_total; + ext_used /= 1024 * 1024; + ext_total /= 1024 * 1024; + furi_record_close(RECORD_STORAGE); + + // Get battery info + uint16_t charge_percent = furi_hal_power_get_pct(); + const char* charge_state; + if(furi_hal_power_is_charging()) { + if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) { + charge_state = "charging"; + } else { + charge_state = "charged"; + } + } else { + charge_state = "discharging"; + } + + // Get misc info + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + const Version* version = version_get(); + uint16_t major, minor; + furi_hal_info_get_api_version(&major, &minor); + + // Print ASCII art with info + const size_t info_height = 16; + for(size_t i = 0; i < MAX(logo_height, info_height); i++) { + printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : ""); + switch(i) { + case 0: // you@ + printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr()); + break; + case 1: // delimiter + printf(ANSI_RESET "%s", delimiter); + break; + case 2: // OS: FURI (SDK .) + printf( + "OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)", + version_get_version(version), + version_get_gitbranch(version), + version_get_version(version), + version_get_githash(version), + major, + minor); + break; + case 3: // Host: + printf( + "Host" ANSI_RESET ": %s %s", + furi_hal_version_get_model_code(), + furi_hal_version_get_device_name_ptr()); + break; + case 4: // Kernel: FreeRTOS .. + printf( + "Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d", + tskKERNEL_VERSION_MAJOR, + tskKERNEL_VERSION_MINOR, + tskKERNEL_VERSION_BUILD); + break; + case 5: // Uptime: ?h?m?s + printf( + "Uptime" ANSI_RESET ": %luh%lum%lus", + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + break; + case 6: // ST7567 128x64 @ 1 bpp in 1.4" + printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\""); + break; + case 7: // DE: GuiSrv + printf("DE" ANSI_RESET ": GuiSrv"); + break; + case 8: // Shell: CliSrv + printf("Shell" ANSI_RESET ": CliSrv"); + break; + case 9: // CPU: STM32WB55RG @ 64 MHz + printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz"); + break; + case 10: // Memory: / B (??%) + printf( + "Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent); + break; + case 11: // Disk (/ext): / MiB (??%) + printf( + "Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)", + ext_used, + ext_total, + ext_percent); + break; + case 12: // Battery: ??% () + printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state); + break; + case 13: // empty space + break; + case 14: // Colors (line 1) + for(size_t j = 30; j <= 37; j++) + printf("\e[%dm███", j); + break; + case 15: // Colors (line 2) + for(size_t j = 90; j <= 97; j++) + printf("\e[%dm███", j); + break; + default: + break; + } + printf("\r\n"); + } + printf(ANSI_RESET); +#undef NEOFETCH_COLOR +} + +void cli_command_help(Cli* cli, FuriString* args, void* context) { + UNUSED(context); printf("Commands available:"); - // Command count - const size_t commands_count = CliCommandTree_size(cli->commands); - const size_t commands_count_mid = commands_count / 2 + commands_count % 2; + // Count non-hidden commands + CliCommandTree_it_t it_count; + CliCommandTree_it(it_count, cli->commands); + size_t commands_count = 0; + while(!CliCommandTree_end_p(it_count)) { + if(!(CliCommandTree_cref(it_count)->value_ptr->flags & CliCommandFlagHidden)) + commands_count++; + CliCommandTree_next(it_count); + } - // Use 2 iterators from start and middle to show 2 columns - CliCommandTree_it_t it_left; - CliCommandTree_it(it_left, cli->commands); - CliCommandTree_it_t it_right; - CliCommandTree_it(it_right, cli->commands); - for(size_t i = 0; i < commands_count_mid; i++) - CliCommandTree_next(it_right); + // Create iterators starting at different positions + const size_t columns = 3; + const size_t commands_per_column = (commands_count / columns) + (commands_count % columns); + CliCommandTree_it_t iterators[columns]; + for(size_t c = 0; c < columns; c++) { + CliCommandTree_it(iterators[c], cli->commands); + for(size_t i = 0; i < c * commands_per_column; i++) + CliCommandTree_next(iterators[c]); + } - // Iterate throw tree - for(size_t i = 0; i < commands_count_mid; i++) { + // Print commands + for(size_t r = 0; r < commands_per_column; r++) { printf("\r\n"); - // Left Column - if(!CliCommandTree_end_p(it_left)) { - printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr)); - CliCommandTree_next(it_left); - } - // Right Column - if(!CliCommandTree_end_p(it_right)) { - printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); - CliCommandTree_next(it_right); + + for(size_t c = 0; c < columns; c++) { + if(!CliCommandTree_end_p(iterators[c])) { + const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]); + if(!(item->value_ptr->flags & CliCommandFlagHidden)) { + printf("%-30s", furi_string_get_cstr(*item->key_ptr)); + } + CliCommandTree_next(iterators[c]); + } } - }; + } if(furi_string_size(args) > 0) { cli_nl(cli); @@ -391,16 +552,18 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { int interval = 1000; args_read_int_and_trim(args, &interval); + if(interval) printf("\e[2J\e[?25l"); // Clear display, hide cursor + FuriThreadList* thread_list = furi_thread_list_alloc(); while(!cli_cmd_interrupt_received(cli)) { uint32_t tick = furi_get_tick(); furi_thread_enumerate(thread_list); - if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0 + if(interval) printf("\e[0;0f"); // Return to 0,0 uint32_t uptime = tick / furi_kernel_get_tick_frequency(); printf( - "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n", + "\rThreads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\e[0K\r\n", furi_thread_list_size(thread_list), (double)furi_thread_list_get_isr_time(thread_list), uptime / 60 / 60, @@ -408,14 +571,14 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { uptime % 60); printf( - "Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n", + "\rHeap: total %zu, free %zu, minimum %zu, max block %zu\e[0K\r\n\r\n", memmgr_get_total_heap(), memmgr_get_free_heap(), memmgr_get_minimum_free_heap(), memmgr_heap_get_max_free_block()); printf( - "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n", + "\r%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\e[0K\r\n", "AppID", "Name", "State", @@ -429,7 +592,7 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) { const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i); printf( - "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n", + "\r%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\e[0K\r\n", item->app_id, item->name, item->state, @@ -448,6 +611,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { } } furi_thread_list_free(thread_list); + + if(interval) printf("\e[?25h"); // Show cursor } void cli_command_free(Cli* cli, FuriString* args, void* context) { @@ -499,6 +664,12 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); + cli_add_command( + cli, + "neofetch", + CliCommandFlagParallelSafe | CliCommandFlagHidden, + cli_command_neofetch, + NULL); cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 744fa7151d..90746801aa 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -3,6 +3,7 @@ #include #include +#include void crypto_cli_print_usage(void) { printf("Usage:\r\n"); @@ -45,14 +46,14 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { input = furi_string_alloc(); char c; while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); furi_string_cat(input, "\r\n"); } @@ -120,14 +121,14 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { hex_input = furi_string_alloc(); char c; while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(hex_input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); } } diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 441b58da66..17bbc02a5b 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -224,7 +225,7 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { while(true) { uint8_t symbol = cli_getc(cli); - if(symbol == CliSymbolAsciiETX) { + if(symbol == CliKeyETX) { size_t write_size = read_index % buffer_size; if(write_size > 0) { From 0241d8d1e8d4f5ec3825ee5ed7c878827190ec1c Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Mon, 14 Oct 2024 18:30:50 +0300 Subject: [PATCH 140/236] Rename #2 --- applications/services/gui/canvas.c | 6 ++---- applications/services/gui/canvas.h | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 5c297b681a..47e4c7d3da 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -512,12 +512,10 @@ void canvas_draw_xbm( size_t height, const uint8_t* bitmap) { furi_check(canvas); - x += canvas->offset_x; - y += canvas->offset_y; - canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap, IconRotation0); + canvas_draw_xbm_ex(canvas, x, y, width, height, IconRotation0, bitmap); } -void canvas_draw_xbm_custom( +void canvas_draw_xbm_ex( Canvas* canvas, int32_t x, int32_t y, diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 1805312fa4..308d17fc31 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -298,7 +298,7 @@ void canvas_draw_xbm( * @param bitmap pointer to XBM bitmap data */ -void canvas_draw_xbm_custom( +void canvas_draw_xbm_ex( Canvas* canvas, int32_t x, int32_t y, From 9839db70f5f44260a497067e1cfc45e11b0bb2be Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Mon, 14 Oct 2024 18:31:48 +0300 Subject: [PATCH 141/236] Api adjustements --- targets/f18/api_symbols.csv | 3 ++- targets/f7/api_symbols.csv | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 1d9ac28698..417c9656dd 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,74.0,, +Version,+,78.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -746,6 +746,7 @@ Function,+,canvas_draw_str,void,"Canvas*, int32_t, int32_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" +Function,+,canvas_draw_xbm_ex,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c88e85fbeb..b9732de59f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.0,, +Version,+,78.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -823,7 +823,7 @@ Function,+,canvas_draw_str,void,"Canvas*, int32_t, int32_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" -Function,+,canvas_draw_xbm_custom,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" +Function,+,canvas_draw_xbm_ex,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* From 0902fd49e1cb7bd0c49bb2685985593f08861449 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:22:47 +0300 Subject: [PATCH 142/236] NFC: iso14443_4a improvements. Canvas: extended icon draw. (#3918) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Now 4a listener invokes upper level callback on Halt and FieldOff * Added new method for drawing mirrored XBM bitmaps * iso14443_4a poller logic enhanced * Function renamed accroding to review suggestions * Rename #2 * Api adjustements * Correct API bump Co-authored-by: あく --- applications/services/gui/canvas.c | 14 ++- applications/services/gui/canvas.h | 20 +++++ lib/nfc/helpers/iso14443_4_layer.c | 90 +++++++++++++++++-- lib/nfc/helpers/iso14443_4_layer.h | 4 + .../iso14443_4a/iso14443_4a_listener.c | 9 ++ .../iso14443_4a/iso14443_4a_listener.h | 1 + .../iso14443_4a/iso14443_4a_poller.h | 63 +++++++++++++ .../iso14443_4a/iso14443_4a_poller_i.c | 31 +++++++ targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 6 +- 10 files changed, 231 insertions(+), 10 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index de09305aab..47e4c7d3da 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -512,9 +512,21 @@ void canvas_draw_xbm( size_t height, const uint8_t* bitmap) { furi_check(canvas); + canvas_draw_xbm_ex(canvas, x, y, width, height, IconRotation0, bitmap); +} + +void canvas_draw_xbm_ex( + Canvas* canvas, + int32_t x, + int32_t y, + size_t width, + size_t height, + IconRotation rotation, + const uint8_t* bitmap_data) { + furi_check(canvas); x += canvas->offset_x; y += canvas->offset_y; - canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap, IconRotation0); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, rotation); } void canvas_draw_glyph(Canvas* canvas, int32_t x, int32_t y, uint16_t ch) { diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 9554a200e3..308d17fc31 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -287,6 +287,26 @@ void canvas_draw_xbm( size_t height, const uint8_t* bitmap); +/** Draw rotated XBM bitmap + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param[in] width bitmap width + * @param[in] height bitmap height + * @param[in] rotation bitmap rotation + * @param bitmap pointer to XBM bitmap data + */ + +void canvas_draw_xbm_ex( + Canvas* canvas, + int32_t x, + int32_t y, + size_t width, + size_t height, + IconRotation rotation, + const uint8_t* bitmap_data); + /** Draw dot at x,y * * @param canvas Canvas instance diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 3e8c6e83a3..2095499380 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -2,10 +2,42 @@ #include -#define ISO14443_4_BLOCK_PCB (1U << 1) -#define ISO14443_4_BLOCK_PCB_I (0U) -#define ISO14443_4_BLOCK_PCB_R (5U << 5) -#define ISO14443_4_BLOCK_PCB_S (3U << 6) +#define ISO14443_4_BLOCK_PCB (1U << 1) +#define ISO14443_4_BLOCK_PCB_MASK (0x03) + +#define ISO14443_4_BLOCK_PCB_I (0U) +#define ISO14443_4_BLOCK_PCB_I_NAD_OFFSET (2) +#define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_I_NAD_MASK (1U << ISO14443_4_BLOCK_PCB_I_NAD_OFFSET) +#define ISO14443_4_BLOCK_PCB_I_CID_MASK (1U << ISO14443_4_BLOCK_PCB_I_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_I_CHAIN_MASK (1U << ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET) + +#define ISO14443_4_BLOCK_PCB_R_MASK (5U << 5) +#define ISO14443_4_BLOCK_PCB_R_NACK_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_R_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_R_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_R_NACK_MASK (1U << ISO14443_4_BLOCK_PCB_R_NACK_OFFSET) + +#define ISO14443_4_BLOCK_PCB_S_MASK (3U << 6) +#define ISO14443_4_BLOCK_PCB_S_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) + +#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & mask) == mask) + +#define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) + +#define ISO14443_4_BLOCK_PCB_IS_S_BLOCK(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_S_MASK) + +#define ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_I_CHAIN_MASK) + +#define ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_NACK_MASK) struct Iso14443_4Layer { uint8_t pcb; @@ -31,9 +63,31 @@ void iso14443_4_layer_free(Iso14443_4Layer* instance) { void iso14443_4_layer_reset(Iso14443_4Layer* instance) { furi_assert(instance); + instance->pcb_prev = 0; instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB; } +void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present) { + uint8_t block_pcb = instance->pcb & ISO14443_4_BLOCK_PCB_MASK; + instance->pcb = ISO14443_4_BLOCK_PCB_I | (chaining << ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET) | + (CID_present << ISO14443_4_BLOCK_PCB_I_CID_OFFSET) | block_pcb; +} + +void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present) { + furi_assert(instance); + uint8_t block_pcb = instance->pcb & ISO14443_4_BLOCK_PCB_MASK; + instance->pcb = ISO14443_4_BLOCK_PCB_R_MASK | + (!acknowledged << ISO14443_4_BLOCK_PCB_R_NACK_OFFSET) | + (CID_present << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) | block_pcb; +} + +void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present) { + furi_assert(instance); + uint8_t des_wtx = !deselect ? (ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK) : 0; + instance->pcb = ISO14443_4_BLOCK_PCB_S_MASK | des_wtx | + (CID_present << ISO14443_4_BLOCK_PCB_S_CID_OFFSET) | ISO14443_4_BLOCK_PCB; +} + void iso14443_4_layer_encode_block( Iso14443_4Layer* instance, const BitBuffer* input_data, @@ -46,6 +100,11 @@ void iso14443_4_layer_encode_block( iso14443_4_layer_update_pcb(instance); } +static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_data) { + const uint8_t* data = bit_buffer_get_data(block_data); + return data[0]; +} + bool iso14443_4_layer_decode_block( Iso14443_4Layer* instance, BitBuffer* output_data, @@ -55,9 +114,26 @@ bool iso14443_4_layer_decode_block( bool ret = false; do { - if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; - bit_buffer_copy_right(output_data, block_data, 1); - ret = true; + if(ISO14443_4_BLOCK_PCB_IS_R_BLOCK(instance->pcb_prev)) { + const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); + ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && + (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); + instance->pcb &= ISO14443_4_BLOCK_PCB_MASK; + iso14443_4_layer_update_pcb(instance); + } else if(ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(instance->pcb_prev)) { + const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); + ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && + (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); + instance->pcb &= ~(ISO14443_4_BLOCK_PCB_I_CHAIN_MASK); + } else if(ISO14443_4_BLOCK_PCB_IS_S_BLOCK(instance->pcb_prev)) { + ret = bit_buffer_starts_with_byte(block_data, instance->pcb_prev); + if(bit_buffer_get_size_bytes(block_data) > 1) + bit_buffer_copy_right(output_data, block_data, 1); + } else { + if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; + bit_buffer_copy_right(output_data, block_data, 1); + ret = true; + } } while(false); return ret; diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h index 437c2e8a65..67a7f37fe2 100644 --- a/lib/nfc/helpers/iso14443_4_layer.h +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -14,6 +14,10 @@ void iso14443_4_layer_free(Iso14443_4Layer* instance); void iso14443_4_layer_reset(Iso14443_4Layer* instance); +void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present); +void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present); +void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present); + void iso14443_4_layer_encode_block( Iso14443_4Layer* instance, const BitBuffer* input_data, diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 95612bf54d..32cc8f1987 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -84,6 +84,15 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted || iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) { instance->state = Iso14443_4aListenerStateIdle; + + instance->iso14443_4a_event.type = iso14443_3a_event->type == + Iso14443_3aListenerEventTypeHalted ? + Iso14443_4aListenerEventTypeHalted : + Iso14443_4aListenerEventTypeFieldOff; + + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } command = NfcCommandContinue; } diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h index ba649847b2..04f0b197af 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h @@ -12,6 +12,7 @@ typedef struct Iso14443_4aListener Iso14443_4aListener; typedef enum { Iso14443_4aListenerEventTypeHalted, + Iso14443_4aListenerEventTypeFieldOff, Iso14443_4aListenerEventTypeReceivedData, } Iso14443_4aListenerEventType; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index fef565e514..80a4c15400 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -56,6 +56,69 @@ Iso14443_4aError iso14443_4a_poller_send_block( const BitBuffer* tx_buffer, BitBuffer* rx_buffer); +/** + * @brief Transmit and receive Iso14443_4a chained block in poller mode. Also it + * automatically modifies PCB packet byte with appropriate bits then resets them back + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer. The fwt parameter is calculated during activation procedure. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_chain_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Transmit Iso14443_4a R-block in poller mode. This block never contains + * data, but can contain CID and NAD, therefore in tx_buffer only two bytes can be added. + * The first one will represent CID, the second one will represent NAD. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with R-block repsonse + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] acknowledged Sets appropriate bit in PCB byte. True - ACK, false - NAK + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_receive_ready_block( + Iso14443_4aPoller* instance, + bool acknowledged, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Transmit Iso14443_4a S-block in poller mode. S-block used to exchange control + * information between the card and the reader. Two different types of S-blocks + * are defined: + * - Waiting time extension containing a 1 byte long INF field and (deselect = false) + * - DESELECT containing no INF field (deselect = true) + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with R-block repsonse + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] deselect Sets appropriate bit in PCB byte. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_supervisory_block( + Iso14443_4aPoller* instance, + bool deselect, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + /** * @brief Send HALT command to the card. * diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index a9453b0390..427973f4af 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -114,3 +114,34 @@ Iso14443_4aError iso14443_4a_poller_send_block( return error; } + +Iso14443_4aError iso14443_4a_poller_send_chain_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + iso14443_4_layer_set_i_block(instance->iso14443_4_layer, true, false); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} + +Iso14443_4aError iso14443_4a_poller_send_receive_ready_block( + Iso14443_4aPoller* instance, + bool acknowledged, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + bool CID_present = bit_buffer_get_size_bytes(tx_buffer) != 0; + iso14443_4_layer_set_r_block(instance->iso14443_4_layer, acknowledged, CID_present); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} + +Iso14443_4aError iso14443_4a_poller_send_supervisory_block( + Iso14443_4aPoller* instance, + bool deselect, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + bool CID_present = bit_buffer_get_size_bytes(tx_buffer) != 0; + iso14443_4_layer_set_s_block(instance->iso14443_4_layer, deselect, CID_present); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2b04e3e403..9b2160d53d 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.0,, +Version,+,77.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -746,6 +746,7 @@ Function,+,canvas_draw_str,void,"Canvas*, int32_t, int32_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" +Function,+,canvas_draw_xbm_ex,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 71d9213d85..b5dab0f281 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.0,, +Version,+,77.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -823,6 +823,7 @@ Function,+,canvas_draw_str,void,"Canvas*, int32_t, int32_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" +Function,+,canvas_draw_xbm_ex,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* @@ -2125,6 +2126,9 @@ Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller* Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*" Function,+,iso14443_4a_poller_send_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_chain_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_receive_ready_block,Iso14443_4aError,"Iso14443_4aPoller*, _Bool, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_supervisory_block,Iso14443_4aError,"Iso14443_4aPoller*, _Bool, const BitBuffer*, BitBuffer*" Function,+,iso14443_4a_reset,void,Iso14443_4aData* Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*" Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t" From 57c438d91a87d53512fc0925187526e72d047889 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 14 Oct 2024 19:26:17 +0100 Subject: [PATCH 143/236] heap: increased size (#3924) * reduced reserved memory size for system stack; added temporary markup to monitor usage * fbt: relink elf file on linker script change; removed debug memory fill * Make PVS Happy * Make doxygen happy Co-authored-by: Aleksandr Kutuzov --- applications/services/gui/canvas.h | 15 +++++++-------- firmware.scons | 1 + lib/nfc/helpers/iso14443_4_layer.c | 2 +- .../protocols/iso14443_4a/iso14443_4a_listener.c | 5 +---- targets/f7/stm32wb55xx_flash.ld | 2 +- targets/f7/stm32wb55xx_ram_fw.ld | 2 +- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 308d17fc31..561bc2ab82 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -289,15 +289,14 @@ void canvas_draw_xbm( /** Draw rotated XBM bitmap * - * @param canvas Canvas instance - * @param x x coordinate - * @param y y coordinate - * @param[in] width bitmap width - * @param[in] height bitmap height - * @param[in] rotation bitmap rotation - * @param bitmap pointer to XBM bitmap data + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param[in] width bitmap width + * @param[in] height bitmap height + * @param[in] rotation bitmap rotation + * @param bitmap_data pointer to XBM bitmap data */ - void canvas_draw_xbm_ex( Canvas* canvas, int32_t x, diff --git a/firmware.scons b/firmware.scons index 62b1184ebd..4c5e058739 100644 --- a/firmware.scons +++ b/firmware.scons @@ -216,6 +216,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( sources, LIBS=fwenv["TARGET_CFG"].linker_dependencies, ) +Depends(fwelf, fwenv["LINKER_SCRIPT_PATH"]) # Firmware depends on everything child builders returned # Depends(fwelf, lib_targets) diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 2095499380..4c5dcd6a41 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -25,7 +25,7 @@ #define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) #define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) -#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & mask) == mask) +#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & (mask)) == (mask)) #define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 32cc8f1987..2519fb90c2 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -65,10 +65,8 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(instance->state == Iso14443_4aListenerStateIdle) { if(bit_buffer_get_size_bytes(rx_buffer) == 2 && bit_buffer_get_byte(rx_buffer, 0) == ISO14443_4A_CMD_READ_ATS) { - if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) != + if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) == Iso14443_4aErrorNone) { - command = NfcCommandContinue; - } else { instance->state = Iso14443_4aListenerStateActive; } } @@ -93,7 +91,6 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(instance->callback) { command = instance->callback(instance->generic_event, instance->context); } - command = NfcCommandContinue; } return command; diff --git a/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld index 3fb7896459..524da6fc32 100644 --- a/targets/f7/stm32wb55xx_flash.ld +++ b/targets/f7/stm32wb55xx_flash.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x1000; /* required amount of stack */ +_stack_size = 0x200; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K diff --git a/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld index cae30b6e99..f0e8ad678c 100644 --- a/targets/f7/stm32wb55xx_ram_fw.ld +++ b/targets/f7/stm32wb55xx_ram_fw.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x1000; /* required amount of stack */ +_stack_size = 0x200; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K From 8a95cb8d6b84c7ddc7e68e7e4aac831e54b5ab61 Mon Sep 17 00:00:00 2001 From: porta Date: Mon, 14 Oct 2024 21:42:11 +0300 Subject: [PATCH 144/236] [FL-3893] JS modules (#3841) * feat: backport js_gpio from unleashed * feat: backport js_keyboard, TextInputModel::minimum_length from unleashed * fix: api version inconsistency * style: js_gpio * build: fix submodule ._ . * refactor: js_gpio * docs: type declarations for gpio * feat: gpio interrupts * fix: js_gpio freeing, resetting and minor stylistic changes * style: js_gpio * style: mlib array, fixme's * feat: js_gpio adc * feat: js_event_loop * docs: js_event_loop * feat: js_event_loop subscription cancellation * feat: js_event_loop + js_gpio integration * fix: js_event_loop memory leak * feat: stop event loop on back button * test: js: basic, math, event_loop * feat: js_event_loop queue * feat: js linkage to previously loaded plugins * build: fix ci errors * feat: js module ordered teardown * feat: js_gui_defer_free * feat: basic hourglass view * style: JS ASS (Argument Schema for Scripts) * fix: js_event_loop mem leaks and lifetime problems * fix: crashing test and pvs false positives * feat: mjs custom obj destructors, gui submenu view * refactor: yank js_gui_defer_free (yuck) * refactor: maybe_unsubscribe * empty_screen, docs, typing fix-ups * docs: navigation event & demo * feat: submenu setHeader * feat: text_input * feat: text_box * docs: text_box availability * ci: silence irrelevant pvs low priority warning * style: use furistring * style: _get_at -> _safe_get * fix: built-in module name assignment * feat: js_dialog; refactor, optimize: js_gui * docs: js_gui * ci: silence pvs warning: Memory allocation is infallible * style: fix storage spelling * feat: foreign pointer signature checks * feat: js_storage * docs: js_storage * fix: my unit test was breaking other tests ;_; * ci: fix ci? * Make doxygen happy * docs: flipper, math, notification, global * style: review suggestions * style: review fixups * fix: badusb demo script * docs: badusb * ci: add nofl * ci: make linter happy * Bump api version Co-authored-by: Aleksandr Kutuzov --- applications/debug/unit_tests/application.fam | 8 + .../resources/unit_tests/js/basic.js | 4 + .../resources/unit_tests/js/event_loop.js | 30 ++ .../resources/unit_tests/js/math.js | 34 ++ .../resources/unit_tests/js/storage.js | 136 ++++++ .../debug/unit_tests/tests/js/js_test.c | 88 ++++ applications/debug/unit_tests/tests/minunit.h | 5 +- .../debug/unit_tests/unit_test_api_table_i.h | 14 +- .../services/gui/modules/text_input.c | 12 +- .../services/gui/modules/text_input.h | 7 + applications/services/gui/view_dispatcher.c | 22 +- applications/services/gui/view_dispatcher.h | 13 + applications/services/gui/view_dispatcher_i.h | 1 + applications/services/storage/storage.h | 4 +- applications/system/js_app/application.fam | 79 ++- .../examples/apps/Scripts/badusb_demo.js | 71 ++- .../js_app/examples/apps/Scripts/delay.js | 2 +- .../js_app/examples/apps/Scripts/dialog.js | 19 - .../examples/apps/Scripts/event_loop.js | 25 + .../js_app/examples/apps/Scripts/gpio.js | 57 +++ .../js_app/examples/apps/Scripts/gui.js | 77 +++ .../js_app/examples/apps/Scripts/load.js | 2 +- .../js_app/examples/apps/Scripts/load_api.js | 2 +- .../js_app/examples/apps/Scripts/math.js | 45 -- .../js_app/examples/apps/Scripts/notify.js | 2 +- .../js_app/examples/apps/Scripts/submenu.js | 11 - .../js_app/examples/apps/Scripts/textbox.js | 30 -- .../js_app/examples/apps/Scripts/uart_echo.js | 2 +- applications/system/js_app/js_app.c | 2 +- applications/system/js_app/js_modules.c | 114 +++-- applications/system/js_app/js_modules.h | 264 +++++++++- applications/system/js_app/js_thread.c | 17 +- applications/system/js_app/js_thread.h | 8 + .../system/js_app/modules/js_badusb.c | 8 +- .../system/js_app/modules/js_dialog.c | 154 ------ .../modules/js_event_loop/js_event_loop.c | 451 ++++++++++++++++++ .../modules/js_event_loop/js_event_loop.h | 104 ++++ .../js_event_loop/js_event_loop_api_table.cpp | 16 + .../js_event_loop/js_event_loop_api_table_i.h | 4 + .../system/js_app/modules/js_flipper.c | 3 +- .../system/js_app/modules/js_flipper.h | 2 +- applications/system/js_app/modules/js_gpio.c | 345 ++++++++++++++ .../system/js_app/modules/js_gui/dialog.c | 129 +++++ .../js_app/modules/js_gui/empty_screen.c | 12 + .../system/js_app/modules/js_gui/js_gui.c | 348 ++++++++++++++ .../system/js_app/modules/js_gui/js_gui.h | 116 +++++ .../modules/js_gui/js_gui_api_table.cpp | 16 + .../modules/js_gui/js_gui_api_table_i.h | 4 + .../system/js_app/modules/js_gui/loading.c | 12 + .../system/js_app/modules/js_gui/submenu.c | 87 ++++ .../system/js_app/modules/js_gui/text_box.c | 78 +++ .../system/js_app/modules/js_gui/text_input.c | 120 +++++ applications/system/js_app/modules/js_math.c | 4 +- .../system/js_app/modules/js_notification.c | 4 +- .../system/js_app/modules/js_serial.c | 4 +- .../system/js_app/modules/js_storage.c | 383 +++++++++++++++ .../system/js_app/modules/js_submenu.c | 147 ------ applications/system/js_app/modules/js_tests.c | 104 ++++ applications/system/js_app/modules/js_tests.h | 5 + .../system/js_app/modules/js_textbox.c | 219 --------- .../js_app/plugin_api/app_api_table_i.h | 3 +- .../system/js_app/plugin_api/js_plugin_api.h | 4 + .../system/js_app/types/badusb/index.d.ts | 81 ++++ .../system/js_app/types/event_loop/index.d.ts | 70 +++ .../system/js_app/types/flipper/index.d.ts | 14 + applications/system/js_app/types/global.d.ts | 178 +++++++ .../system/js_app/types/gpio/index.d.ts | 45 ++ .../system/js_app/types/gui/dialog.d.ts | 16 + .../system/js_app/types/gui/empty_screen.d.ts | 7 + .../system/js_app/types/gui/index.d.ts | 41 ++ .../system/js_app/types/gui/loading.d.ts | 7 + .../system/js_app/types/gui/submenu.d.ts | 13 + .../system/js_app/types/gui/text_box.d.ts | 14 + .../system/js_app/types/gui/text_input.d.ts | 14 + .../system/js_app/types/math/index.d.ts | 24 + .../js_app/types/notification/index.d.ts | 20 + .../system/js_app/types/serial/index.d.ts | 77 +++ .../system/js_app/types/storage/index.d.ts | 237 +++++++++ .../system/js_app/types/tests/index.d.ts | 8 + documentation/doxygen/Doxyfile.cfg | 2 +- documentation/doxygen/js.dox | 22 +- documentation/images/dialog.png | Bin 0 -> 1377 bytes documentation/images/empty.png | Bin 0 -> 1005 bytes documentation/images/loading.png | Bin 0 -> 1173 bytes documentation/images/submenu.png | Bin 0 -> 1774 bytes documentation/images/text_box.png | Bin 0 -> 2350 bytes documentation/images/text_input.png | Bin 0 -> 2044 bytes documentation/js/js_builtin.md | 12 +- documentation/js/js_dialog.md | 49 -- documentation/js/js_event_loop.md | 144 ++++++ documentation/js/js_gpio.md | 77 +++ documentation/js/js_gui.md | 161 +++++++ documentation/js/js_gui__dialog.md | 53 ++ documentation/js/js_gui__empty_screen.md | 22 + documentation/js/js_gui__loading.md | 23 + documentation/js/js_gui__submenu.md | 37 ++ documentation/js/js_gui__text_box.md | 25 + documentation/js/js_gui__text_input.md | 44 ++ documentation/js/js_submenu.md | 48 -- documentation/js/js_textbox.md | 69 --- fbt_options.py | 1 + furi/core/event_loop.c | 12 + furi/core/event_loop.h | 17 + lib/mjs/mjs_core.c | 1 + lib/mjs/mjs_object.c | 17 +- lib/mjs/mjs_object.h | 5 + lib/mjs/mjs_object_public.h | 8 + targets/f18/api_symbols.csv | 7 +- targets/f18/furi_hal/furi_hal_resources.c | 16 + targets/f18/furi_hal/furi_hal_resources.h | 20 + targets/f7/api_symbols.csv | 7 +- targets/f7/furi_hal/furi_hal_resources.c | 16 + targets/f7/furi_hal/furi_hal_resources.h | 20 + tsconfig.json | 15 + 114 files changed, 4978 insertions(+), 931 deletions(-) create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/basic.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/event_loop.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/math.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/storage.js create mode 100644 applications/debug/unit_tests/tests/js/js_test.c delete mode 100644 applications/system/js_app/examples/apps/Scripts/dialog.js create mode 100644 applications/system/js_app/examples/apps/Scripts/event_loop.js create mode 100644 applications/system/js_app/examples/apps/Scripts/gpio.js create mode 100644 applications/system/js_app/examples/apps/Scripts/gui.js delete mode 100644 applications/system/js_app/examples/apps/Scripts/submenu.js delete mode 100644 applications/system/js_app/examples/apps/Scripts/textbox.js delete mode 100644 applications/system/js_app/modules/js_dialog.c create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop.c create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop.h create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop_api_table.cpp create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h create mode 100644 applications/system/js_app/modules/js_gpio.c create mode 100644 applications/system/js_app/modules/js_gui/dialog.c create mode 100644 applications/system/js_app/modules/js_gui/empty_screen.c create mode 100644 applications/system/js_app/modules/js_gui/js_gui.c create mode 100644 applications/system/js_app/modules/js_gui/js_gui.h create mode 100644 applications/system/js_app/modules/js_gui/js_gui_api_table.cpp create mode 100644 applications/system/js_app/modules/js_gui/js_gui_api_table_i.h create mode 100644 applications/system/js_app/modules/js_gui/loading.c create mode 100644 applications/system/js_app/modules/js_gui/submenu.c create mode 100644 applications/system/js_app/modules/js_gui/text_box.c create mode 100644 applications/system/js_app/modules/js_gui/text_input.c create mode 100644 applications/system/js_app/modules/js_storage.c delete mode 100644 applications/system/js_app/modules/js_submenu.c create mode 100644 applications/system/js_app/modules/js_tests.c create mode 100644 applications/system/js_app/modules/js_tests.h delete mode 100644 applications/system/js_app/modules/js_textbox.c create mode 100644 applications/system/js_app/types/badusb/index.d.ts create mode 100644 applications/system/js_app/types/event_loop/index.d.ts create mode 100644 applications/system/js_app/types/flipper/index.d.ts create mode 100644 applications/system/js_app/types/global.d.ts create mode 100644 applications/system/js_app/types/gpio/index.d.ts create mode 100644 applications/system/js_app/types/gui/dialog.d.ts create mode 100644 applications/system/js_app/types/gui/empty_screen.d.ts create mode 100644 applications/system/js_app/types/gui/index.d.ts create mode 100644 applications/system/js_app/types/gui/loading.d.ts create mode 100644 applications/system/js_app/types/gui/submenu.d.ts create mode 100644 applications/system/js_app/types/gui/text_box.d.ts create mode 100644 applications/system/js_app/types/gui/text_input.d.ts create mode 100644 applications/system/js_app/types/math/index.d.ts create mode 100644 applications/system/js_app/types/notification/index.d.ts create mode 100644 applications/system/js_app/types/serial/index.d.ts create mode 100644 applications/system/js_app/types/storage/index.d.ts create mode 100644 applications/system/js_app/types/tests/index.d.ts create mode 100644 documentation/images/dialog.png create mode 100644 documentation/images/empty.png create mode 100644 documentation/images/loading.png create mode 100644 documentation/images/submenu.png create mode 100644 documentation/images/text_box.png create mode 100644 documentation/images/text_input.png delete mode 100644 documentation/js/js_dialog.md create mode 100644 documentation/js/js_event_loop.md create mode 100644 documentation/js/js_gpio.md create mode 100644 documentation/js/js_gui.md create mode 100644 documentation/js/js_gui__dialog.md create mode 100644 documentation/js/js_gui__empty_screen.md create mode 100644 documentation/js/js_gui__loading.md create mode 100644 documentation/js/js_gui__submenu.md create mode 100644 documentation/js/js_gui__text_box.md create mode 100644 documentation/js/js_gui__text_input.md delete mode 100644 documentation/js/js_submenu.md delete mode 100644 documentation/js/js_textbox.md create mode 100644 tsconfig.json diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index c87305847a..dec3283e4e 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -221,6 +221,14 @@ App( requires=["unit_tests"], ) +App( + appid="test_js", + sources=["tests/common/*.c", "tests/js/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests", "js_app"], +) + App( appid="test_strint", sources=["tests/common/*.c", "tests/strint/*.c"], diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js new file mode 100644 index 0000000000..0927595a2c --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -0,0 +1,4 @@ +let tests = require("tests"); + +tests.assert_eq(1337, 1337); +tests.assert_eq("hello", "hello"); diff --git a/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js b/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js new file mode 100644 index 0000000000..0437b82932 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js @@ -0,0 +1,30 @@ +let tests = require("tests"); +let event_loop = require("event_loop"); + +let ext = { + i: 0, + received: false, +}; + +let queue = event_loop.queue(16); + +event_loop.subscribe(queue.input, function (_, item, tests, ext) { + tests.assert_eq(123, item); + ext.received = true; +}, tests, ext); + +event_loop.subscribe(event_loop.timer("periodic", 1), function (_, _item, queue, counter, ext) { + ext.i++; + queue.send(123); + if (counter === 10) + event_loop.stop(); + return [queue, counter + 1, ext]; +}, queue, 1, ext); + +event_loop.subscribe(event_loop.timer("oneshot", 1000), function (_, _item, tests) { + tests.fail("event loop was not stopped"); +}, tests); + +event_loop.run(); +tests.assert_eq(10, ext.i); +tests.assert_eq(true, ext.received); diff --git a/applications/debug/unit_tests/resources/unit_tests/js/math.js b/applications/debug/unit_tests/resources/unit_tests/js/math.js new file mode 100644 index 0000000000..ea8d80f914 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/math.js @@ -0,0 +1,34 @@ +let tests = require("tests"); +let math = require("math"); + +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +// basics +tests.assert_float_close(5, math.abs(-5), math.EPSILON); +tests.assert_float_close(0.5, math.abs(-0.5), math.EPSILON); +tests.assert_float_close(5, math.abs(5), math.EPSILON); +tests.assert_float_close(0.5, math.abs(0.5), math.EPSILON); +tests.assert_float_close(3, math.cbrt(27), math.EPSILON); +tests.assert_float_close(6, math.ceil(5.3), math.EPSILON); +tests.assert_float_close(31, math.clz32(1), math.EPSILON); +tests.assert_float_close(5, math.floor(5.7), math.EPSILON); +tests.assert_float_close(5, math.max(3, 5), math.EPSILON); +tests.assert_float_close(3, math.min(3, 5), math.EPSILON); +tests.assert_float_close(-1, math.sign(-5), math.EPSILON); +tests.assert_float_close(5, math.trunc(5.7), math.EPSILON); + +// trig +tests.assert_float_close(1.0471975511965976, math.acos(0.5), math.EPSILON); +tests.assert_float_close(1.3169578969248166, math.acosh(2), math.EPSILON); +tests.assert_float_close(0.5235987755982988, math.asin(0.5), math.EPSILON); +tests.assert_float_close(1.4436354751788103, math.asinh(2), math.EPSILON); +tests.assert_float_close(0.7853981633974483, math.atan(1), math.EPSILON); +tests.assert_float_close(0.7853981633974483, math.atan2(1, 1), math.EPSILON); +tests.assert_float_close(0.5493061443340549, math.atanh(0.5), math.EPSILON); +tests.assert_float_close(-1, math.cos(math.PI), math.EPSILON * 18); // Error 3.77475828372553223744e-15 +tests.assert_float_close(1, math.sin(math.PI / 2), math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +// powers +tests.assert_float_close(5, math.sqrt(25), math.EPSILON); +tests.assert_float_close(8, math.pow(2, 3), math.EPSILON); +tests.assert_float_close(2.718281828459045, math.exp(1), math.EPSILON * 2); // Error 4.44089209850062616169e-16 diff --git a/applications/debug/unit_tests/resources/unit_tests/js/storage.js b/applications/debug/unit_tests/resources/unit_tests/js/storage.js new file mode 100644 index 0000000000..872b29cfbc --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/storage.js @@ -0,0 +1,136 @@ +let storage = require("storage"); +let tests = require("tests"); + +let baseDir = "/ext/.tmp/unit_tests"; + +tests.assert_eq(true, storage.rmrf(baseDir)); +tests.assert_eq(true, storage.makeDirectory(baseDir)); + +// write +let file = storage.openFile(baseDir + "/helloworld", "w", "create_always"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.write("Hello, World!")); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// read +file = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.size()); +tests.assert_eq("Hello, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// seek +file = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.size()); +tests.assert_eq("Hello, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.seekAbsolute(1)); +tests.assert_eq(true, file.seekRelative(2)); +tests.assert_eq(3, file.tell()); +tests.assert_eq(false, file.eof()); +tests.assert_eq("lo, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.eof()); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// byte-level copy +let src = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +let dst = storage.openFile(baseDir + "/helloworld2", "rw", "create_always"); +tests.assert_eq(true, !!src); +tests.assert_eq(true, src.isOpen()); +tests.assert_eq(true, !!dst); +tests.assert_eq(true, dst.isOpen()); +tests.assert_eq(true, src.copyTo(dst, 10)); +tests.assert_eq(true, dst.seekAbsolute(0)); +tests.assert_eq("Hello, Wor", dst.read("ascii", 128)); +tests.assert_eq(true, src.copyTo(dst, 3)); +tests.assert_eq(true, dst.seekAbsolute(0)); +tests.assert_eq("Hello, World!", dst.read("ascii", 128)); +tests.assert_eq(true, src.eof()); +tests.assert_eq(true, src.close()); +tests.assert_eq(false, src.isOpen()); +tests.assert_eq(true, dst.eof()); +tests.assert_eq(true, dst.close()); +tests.assert_eq(false, dst.isOpen()); + +// truncate +tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld2")); +file = storage.openFile(baseDir + "/helloworld2", "w", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.seekAbsolute(5)); +tests.assert_eq(true, file.truncate()); +tests.assert_eq(true, file.close()); +file = storage.openFile(baseDir + "/helloworld2", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq("Hello", file.read("ascii", 128)); +tests.assert_eq(true, file.close()); + +// existence +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld2")); +tests.assert_eq(false, storage.fileExists(baseDir + "/sus_amogus_123")); +tests.assert_eq(false, storage.directoryExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir)); +tests.assert_eq(true, storage.directoryExists(baseDir)); +tests.assert_eq(true, storage.fileOrDirExists(baseDir)); +tests.assert_eq(true, storage.remove(baseDir + "/helloworld2")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld2")); + +// stat +let stat = storage.stat(baseDir + "/helloworld"); +tests.assert_eq(true, !!stat); +tests.assert_eq(baseDir + "/helloworld", stat.path); +tests.assert_eq(false, stat.isDirectory); +tests.assert_eq(13, stat.size); + +// rename +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.rename(baseDir + "/helloworld", baseDir + "/helloworld123")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.rename(baseDir + "/helloworld123", baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); + +// copy +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld123")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123")); + +// next avail +tests.assert_eq("helloworld1", storage.nextAvailableFilename(baseDir, "helloworld", "", 20)); + +// fs info +let fsInfo = storage.fsInfo("/ext"); +tests.assert_eq(true, !!fsInfo); +tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); // idk \(-_-)/ +fsInfo = storage.fsInfo("/int"); +tests.assert_eq(true, !!fsInfo); +tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); + +// path operations +tests.assert_eq(true, storage.arePathsEqual("/ext/test", "/ext/Test")); +tests.assert_eq(false, storage.arePathsEqual("/ext/test", "/ext/Testttt")); +tests.assert_eq(true, storage.isSubpathOf("/ext/test", "/ext/test/sub")); +tests.assert_eq(false, storage.isSubpathOf("/ext/test/sub", "/ext/test")); + +// dir +let entries = storage.readDirectory(baseDir); +tests.assert_eq(true, !!entries); +// FIXME: (-nofl) this test suite assumes that files are listed by +// `readDirectory` in the exact order that they were created, which is not +// something that is actually guaranteed. +// Possible solution: sort and compare the array. +tests.assert_eq("helloworld", entries[0].path); +tests.assert_eq("helloworld123", entries[1].path); + +tests.assert_eq(true, storage.rmrf(baseDir)); +tests.assert_eq(true, storage.makeDirectory(baseDir)); diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c new file mode 100644 index 0000000000..af590e8995 --- /dev/null +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -0,0 +1,88 @@ +#include "../test.h" // IWYU pragma: keep + +#include +#include +#include + +#include +#include + +#include + +#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js") + +typedef enum { + JsTestsFinished = 1, + JsTestsError = 2, +} JsTestFlag; + +typedef struct { + FuriEventFlag* event_flags; + FuriString* error_string; +} JsTestCallbackContext; + +static void js_test_callback(JsThreadEvent event, const char* msg, void* param) { + JsTestCallbackContext* context = param; + if(event == JsThreadEventPrint) { + FURI_LOG_I("js_test", "%s", msg); + } else if(event == JsThreadEventError || event == JsThreadEventErrorTrace) { + context->error_string = furi_string_alloc_set_str(msg); + furi_event_flag_set(context->event_flags, JsTestsFinished | JsTestsError); + } else if(event == JsThreadEventDone) { + furi_event_flag_set(context->event_flags, JsTestsFinished); + } +} + +static void js_test_run(const char* script_path) { + JsTestCallbackContext* context = malloc(sizeof(JsTestCallbackContext)); + context->event_flags = furi_event_flag_alloc(); + + JsThread* thread = js_thread_run(script_path, js_test_callback, context); + uint32_t flags = furi_event_flag_wait( + context->event_flags, JsTestsFinished, FuriFlagWaitAny, FuriWaitForever); + if(flags & FuriFlagError) { + // getting the flags themselves should not fail + furi_crash(); + } + + FuriString* error_string = context->error_string; + + js_thread_stop(thread); + furi_event_flag_free(context->event_flags); + free(context); + + if(flags & JsTestsError) { + // memory leak: not freeing the FuriString if the tests fail, + // because mu_fail executes a return + // + // who cares tho? + mu_fail(furi_string_get_cstr(error_string)); + } +} + +MU_TEST(js_test_basic) { + js_test_run(JS_SCRIPT_PATH("basic")); +} +MU_TEST(js_test_math) { + js_test_run(JS_SCRIPT_PATH("math")); +} +MU_TEST(js_test_event_loop) { + js_test_run(JS_SCRIPT_PATH("event_loop")); +} +MU_TEST(js_test_storage) { + js_test_run(JS_SCRIPT_PATH("storage")); +} + +MU_TEST_SUITE(test_js) { + MU_RUN_TEST(js_test_basic); + MU_RUN_TEST(js_test_math); + MU_RUN_TEST(js_test_event_loop); + MU_RUN_TEST(js_test_storage); +} + +int run_minunit_test_js(void) { + MU_RUN_SUITE(test_js); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_js) diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index 9310cfc9c9..9ca3bb403d 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -31,7 +31,7 @@ extern "C" { #include #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf -#define __func__ __FUNCTION__ +#define __func__ __FUNCTION__ //-V1059 #endif #elif defined(__unix__) || defined(__unix) || defined(unix) || \ @@ -56,7 +56,7 @@ extern "C" { #endif #if __GNUC__ >= 5 && !defined(__STDC_VERSION__) -#define __func__ __extension__ __FUNCTION__ +#define __func__ __extension__ __FUNCTION__ //-V1059 #endif #else @@ -102,6 +102,7 @@ void minunit_printf_warning(const char* format, ...); MU__SAFE_BLOCK(minunit_setup = setup_fun; minunit_teardown = teardown_fun;) /* Test runner */ +//-V:MU_RUN_TEST:550 #define MU_RUN_TEST(test) \ MU__SAFE_BLOCK( \ if(minunit_real_timer == 0 && minunit_proc_timer == 0) { \ diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 50524e5b7d..10b0890225 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -7,7 +7,7 @@ #include #include -#include +#include static constexpr auto unit_tests_api_table = sort(create_array_t( API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), @@ -33,13 +33,9 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( xQueueGenericSend, BaseType_t, (QueueHandle_t, const void* const, TickType_t, const BaseType_t)), - API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)), - API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)), API_METHOD( - furi_event_loop_subscribe_message_queue, - void, - (FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)), - API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)), - API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)), - API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)), + js_thread_run, + JsThread*, + (const char* script_path, JsThreadCallback callback, void* context)), + API_METHOD(js_thread_stop, void, (JsThread * worker)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index dc1c9c8c1c..753f0b3b8a 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -18,6 +18,7 @@ typedef struct { const char* header; char* text_buffer; size_t text_buffer_size; + size_t minimum_length; bool clear_default_text; TextInputCallback callback; @@ -321,7 +322,7 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b model->text_buffer, model->validator_text, model->validator_callback_context))) { model->validator_message_visible = true; furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4); - } else if(model->callback != 0 && text_length > 0) { + } else if(model->callback != 0 && text_length >= model->minimum_length) { model->callback(model->callback_context); } } else if(selected == BACKSPACE_KEY) { @@ -487,6 +488,7 @@ void text_input_reset(TextInput* text_input) { model->header = ""; model->selected_row = 0; model->selected_column = 0; + model->minimum_length = 1; model->clear_default_text = false; model->text_buffer = NULL; model->text_buffer_size = 0; @@ -531,6 +533,14 @@ void text_input_set_result_callback( true); } +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length) { + with_view_model( + text_input->view, + TextInputModel * model, + { model->minimum_length = minimum_length; }, + true); +} + void text_input_set_validator( TextInput* text_input, TextInputValidatorCallback callback, diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index b6ca6b54f2..b6198f9695 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -65,6 +65,13 @@ void text_input_set_result_callback( size_t text_buffer_size, bool clear_default_text); +/** + * @brief Sets the minimum length of a TextInput + * @param [in] text_input TextInput + * @param [in] minimum_length Minimum input length + */ +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length); + void text_input_set_validator( TextInput* text_input, TextInputValidatorCallback callback, diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 63878fc190..6db4d82412 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -5,6 +5,12 @@ #define VIEW_DISPATCHER_QUEUE_LEN (16U) ViewDispatcher* view_dispatcher_alloc(void) { + ViewDispatcher* dispatcher = view_dispatcher_alloc_ex(furi_event_loop_alloc()); + dispatcher->is_event_loop_owned = true; + return dispatcher; +} + +ViewDispatcher* view_dispatcher_alloc_ex(FuriEventLoop* loop) { ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher)); view_dispatcher->view_port = view_port_alloc(); @@ -16,7 +22,7 @@ ViewDispatcher* view_dispatcher_alloc(void) { ViewDict_init(view_dispatcher->views); - view_dispatcher->event_loop = furi_event_loop_alloc(); + view_dispatcher->event_loop = loop; view_dispatcher->input_queue = furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(InputEvent)); @@ -57,7 +63,7 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) { furi_message_queue_free(view_dispatcher->input_queue); furi_message_queue_free(view_dispatcher->event_queue); - furi_event_loop_free(view_dispatcher->event_loop); + if(view_dispatcher->is_event_loop_owned) furi_event_loop_free(view_dispatcher->event_loop); // Free dispatcher free(view_dispatcher); } @@ -85,6 +91,7 @@ void view_dispatcher_set_tick_event_callback( ViewDispatcherTickEventCallback callback, uint32_t tick_period) { furi_check(view_dispatcher); + furi_check(view_dispatcher->is_event_loop_owned); view_dispatcher->tick_event_callback = callback; view_dispatcher->tick_period = tick_period; } @@ -106,11 +113,12 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) { uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever : view_dispatcher->tick_period; - furi_event_loop_tick_set( - view_dispatcher->event_loop, - tick_period, - view_dispatcher_handle_tick_event, - view_dispatcher); + if(view_dispatcher->is_event_loop_owned) + furi_event_loop_tick_set( + view_dispatcher->event_loop, + tick_period, + view_dispatcher_handle_tick_event, + view_dispatcher); furi_event_loop_run(view_dispatcher->event_loop); diff --git a/applications/services/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h index 9fbf897918..5820bcad3b 100644 --- a/applications/services/gui/view_dispatcher.h +++ b/applications/services/gui/view_dispatcher.h @@ -47,6 +47,15 @@ typedef void (*ViewDispatcherTickEventCallback)(void* context); */ ViewDispatcher* view_dispatcher_alloc(void); +/** Allocate ViewDispatcher instance with an externally owned event loop. If + * this constructor is used instead of `view_dispatcher_alloc`, the burden of + * freeing the event loop is placed on the caller. + * + * @param loop pointer to FuriEventLoop instance + * @return pointer to ViewDispatcher instance + */ +ViewDispatcher* view_dispatcher_alloc_ex(FuriEventLoop* loop); + /** Free ViewDispatcher instance * * @warning All added views MUST be removed using view_dispatcher_remove_view() @@ -97,6 +106,10 @@ void view_dispatcher_set_navigation_event_callback( /** Set tick event handler * + * @warning Requires the event loop to be owned by the view dispatcher, i.e. + * it should have been instantiated with `view_dispatcher_alloc`, not + * `view_dispatcher_alloc_ex`. + * * @param view_dispatcher ViewDispatcher instance * @param callback ViewDispatcherTickEventCallback * @param tick_period callback call period diff --git a/applications/services/gui/view_dispatcher_i.h b/applications/services/gui/view_dispatcher_i.h index c6c8dc665c..3d84b54995 100644 --- a/applications/services/gui/view_dispatcher_i.h +++ b/applications/services/gui/view_dispatcher_i.h @@ -14,6 +14,7 @@ DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) // NOLINT struct ViewDispatcher { + bool is_event_loop_owned; FuriEventLoop* event_loop; FuriMessageQueue* input_queue; FuriMessageQueue* event_queue; diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index ea0ff24ade..c28a5e10f5 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -377,7 +377,7 @@ void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, Furi * @param storage pointer to a storage API instance. * @param source pointer to a zero-terminated string containing the source path. * @param dest pointer to a zero-terminated string containing the destination path. - * @return FSE_OK if the migration was successfull completed, any other error code on failure. + * @return FSE_OK if the migration was successfully completed, any other error code on failure. */ FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest); @@ -425,7 +425,7 @@ bool storage_common_is_subdir(Storage* storage, const char* parent, const char* /******************* Error Functions *******************/ /** - * @brief Get the textual description of a numeric error identifer. + * @brief Get the textual description of a numeric error identifier. * * @param error_id numeric identifier of the error in question. * @return pointer to a statically allocated zero-terminated string containing the respective error text. diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index a7ae5c7c75..36fd7b16c4 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -16,11 +16,70 @@ App( ) App( - appid="js_dialog", + appid="js_event_loop", apptype=FlipperAppType.PLUGIN, - entry_point="js_dialog_ep", + entry_point="js_event_loop_ep", requires=["js_app"], - sources=["modules/js_dialog.c"], + sources=[ + "modules/js_event_loop/js_event_loop.c", + "modules/js_event_loop/js_event_loop_api_table.cpp", + ], +) + +App( + appid="js_gui", + apptype=FlipperAppType.PLUGIN, + entry_point="js_gui_ep", + requires=["js_app", "js_event_loop"], + sources=["modules/js_gui/js_gui.c", "modules/js_gui/js_gui_api_table.cpp"], +) + +App( + appid="js_gui__loading", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_loading_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/loading.c"], +) + +App( + appid="js_gui__empty_screen", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_empty_screen_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/empty_screen.c"], +) + +App( + appid="js_gui__submenu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_submenu_ep", + requires=["js_app", "js_gui"], + sources=["modules/js_gui/submenu.c"], +) + +App( + appid="js_gui__text_input", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_text_input_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/text_input.c"], +) + +App( + appid="js_gui__text_box", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_text_box_ep", + requires=["js_app"], + sources=["modules/js_gui/text_box.c"], +) + +App( + appid="js_gui__dialog", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_dialog_ep", + requires=["js_app"], + sources=["modules/js_gui/dialog.c"], ) App( @@ -48,11 +107,11 @@ App( ) App( - appid="js_submenu", + appid="js_gpio", apptype=FlipperAppType.PLUGIN, - entry_point="js_submenu_ep", - requires=["js_app"], - sources=["modules/js_submenu.c"], + entry_point="js_gpio_ep", + requires=["js_app", "js_event_loop"], + sources=["modules/js_gpio.c"], ) App( @@ -64,9 +123,9 @@ App( ) App( - appid="js_textbox", + appid="js_storage", apptype=FlipperAppType.PLUGIN, - entry_point="js_textbox_ep", + entry_point="js_storage_ep", requires=["js_app"], - sources=["modules/js_textbox.c"], + sources=["modules/js_storage.c"], ) diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js index 21090f6034..7284d86b74 100644 --- a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js +++ b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js @@ -1,33 +1,58 @@ let badusb = require("badusb"); let notify = require("notification"); let flipper = require("flipper"); -let dialog = require("dialog"); +let eventLoop = require("event_loop"); +let gui = require("gui"); +let dialog = require("gui/dialog"); -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" }); -dialog.message("BadUSB demo", "Press OK to start"); +let views = { + dialog: dialog.makeWith({ + header: "BadUSB demo", + text: "Press OK to start", + center: "Start", + }), +}; -if (badusb.isConnected()) { - notify.blink("green", "short"); - print("USB is connected"); +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: "Flipper", prodName: "Zero" }); - badusb.println("Hello, world!"); +eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui) { + if (button !== "center") + return; - badusb.press("CTRL", "a"); - badusb.press("CTRL", "c"); - badusb.press("DOWN"); - delay(1000); - badusb.press("CTRL", "v"); - delay(1000); - badusb.press("CTRL", "v"); + gui.viewDispatcher.sendTo("back"); - badusb.println("1234", 200); + if (badusb.isConnected()) { + notify.blink("green", "short"); + print("USB is connected"); - badusb.println("Flipper Model: " + flipper.getModel()); - badusb.println("Flipper Name: " + flipper.getName()); - badusb.println("Battery level: " + to_string(flipper.getBatteryCharge()) + "%"); + badusb.println("Hello, world!"); - notify.success(); -} else { - print("USB not connected"); - notify.error(); -} + badusb.press("CTRL", "a"); + badusb.press("CTRL", "c"); + badusb.press("DOWN"); + delay(1000); + badusb.press("CTRL", "v"); + delay(1000); + badusb.press("CTRL", "v"); + + badusb.println("1234", 200); + + badusb.println("Flipper Model: " + flipper.getModel()); + badusb.println("Flipper Name: " + flipper.getName()); + badusb.println("Battery level: " + toString(flipper.getBatteryCharge()) + "%"); + + notify.success(); + } else { + print("USB not connected"); + notify.error(); + } + + eventLoop.stop(); +}, eventLoop, gui); + +eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _item, eventLoop) { + eventLoop.stop(); +}, eventLoop); + +gui.viewDispatcher.switchTo(views.dialog); +eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/delay.js b/applications/system/js_app/examples/apps/Scripts/delay.js index 9f64abee80..5d8fbe4224 100644 --- a/applications/system/js_app/examples/apps/Scripts/delay.js +++ b/applications/system/js_app/examples/apps/Scripts/delay.js @@ -6,4 +6,4 @@ print("2"); delay(1000) print("3"); delay(1000) -print("end"); \ No newline at end of file +print("end"); diff --git a/applications/system/js_app/examples/apps/Scripts/dialog.js b/applications/system/js_app/examples/apps/Scripts/dialog.js deleted file mode 100644 index 9fc44f8b9e..0000000000 --- a/applications/system/js_app/examples/apps/Scripts/dialog.js +++ /dev/null @@ -1,19 +0,0 @@ -let dialog = require("dialog"); - -let result1 = dialog.message("Dialog demo", "Press OK to start"); -print(result1); - -let dialog_params = ({ - header: "Test_header", - text: "Test_text", - button_left: "Left", - button_right: "Right", - button_center: "OK" -}); - -let result2 = dialog.custom(dialog_params); -if (result2 === "") { - print("Back is pressed"); -} else { - print(result2, "is pressed"); -} diff --git a/applications/system/js_app/examples/apps/Scripts/event_loop.js b/applications/system/js_app/examples/apps/Scripts/event_loop.js new file mode 100644 index 0000000000..ad2f8a7dc7 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/event_loop.js @@ -0,0 +1,25 @@ +let eventLoop = require("event_loop"); + +// print a string after 1337 milliseconds +eventLoop.subscribe(eventLoop.timer("oneshot", 1337), function (_subscription, _item) { + print("Hi after 1337 ms"); +}); + +// count up to 5 with a delay of 100ms between increments +eventLoop.subscribe(eventLoop.timer("periodic", 100), function (subscription, _item, counter) { + print("Counter two:", counter); + if (counter === 5) + subscription.cancel(); + return [counter + 1]; +}, 0); + +// count up to 15 with a delay of 100ms between increments +// and stop the program when the count reaches 15 +eventLoop.subscribe(eventLoop.timer("periodic", 100), function (subscription, _item, event_loop, counter) { + print("Counter one:", counter); + if (counter === 15) + event_loop.stop(); + return [event_loop, counter + 1]; +}, eventLoop, 0); + +eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/gpio.js b/applications/system/js_app/examples/apps/Scripts/gpio.js new file mode 100644 index 0000000000..f3b4bc121b --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/gpio.js @@ -0,0 +1,57 @@ +let eventLoop = require("event_loop"); +let gpio = require("gpio"); + +// initialize pins +let led = gpio.get("pc3"); // same as `gpio.get(7)` +let pot = gpio.get("pc0"); // same as `gpio.get(16)` +let button = gpio.get("pc1"); // same as `gpio.get(15)` +led.init({ direction: "out", outMode: "push_pull" }); +pot.init({ direction: "in", inMode: "analog" }); +button.init({ direction: "in", pull: "up", inMode: "interrupt", edge: "falling" }); + +// blink led +print("Commencing blinking (PC3)"); +eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led, state) { + led.write(state); + return [led, !state]; +}, led, true); + +// read potentiometer when button is pressed +print("Press the button (PC1)"); +eventLoop.subscribe(button.interrupt(), function (_, _item, pot) { + print("PC0 is at", pot.read_analog(), "mV"); +}, pot); + +// the program will just exit unless this is here +eventLoop.run(); + +// possible pins https://docs.flipper.net/gpio-and-modules#miFsS +// "PA7" aka 2 +// "PA6" aka 3 +// "PA4" aka 4 +// "PB3" aka 5 +// "PB2" aka 6 +// "PC3" aka 7 +// "PA14" aka 10 +// "PA13" aka 12 +// "PB6" aka 13 +// "PB7" aka 14 +// "PC1" aka 15 +// "PC0" aka 16 +// "PB14" aka 17 + +// possible modes +// { direction: "out", outMode: "push_pull" } +// { direction: "out", outMode: "open_drain" } +// { direction: "out", outMode: "push_pull", altFn: true } +// { direction: "out", outMode: "open_drain", altFn: true } +// { direction: "in", inMode: "analog" } +// { direction: "in", inMode: "plain_digital" } +// { direction: "in", inMode: "interrupt", edge: "rising" } +// { direction: "in", inMode: "interrupt", edge: "falling" } +// { direction: "in", inMode: "interrupt", edge: "both" } +// { direction: "in", inMode: "event", edge: "rising" } +// { direction: "in", inMode: "event", edge: "falling" } +// { direction: "in", inMode: "event", edge: "both" } +// all variants support an optional `pull` field which can either be undefined, +// "up" or "down" diff --git a/applications/system/js_app/examples/apps/Scripts/gui.js b/applications/system/js_app/examples/apps/Scripts/gui.js new file mode 100644 index 0000000000..dd80b5bc4b --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/gui.js @@ -0,0 +1,77 @@ +// import modules +let eventLoop = require("event_loop"); +let gui = require("gui"); +let loadingView = require("gui/loading"); +let submenuView = require("gui/submenu"); +let emptyView = require("gui/empty_screen"); +let textInputView = require("gui/text_input"); +let textBoxView = require("gui/text_box"); +let dialogView = require("gui/dialog"); + +// declare view instances +let views = { + loading: loadingView.make(), + empty: emptyView.make(), + keyboard: textInputView.makeWith({ + header: "Enter your name", + minLength: 0, + maxLength: 32, + }), + helloDialog: dialogView.makeWith({ + center: "Hi Flipper! :)", + }), + longText: textBoxView.makeWith({ + text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.", + }), + demos: submenuView.makeWith({ + header: "Choose a demo", + items: [ + "Hourglass screen", + "Empty screen", + "Text input & Dialog", + "Text box", + "Exit app", + ], + }), +}; + +// demo selector +eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) { + if (index === 0) { + gui.viewDispatcher.switchTo(views.loading); + // the loading view captures all back events, preventing our navigation callback from firing + // switch to the demo chooser after a second + eventLoop.subscribe(eventLoop.timer("oneshot", 1000), function (_sub, _, gui, views) { + gui.viewDispatcher.switchTo(views.demos); + }, gui, views); + } else if (index === 1) { + gui.viewDispatcher.switchTo(views.empty); + } else if (index === 2) { + gui.viewDispatcher.switchTo(views.keyboard); + } else if (index === 3) { + gui.viewDispatcher.switchTo(views.longText); + } else if (index === 4) { + eventLoop.stop(); + } +}, gui, eventLoop, views); + +// say hi after keyboard input +eventLoop.subscribe(views.keyboard.input, function (_sub, name, gui, views) { + views.helloDialog.set("text", "Hi " + name + "! :)"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// go back after the greeting dialog +eventLoop.subscribe(views.helloDialog.input, function (_sub, button, gui, views) { + if (button === "center") + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// go to the demo chooser screen when the back key is pressed +eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) { + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// run UI +gui.viewDispatcher.switchTo(views.demos); +eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/load.js b/applications/system/js_app/examples/apps/Scripts/load.js index dfb110ca59..813619741a 100644 --- a/applications/system/js_app/examples/apps/Scripts/load.js +++ b/applications/system/js_app/examples/apps/Scripts/load.js @@ -1,3 +1,3 @@ let math = load("/ext/apps/Scripts/load_api.js"); let result = math.add(5, 10); -print(result); \ No newline at end of file +print(result); diff --git a/applications/system/js_app/examples/apps/Scripts/load_api.js b/applications/system/js_app/examples/apps/Scripts/load_api.js index ad3b26e156..80712c40b0 100644 --- a/applications/system/js_app/examples/apps/Scripts/load_api.js +++ b/applications/system/js_app/examples/apps/Scripts/load_api.js @@ -1,3 +1,3 @@ ({ add: function (a, b) { return a + b; }, -}) \ No newline at end of file +}) diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js index c5a0bf18d0..63527ea671 100644 --- a/applications/system/js_app/examples/apps/Scripts/math.js +++ b/applications/system/js_app/examples/apps/Scripts/math.js @@ -22,48 +22,3 @@ print("math.sign(-5):", math.sign(-5)); print("math.sin(math.PI/2):", math.sin(math.PI / 2)); print("math.sqrt(25):", math.sqrt(25)); print("math.trunc(5.7):", math.trunc(5.7)); - -// Unit tests. Please add more if you have time and knowledge. -// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 - -let succeeded = 0; -let failed = 0; - -function test(text, result, expected, epsilon) { - let is_equal = math.is_equal(result, expected, epsilon); - if (is_equal) { - succeeded += 1; - } else { - failed += 1; - print(text, "expected", expected, "got", result); - } -} - -test("math.abs(5)", math.abs(-5), 5, math.EPSILON); -test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON); -test("math.abs(5)", math.abs(5), 5, math.EPSILON); -test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON); -test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON); -test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON); -test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON); -test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON); -test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON); -test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON); -test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON); -test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON); -test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON); -test("math.clz32(1)", math.clz32(1), 31, math.EPSILON); -test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON); -test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON); -test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON); -test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON); -test("math.sign(-5)", math.sign(-5), -1, math.EPSILON); -test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON); -test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON); -test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15 -test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16 -test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 - -if (failed > 0) { - print("!!!", failed, "Unit tests failed !!!"); -} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/notify.js b/applications/system/js_app/examples/apps/Scripts/notify.js index 20f60c732e..dd471650c9 100644 --- a/applications/system/js_app/examples/apps/Scripts/notify.js +++ b/applications/system/js_app/examples/apps/Scripts/notify.js @@ -6,4 +6,4 @@ delay(1000); for (let i = 0; i < 10; i++) { notify.blink("red", "short"); delay(500); -} \ No newline at end of file +} diff --git a/applications/system/js_app/examples/apps/Scripts/submenu.js b/applications/system/js_app/examples/apps/Scripts/submenu.js deleted file mode 100644 index 2455513093..0000000000 --- a/applications/system/js_app/examples/apps/Scripts/submenu.js +++ /dev/null @@ -1,11 +0,0 @@ -let submenu = require("submenu"); - -submenu.addItem("Item 1", 0); -submenu.addItem("Item 2", 1); -submenu.addItem("Item 3", 2); - -submenu.setHeader("Select an option:"); - -let result = submenu.show(); -// Returns undefined when pressing back -print("Result:", result); diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/textbox.js deleted file mode 100644 index 6caf372347..0000000000 --- a/applications/system/js_app/examples/apps/Scripts/textbox.js +++ /dev/null @@ -1,30 +0,0 @@ -let textbox = require("textbox"); - -// You should set config before adding text -// Focus (start / end), Font (text / hex) -textbox.setConfig("end", "text"); - -// Can make sure it's cleared before showing, in case of reusing in same script -// (Closing textbox already clears the text, but maybe you added more in a loop for example) -textbox.clearText(); - -// Add default text -textbox.addText("Example dynamic updating textbox\n"); - -// Non-blocking, can keep updating text after, can close in JS or in GUI -textbox.show(); - -let i = 0; -while (textbox.isOpen() && i < 20) { - print("console", i++); - - // Add text to textbox buffer - textbox.addText("textbox " + to_string(i) + "\n"); - - delay(500); -} - -// If not closed by user (instead i < 20 is false above), close forcefully -if (textbox.isOpen()) { - textbox.close(); -} diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/uart_echo.js index 60d44d078d..2a0159b469 100644 --- a/applications/system/js_app/examples/apps/Scripts/uart_echo.js +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo.js @@ -6,6 +6,6 @@ while (1) { if (rx_data !== undefined) { serial.write(rx_data); let data_view = Uint8Array(rx_data); - print("0x" + to_hex_string(data_view[0])); + print("0x" + toString(data_view[0], 16)); } } \ No newline at end of file diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index d36f3c8dbf..5de720b437 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -114,7 +114,7 @@ int32_t js_app(void* arg) { FuriString* start_text = furi_string_alloc_printf("Running %s", furi_string_get_cstr(name)); console_view_print(app->console_view, furi_string_get_cstr(start_text)); - console_view_print(app->console_view, "------------"); + console_view_print(app->console_view, "-------------"); furi_string_free(name); furi_string_free(start_text); diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 9ab6cb1407..38ff46f752 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -1,7 +1,11 @@ #include #include "js_modules.h" -#include +#include + #include "modules/js_flipper.h" +#ifdef FW_CFG_unit_tests +#include "modules/js_tests.h" +#endif #define TAG "JS modules" @@ -9,54 +13,72 @@ #define MODULES_PATH "/ext/apps_data/js_app/plugins" typedef struct { - JsModeConstructor create; - JsModeDestructor destroy; + FuriString* name; + const JsModuleConstructor create; + const JsModuleDestructor destroy; void* context; } JsModuleData; -DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST); +// not using: +// - a dict because ordering is required +// - a bptree because it forces a sorted ordering +// - an rbtree because i deemed it more tedious to implement, and with the +// amount of modules in use (under 10 in the overwhelming majority of cases) +// i bet it's going to be slower than a plain array +ARRAY_DEF(JsModuleArray, JsModuleData, M_POD_OPLIST); +#define M_OPL_JsModuleArray_t() ARRAY_OPLIST(JsModuleArray) static const JsModuleDescriptor modules_builtin[] = { - {"flipper", js_flipper_create, NULL}, + {"flipper", js_flipper_create, NULL, NULL}, +#ifdef FW_CFG_unit_tests + {"tests", js_tests_create, NULL, NULL}, +#endif }; struct JsModules { struct mjs* mjs; - JsModuleDict_t module_dict; + JsModuleArray_t modules; PluginManager* plugin_manager; + CompositeApiResolver* resolver; }; JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver) { JsModules* modules = malloc(sizeof(JsModules)); modules->mjs = mjs; - JsModuleDict_init(modules->module_dict); + JsModuleArray_init(modules->modules); modules->plugin_manager = plugin_manager_alloc( PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); + modules->resolver = resolver; + return modules; } -void js_modules_destroy(JsModules* modules) { - JsModuleDict_it_t it; - for(JsModuleDict_it(it, modules->module_dict); !JsModuleDict_end_p(it); - JsModuleDict_next(it)) { - const JsModuleDict_itref_t* module_itref = JsModuleDict_cref(it); - if(module_itref->value.destroy) { - module_itref->value.destroy(module_itref->value.context); +void js_modules_destroy(JsModules* instance) { + for + M_EACH(module, instance->modules, JsModuleArray_t) { + FURI_LOG_T(TAG, "Tearing down %s", furi_string_get_cstr(module->name)); + if(module->destroy) module->destroy(module->context); + furi_string_free(module->name); } - } - plugin_manager_free(modules->plugin_manager); - JsModuleDict_clear(modules->module_dict); - free(modules); + plugin_manager_free(instance->plugin_manager); + JsModuleArray_clear(instance->modules); + free(instance); +} + +JsModuleData* js_find_loaded_module(JsModules* instance, const char* name) { + for + M_EACH(module, instance->modules, JsModuleArray_t) { + if(furi_string_cmp_str(module->name, name) == 0) return module; + } + return NULL; } mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) { - FuriString* module_name = furi_string_alloc_set_str(name); // Check if module is already installed - JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name); + JsModuleData* module_inst = js_find_loaded_module(modules, name); if(module_inst) { //-V547 - furi_string_free(module_name); mjs_prepend_errorf( modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name); return MJS_UNDEFINED; @@ -73,8 +95,11 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le if(strncmp(name, modules_builtin[i].name, name_compare_len) == 0) { JsModuleData module = { - .create = modules_builtin[i].create, .destroy = modules_builtin[i].destroy}; - JsModuleDict_set_at(modules->module_dict, module_name, module); + .create = modules_builtin[i].create, + .destroy = modules_builtin[i].destroy, + .name = furi_string_alloc_set_str(name), + }; + JsModuleArray_push_at(modules->modules, 0, module); module_found = true; FURI_LOG_I(TAG, "Using built-in module %s", name); break; @@ -83,39 +108,57 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le // External module load if(!module_found) { + FuriString* deslashed_name = furi_string_alloc_set_str(name); + furi_string_replace_all_str(deslashed_name, "/", "__"); FuriString* module_path = furi_string_alloc(); - furi_string_printf(module_path, "%s/js_%s.fal", MODULES_PATH, name); - FURI_LOG_I(TAG, "Loading external module %s", furi_string_get_cstr(module_path)); + furi_string_printf( + module_path, "%s/js_%s.fal", MODULES_PATH, furi_string_get_cstr(deslashed_name)); + FURI_LOG_I( + TAG, "Loading external module %s from %s", name, furi_string_get_cstr(module_path)); do { uint32_t plugin_cnt_last = plugin_manager_get_count(modules->plugin_manager); PluginManagerError load_error = plugin_manager_load_single( modules->plugin_manager, furi_string_get_cstr(module_path)); if(load_error != PluginManagerErrorNone) { + FURI_LOG_E( + TAG, + "Module %s load error. It may depend on other modules that are not yet loaded.", + name); break; } const JsModuleDescriptor* plugin = plugin_manager_get_ep(modules->plugin_manager, plugin_cnt_last); furi_assert(plugin); - if(strncmp(name, plugin->name, name_len) != 0) { - FURI_LOG_E(TAG, "Module name missmatch %s", plugin->name); + if(furi_string_cmp_str(deslashed_name, plugin->name) != 0) { + FURI_LOG_E(TAG, "Module name mismatch %s", plugin->name); break; } - JsModuleData module = {.create = plugin->create, .destroy = plugin->destroy}; - JsModuleDict_set_at(modules->module_dict, module_name, module); + JsModuleData module = { + .create = plugin->create, + .destroy = plugin->destroy, + .name = furi_string_alloc_set_str(name), + }; + JsModuleArray_push_at(modules->modules, 0, module); + + if(plugin->api_interface) { + FURI_LOG_I(TAG, "Added module API to composite resolver: %s", plugin->name); + composite_api_resolver_add(modules->resolver, plugin->api_interface); + } module_found = true; } while(0); furi_string_free(module_path); + furi_string_free(deslashed_name); } // Run module constructor mjs_val_t module_object = MJS_UNDEFINED; if(module_found) { - module_inst = JsModuleDict_get(modules->module_dict, module_name); + module_inst = js_find_loaded_module(modules, name); furi_assert(module_inst); if(module_inst->create) { //-V779 - module_inst->context = module_inst->create(modules->mjs, &module_object); + module_inst->context = module_inst->create(modules->mjs, &module_object, modules); } } @@ -123,7 +166,12 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name); } - furi_string_free(module_name); - return module_object; } + +void* js_module_get(JsModules* modules, const char* name) { + FuriString* module_name = furi_string_alloc_set_str(name); + JsModuleData* module_inst = js_find_loaded_module(modules, name); + furi_string_free(module_name); + return module_inst ? module_inst->context : NULL; +} diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 77e50786f4..788715872e 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -1,4 +1,6 @@ #pragma once + +#include #include "js_thread_i.h" #include #include @@ -7,19 +9,269 @@ #define PLUGIN_APP_ID "js" #define PLUGIN_API_VERSION 1 -typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); -typedef void (*JsModeDestructor)(void* inst); +/** + * @brief Returns the foreign pointer in `obj["_"]` + */ +#define JS_GET_INST(mjs, obj) mjs_get_ptr(mjs, mjs_get(mjs, obj, INST_PROP_NAME, ~0)) +/** + * @brief Returns the foreign pointer in `this["_"]` + */ +#define JS_GET_CONTEXT(mjs) JS_GET_INST(mjs, mjs_get_this(mjs)) + +/** + * @brief Syntax sugar for constructing an object + * + * @example + * ```c + * mjs_val_t my_obj = mjs_mk_object(mjs); + * JS_ASSIGN_MULTI(mjs, my_obj) { + * JS_FIELD("method1", MJS_MK_FN(js_storage_file_is_open)); + * JS_FIELD("method2", MJS_MK_FN(js_storage_file_is_open)); + * } + * ``` + */ +#define JS_ASSIGN_MULTI(mjs, object) \ + for(struct { \ + struct mjs* mjs; \ + mjs_val_t val; \ + int i; \ + } _ass_multi = {mjs, object, 0}; \ + _ass_multi.i == 0; \ + _ass_multi.i++) +#define JS_FIELD(name, value) mjs_set(_ass_multi.mjs, _ass_multi.val, name, ~0, value) + +/** + * @brief The first word of structures that foreign pointer JS values point to + * + * This is used to detect situations where JS code mistakenly passes an opaque + * foreign pointer of one type as an argument to a native function which expects + * a struct of another type. + * + * It is recommended to use this functionality in conjunction with the following + * convenience verification macros: + * - `JS_ARG_STRUCT()` + * - `JS_ARG_OBJ_WITH_STRUCT()` + * + * @warning In order for the mechanism to work properly, your struct must store + * the magic value in the first word. + */ +typedef enum { + JsForeignMagicStart = 0x15BAD000, + JsForeignMagic_JsEventLoopContract, +} JsForeignMagic; + +// Are you tired of your silly little JS+C glue code functions being 75% +// argument validation code and 25% actual logic? Introducing: ASS (Argument +// Schema for Scripts)! ASS is a set of macros that reduce the typical +// boilerplate code of "check argument count, get arguments, validate arguments, +// extract C values from arguments" down to just one line! + +/** + * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies + * that the function requires exactly as many arguments as were specified. + */ +#define JS_EXACTLY == +/** + * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies + * that the function requires at least as many arguments as were specified. + */ +#define JS_AT_LEAST >= + +#define JS_ENUM_MAP(var_name, ...) \ + static const JsEnumMapping var_name##_mapping[] = { \ + {NULL, sizeof(var_name)}, \ + __VA_ARGS__, \ + {NULL, 0}, \ + }; typedef struct { - char* name; - JsModeConstructor create; - JsModeDestructor destroy; -} JsModuleDescriptor; + const char* name; + size_t value; +} JsEnumMapping; + +typedef struct { + void* out; + int (*validator)(mjs_val_t); + void (*converter)(struct mjs*, mjs_val_t*, void* out, const void* extra); + const char* expected_type; + bool (*extended_validator)(struct mjs*, mjs_val_t, const void* extra); + const void* extra_data; +} _js_arg_decl; + +static inline void _js_to_int32(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(int32_t*)out = mjs_get_int32(mjs, *in); +} +#define JS_ARG_INT32(out) ((_js_arg_decl){out, mjs_is_number, _js_to_int32, "number", NULL, NULL}) + +static inline void _js_to_ptr(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(void**)out = mjs_get_ptr(mjs, *in); +} +#define JS_ARG_PTR(out) \ + ((_js_arg_decl){out, mjs_is_foreign, _js_to_ptr, "opaque pointer", NULL, NULL}) + +static inline void _js_to_string(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(const char**)out = mjs_get_string(mjs, in, NULL); +} +#define JS_ARG_STR(out) ((_js_arg_decl){out, mjs_is_string, _js_to_string, "string", NULL, NULL}) + +static inline void _js_to_bool(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(bool*)out = !!mjs_get_bool(mjs, *in); +} +#define JS_ARG_BOOL(out) ((_js_arg_decl){out, mjs_is_boolean, _js_to_bool, "boolean", NULL, NULL}) + +static inline void _js_passthrough(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + UNUSED(mjs); + *(mjs_val_t*)out = *in; +} +#define JS_ARG_ANY(out) ((_js_arg_decl){out, NULL, _js_passthrough, "any", NULL, NULL}) +#define JS_ARG_OBJ(out) ((_js_arg_decl){out, mjs_is_object, _js_passthrough, "any", NULL, NULL}) +#define JS_ARG_FN(out) \ + ((_js_arg_decl){out, mjs_is_function, _js_passthrough, "function", NULL, NULL}) +#define JS_ARG_ARR(out) ((_js_arg_decl){out, mjs_is_array, _js_passthrough, "array", NULL, NULL}) + +static inline bool _js_validate_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { + JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; + JsForeignMagic struct_magic = *(JsForeignMagic*)mjs_get_ptr(mjs, val); + return struct_magic == expected_magic; +} +#define JS_ARG_STRUCT(type, out) \ + ((_js_arg_decl){ \ + out, \ + mjs_is_foreign, \ + _js_to_ptr, \ + #type, \ + _js_validate_struct, \ + (void*)JsForeignMagic##_##type}) + +static inline bool _js_validate_obj_w_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { + JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; + JsForeignMagic struct_magic = *(JsForeignMagic*)JS_GET_INST(mjs, val); + return struct_magic == expected_magic; +} +#define JS_ARG_OBJ_WITH_STRUCT(type, out) \ + ((_js_arg_decl){ \ + out, \ + mjs_is_object, \ + _js_passthrough, \ + #type, \ + _js_validate_obj_w_struct, \ + (void*)JsForeignMagic##_##type}) + +static inline bool _js_validate_enum(struct mjs* mjs, mjs_val_t val, const void* extra) { + for(const JsEnumMapping* mapping = (JsEnumMapping*)extra + 1; mapping->name; mapping++) + if(strcmp(mapping->name, mjs_get_string(mjs, &val, NULL)) == 0) return true; + return false; +} +static inline void + _js_convert_enum(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) { + const JsEnumMapping* mapping = (JsEnumMapping*)extra; + size_t size = mapping->value; // get enum size from first entry + for(mapping++; mapping->name; mapping++) { + if(strcmp(mapping->name, mjs_get_string(mjs, val, NULL)) == 0) { + if(size == 1) + *(uint8_t*)out = mapping->value; + else if(size == 2) + *(uint16_t*)out = mapping->value; + else if(size == 4) + *(uint32_t*)out = mapping->value; + else if(size == 8) + *(uint64_t*)out = mapping->value; + return; + } + } + // unreachable, thanks to _js_validate_enum +} +#define JS_ARG_ENUM(var_name, name) \ + ((_js_arg_decl){ \ + &var_name, \ + mjs_is_string, \ + _js_convert_enum, \ + name " enum", \ + _js_validate_enum, \ + var_name##_mapping}) + +//-V:JS_FETCH_ARGS_OR_RETURN:1008 +/** + * @brief Fetches and validates the arguments passed to a JS function + * + * Example: `int32_t my_arg; JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&my_arg));` + * + * @warning This macro executes `return;` by design in case of an argument count + * mismatch or a validation failure + */ +#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \ + _js_arg_decl _js_args[] = {__VA_ARGS__}; \ + int _js_arg_cnt = COUNT_OF(_js_args); \ + mjs_val_t _js_arg_vals[_js_arg_cnt]; \ + if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "expected %s%d arguments, got %d", \ + #arg_operator, \ + _js_arg_cnt, \ + mjs_nargs(mjs)); \ + for(int _i = 0; _i < _js_arg_cnt; _i++) { \ + _js_arg_vals[_i] = mjs_arg(mjs, _i); \ + if(_js_args[_i].validator) \ + if(!_js_args[_i].validator(_js_arg_vals[_i])) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "argument %d: expected %s", \ + _i, \ + _js_args[_i].expected_type); \ + if(_js_args[_i].extended_validator) \ + if(!_js_args[_i].extended_validator(mjs, _js_arg_vals[_i], _js_args[_i].extra_data)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "argument %d: expected %s", \ + _i, \ + _js_args[_i].expected_type); \ + _js_args[_i].converter( \ + mjs, &_js_arg_vals[_i], _js_args[_i].out, _js_args[_i].extra_data); \ + } + +/** + * @brief Prepends an error, sets the JS return value to `undefined` and returns + * from the C function + * @warning This macro executes `return;` by design + */ +#define JS_ERROR_AND_RETURN(mjs, error_code, ...) \ + do { \ + mjs_prepend_errorf(mjs, error_code, __VA_ARGS__); \ + mjs_return(mjs, MJS_UNDEFINED); \ + return; \ + } while(0) typedef struct JsModules JsModules; +typedef void* (*JsModuleConstructor)(struct mjs* mjs, mjs_val_t* object, JsModules* modules); +typedef void (*JsModuleDestructor)(void* inst); + +typedef struct { + char* name; + JsModuleConstructor create; + JsModuleDestructor destroy; + const ElfApiInterface* api_interface; +} JsModuleDescriptor; + JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver); void js_modules_destroy(JsModules* modules); mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len); + +/** + * @brief Gets a module instance by its name + * This is useful when a module wants to access a stateful API of another + * module. + * @returns Pointer to module context, NULL if the module is not instantiated + */ +void* js_module_get(JsModules* modules, const char* name); diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 78b6f6ff47..7e7280e9cb 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -195,17 +195,11 @@ static void js_require(struct mjs* mjs) { } static void js_global_to_string(struct mjs* mjs) { + int base = 10; + if(mjs_nargs(mjs) > 1) base = mjs_get_int(mjs, mjs_arg(mjs, 1)); double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); char tmp_str[] = "-2147483648"; - itoa(num, tmp_str, 10); - mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); - mjs_return(mjs, ret); -} - -static void js_global_to_hex_string(struct mjs* mjs) { - double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); - char tmp_str[] = "-FFFFFFFF"; - itoa(num, tmp_str, 16); + itoa(num, tmp_str, base); mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); mjs_return(mjs, ret); } @@ -239,8 +233,7 @@ static int32_t js_thread(void* arg) { mjs_val_t global = mjs_get_global(mjs); mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print)); mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay)); - mjs_set(mjs, global, "to_string", ~0, MJS_MK_FN(js_global_to_string)); - mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string)); + mjs_set(mjs, global, "toString", ~0, MJS_MK_FN(js_global_to_string)); mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address)); mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require)); @@ -296,8 +289,8 @@ static int32_t js_thread(void* arg) { } } - js_modules_destroy(worker->modules); mjs_destroy(mjs); + js_modules_destroy(worker->modules); composite_api_resolver_free(worker->resolver); diff --git a/applications/system/js_app/js_thread.h b/applications/system/js_app/js_thread.h index 969715ec1a..581a449192 100644 --- a/applications/system/js_app/js_thread.h +++ b/applications/system/js_app/js_thread.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + typedef struct JsThread JsThread; typedef enum { @@ -14,3 +18,7 @@ typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* con JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context); void js_thread_stop(JsThread* worker); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 99f8958f76..891bfa2cdf 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -72,8 +72,8 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf } mjs_val_t vid_obj = mjs_get(mjs, arg, "vid", ~0); mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0); - mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0); - mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0); + mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfrName", ~0); + mjs_val_t prod_obj = mjs_get(mjs, arg, "prodName", ~0); if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) { hid_cfg->vid = mjs_get_int32(mjs, vid_obj); @@ -378,7 +378,8 @@ static void js_badusb_println(struct mjs* mjs) { badusb_print(mjs, true); } -static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst)); mjs_val_t badusb_obj = mjs_mk_object(mjs); mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb)); @@ -409,6 +410,7 @@ static const JsModuleDescriptor js_badusb_desc = { "badusb", js_badusb_create, js_badusb_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c deleted file mode 100644 index 34de6d6416..0000000000 --- a/applications/system/js_app/modules/js_dialog.c +++ /dev/null @@ -1,154 +0,0 @@ -#include -#include "../js_modules.h" -#include - -static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { - size_t num_args = mjs_nargs(mjs); - if(num_args != 2) { - return false; - } - mjs_val_t header_obj = mjs_arg(mjs, 0); - mjs_val_t msg_obj = mjs_arg(mjs, 1); - if((!mjs_is_string(header_obj)) || (!mjs_is_string(msg_obj))) { - return false; - } - - size_t arg_len = 0; - *hdr = mjs_get_string(mjs, &header_obj, &arg_len); - if(arg_len == 0) { - *hdr = NULL; - } - - *msg = mjs_get_string(mjs, &msg_obj, &arg_len); - if(arg_len == 0) { - *msg = NULL; - } - - return true; -} - -static void js_dialog_message(struct mjs* mjs) { - const char* dialog_header = NULL; - const char* dialog_msg = NULL; - if(!js_dialog_msg_parse_params(mjs, &dialog_header, &dialog_msg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_buttons(message, NULL, "OK", NULL); - if(dialog_header) { - dialog_message_set_header(message, dialog_header, 64, 3, AlignCenter, AlignTop); - } - if(dialog_msg) { - dialog_message_set_text(message, dialog_msg, 64, 26, AlignCenter, AlignTop); - } - DialogMessageButton result = dialog_message_show(dialogs, message); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - mjs_return(mjs, mjs_mk_boolean(mjs, result == DialogMessageButtonCenter)); -} - -static void js_dialog_custom(struct mjs* mjs) { - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - - bool params_correct = false; - - do { - if(mjs_nargs(mjs) != 1) { - break; - } - mjs_val_t params_obj = mjs_arg(mjs, 0); - if(!mjs_is_object(params_obj)) { - break; - } - - mjs_val_t text_obj = mjs_get(mjs, params_obj, "header", ~0); - size_t arg_len = 0; - const char* text_str = mjs_get_string(mjs, &text_obj, &arg_len); - if(arg_len == 0) { - text_str = NULL; - } - if(text_str) { - dialog_message_set_header(message, text_str, 64, 3, AlignCenter, AlignTop); - } - - text_obj = mjs_get(mjs, params_obj, "text", ~0); - text_str = mjs_get_string(mjs, &text_obj, &arg_len); - if(arg_len == 0) { - text_str = NULL; - } - if(text_str) { - dialog_message_set_text(message, text_str, 64, 26, AlignCenter, AlignTop); - } - - mjs_val_t btn_obj[3] = { - mjs_get(mjs, params_obj, "button_left", ~0), - mjs_get(mjs, params_obj, "button_center", ~0), - mjs_get(mjs, params_obj, "button_right", ~0), - }; - const char* btn_text[3] = {NULL, NULL, NULL}; - - for(uint8_t i = 0; i < 3; i++) { - if(!mjs_is_string(btn_obj[i])) { - continue; - } - btn_text[i] = mjs_get_string(mjs, &btn_obj[i], &arg_len); - if(arg_len == 0) { - btn_text[i] = NULL; - } - } - - dialog_message_set_buttons(message, btn_text[0], btn_text[1], btn_text[2]); - - DialogMessageButton result = dialog_message_show(dialogs, message); - mjs_val_t return_obj = MJS_UNDEFINED; - if(result == DialogMessageButtonLeft) { - return_obj = mjs_mk_string(mjs, btn_text[0], ~0, true); - } else if(result == DialogMessageButtonCenter) { - return_obj = mjs_mk_string(mjs, btn_text[1], ~0, true); - } else if(result == DialogMessageButtonRight) { - return_obj = mjs_mk_string(mjs, btn_text[2], ~0, true); - } else { - return_obj = mjs_mk_string(mjs, "", ~0, true); - } - - mjs_return(mjs, return_obj); - params_correct = true; - } while(0); - - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - - if(!params_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - } -} - -static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { - mjs_val_t dialog_obj = mjs_mk_object(mjs); - mjs_set(mjs, dialog_obj, "message", ~0, MJS_MK_FN(js_dialog_message)); - mjs_set(mjs, dialog_obj, "custom", ~0, MJS_MK_FN(js_dialog_custom)); - *object = dialog_obj; - - return (void*)1; -} - -static const JsModuleDescriptor js_dialog_desc = { - "dialog", - js_dialog_create, - NULL, -}; - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_dialog_desc, -}; - -const FlipperAppPluginDescriptor* js_dialog_ep(void) { - return &plugin_descriptor; -} diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.c b/applications/system/js_app/modules/js_event_loop/js_event_loop.c new file mode 100644 index 0000000000..c4f0d1beec --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.c @@ -0,0 +1,451 @@ +#include "js_event_loop.h" +#include "../../js_modules.h" // IWYU pragma: keep +#include +#include + +/** + * @brief Number of arguments that callbacks receive from this module that they can't modify + */ +#define SYSTEM_ARGS 2 + +/** + * @brief Context passed to the generic event callback + */ +typedef struct { + JsEventLoopObjectType object_type; + + struct mjs* mjs; + mjs_val_t callback; + // NOTE: not using an mlib array because resizing is not needed. + mjs_val_t* arguments; + size_t arity; + + JsEventLoopTransformer transformer; + void* transformer_context; +} JsEventLoopCallbackContext; + +/** + * @brief Contains data needed to cancel a subscription + */ +typedef struct { + FuriEventLoop* loop; + JsEventLoopObjectType object_type; + FuriEventLoopObject* object; + JsEventLoopCallbackContext* context; + JsEventLoopContract* contract; + void* subscriptions; // SubscriptionArray_t, which we can't reference in this definition +} JsEventLoopSubscription; + +typedef struct { + FuriEventLoop* loop; + struct mjs* mjs; +} JsEventLoopTickContext; + +ARRAY_DEF(SubscriptionArray, JsEventLoopSubscription*, M_PTR_OPLIST); //-V575 +ARRAY_DEF(ContractArray, JsEventLoopContract*, M_PTR_OPLIST); //-V575 + +/** + * @brief Per-module instance control structure + */ +struct JsEventLoop { + FuriEventLoop* loop; + SubscriptionArray_t subscriptions; + ContractArray_t owned_contracts; //mjs, + &result, + context->callback, + MJS_UNDEFINED, + context->arity, + context->arguments); + + // save returned args for next call + if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return; + for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) { + mjs_disown(context->mjs, &context->arguments[i + SYSTEM_ARGS]); + context->arguments[i + SYSTEM_ARGS] = mjs_array_get(context->mjs, result, i); + mjs_own(context->mjs, &context->arguments[i + SYSTEM_ARGS]); + } +} + +/** + * @brief Handles non-timer events + */ +static bool js_event_loop_callback(void* object, void* param) { + JsEventLoopCallbackContext* context = param; + + if(context->transformer) { + mjs_disown(context->mjs, &context->arguments[1]); + context->arguments[1] = + context->transformer(context->mjs, object, context->transformer_context); + mjs_own(context->mjs, &context->arguments[1]); + } else { + // default behavior: take semaphores and mutexes + switch(context->object_type) { + case JsEventLoopObjectTypeSemaphore: { + FuriSemaphore* semaphore = object; + furi_check(furi_semaphore_acquire(semaphore, 0) == FuriStatusOk); + } break; + default: + // the corresponding check has been performed when we were given the contract + furi_crash(); + } + } + + js_event_loop_callback_generic(param); + + return true; +} + +/** + * @brief Cancels an event subscription + */ +static void js_event_loop_subscription_cancel(struct mjs* mjs) { + JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs); + + if(subscription->object_type == JsEventLoopObjectTypeTimer) { + furi_event_loop_timer_stop(subscription->object); + } else { + furi_event_loop_unsubscribe(subscription->loop, subscription->object); + } + + free(subscription->context->arguments); + free(subscription->context); + + // find and remove ourselves from the array + SubscriptionArray_it_t iterator; + for(SubscriptionArray_it(iterator, subscription->subscriptions); + !SubscriptionArray_end_p(iterator); + SubscriptionArray_next(iterator)) { + JsEventLoopSubscription* item = *SubscriptionArray_cref(iterator); + if(item == subscription) break; + } + SubscriptionArray_remove(subscription->subscriptions, iterator); + free(subscription); + + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Subscribes a JavaScript function to an event + */ +static void js_event_loop_subscribe(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + // get arguments + JsEventLoopContract* contract; + mjs_val_t callback; + JS_FETCH_ARGS_OR_RETURN( + mjs, JS_AT_LEAST, JS_ARG_STRUCT(JsEventLoopContract, &contract), JS_ARG_FN(&callback)); + + // create subscription object + JsEventLoopSubscription* subscription = malloc(sizeof(JsEventLoopSubscription)); + JsEventLoopCallbackContext* context = malloc(sizeof(JsEventLoopCallbackContext)); + subscription->loop = module->loop; + subscription->object_type = contract->object_type; + subscription->context = context; + subscription->subscriptions = module->subscriptions; + if(contract->object_type == JsEventLoopObjectTypeTimer) subscription->contract = contract; + mjs_val_t subscription_obj = mjs_mk_object(mjs); + mjs_set(mjs, subscription_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, subscription)); + mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel)); + + // create callback context + context->object_type = contract->object_type; + context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2; + context->arguments = calloc(context->arity, sizeof(mjs_val_t)); + context->arguments[0] = subscription_obj; + context->arguments[1] = MJS_UNDEFINED; + for(size_t i = SYSTEM_ARGS; i < context->arity; i++) { + mjs_val_t arg = mjs_arg(mjs, i - SYSTEM_ARGS + 2); + context->arguments[i] = arg; + mjs_own(mjs, &context->arguments[i]); + } + context->mjs = mjs; + context->callback = callback; + mjs_own(mjs, &context->callback); + mjs_own(mjs, &context->arguments[0]); + mjs_own(mjs, &context->arguments[1]); + + // queue and stream contracts must have a transform callback, others are allowed to delegate + // the obvious default behavior to this module + if(contract->object_type == JsEventLoopObjectTypeQueue || + contract->object_type == JsEventLoopObjectTypeStream) { + furi_check(contract->non_timer.transformer); + } + context->transformer = contract->non_timer.transformer; + context->transformer_context = contract->non_timer.transformer_context; + + // subscribe + switch(contract->object_type) { + case JsEventLoopObjectTypeTimer: { + FuriEventLoopTimer* timer = furi_event_loop_timer_alloc( + module->loop, js_event_loop_callback_generic, contract->timer.type, context); + furi_event_loop_timer_start(timer, contract->timer.interval_ticks); + contract->object = timer; + } break; + case JsEventLoopObjectTypeSemaphore: + furi_event_loop_subscribe_semaphore( + module->loop, + contract->object, + contract->non_timer.event, + js_event_loop_callback, + context); + break; + case JsEventLoopObjectTypeQueue: + furi_event_loop_subscribe_message_queue( + module->loop, + contract->object, + contract->non_timer.event, + js_event_loop_callback, + context); + break; + default: + furi_crash("unimplemented"); + } + + subscription->object = contract->object; + SubscriptionArray_push_back(module->subscriptions, subscription); + mjs_return(mjs, subscription_obj); +} + +/** + * @brief Runs the event loop until it is stopped + */ +static void js_event_loop_run(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + furi_event_loop_run(module->loop); +} + +/** + * @brief Stops a running event loop + */ +static void js_event_loop_stop(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + furi_event_loop_stop(module->loop); +} + +/** + * @brief Creates a timer event that can be subscribed to just like any other + * event + */ +static void js_event_loop_timer(struct mjs* mjs) { + // get arguments + const char* mode_str; + int32_t interval; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&mode_str), JS_ARG_INT32(&interval)); + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + FuriEventLoopTimerType mode; + if(strcasecmp(mode_str, "periodic") == 0) { + mode = FuriEventLoopTimerTypePeriodic; + } else if(strcasecmp(mode_str, "oneshot") == 0) { + mode = FuriEventLoopTimerTypeOnce; + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: unknown mode"); + } + + // make timer contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeTimer, + .object = NULL, + .timer = + { + .interval_ticks = furi_ms_to_ticks((uint32_t)interval), + .type = mode, + }, + }; + ContractArray_push_back(module->owned_contracts, contract); + mjs_return(mjs, mjs_mk_foreign(mjs, contract)); +} + +/** + * @brief Queue transformer. Takes `mjs_val_t` pointers out of a queue and + * returns their dereferenced value + */ +static mjs_val_t + js_event_loop_queue_transformer(struct mjs* mjs, FuriEventLoopObject* object, void* context) { + UNUSED(context); + mjs_val_t* message_ptr; + furi_check(furi_message_queue_get(object, &message_ptr, 0) == FuriStatusOk); + mjs_val_t message = *message_ptr; + mjs_disown(mjs, message_ptr); + free(message_ptr); + return message; +} + +/** + * @brief Sends a message to a queue + */ +static void js_event_loop_queue_send(struct mjs* mjs) { + // get arguments + mjs_val_t message; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&message)); + JsEventLoopContract* contract = JS_GET_CONTEXT(mjs); + + // send message + mjs_val_t* message_ptr = malloc(sizeof(mjs_val_t)); + *message_ptr = message; + mjs_own(mjs, message_ptr); + furi_message_queue_put(contract->object, &message_ptr, 0); + + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Creates a queue + */ +static void js_event_loop_queue(struct mjs* mjs) { + // get arguments + int32_t length; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&length)); + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + // make queue contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + // we could store `mjs_val_t`s in the queue directly if not for mJS' requirement to have consistent pointers to owned values + .object = furi_message_queue_alloc((size_t)length, sizeof(mjs_val_t*)), + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = js_event_loop_queue_transformer, + }, + }; + ContractArray_push_back(module->owned_contracts, contract); + + // return object with control methods + mjs_val_t queue = mjs_mk_object(mjs); + mjs_set(mjs, queue, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, contract)); + mjs_set(mjs, queue, "input", ~0, mjs_mk_foreign(mjs, contract)); + mjs_set(mjs, queue, "send", ~0, MJS_MK_FN(js_event_loop_queue_send)); + mjs_return(mjs, queue); +} + +static void js_event_loop_tick(void* param) { + JsEventLoopTickContext* context = param; + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0); + if(flags & FuriFlagError) { + return; + } + if(flags & ThreadEventStop) { + furi_event_loop_stop(context->loop); + mjs_exit(context->mjs); + } +} + +static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + mjs_val_t event_loop_obj = mjs_mk_object(mjs); + JsEventLoop* module = malloc(sizeof(JsEventLoop)); + JsEventLoopTickContext* tick_ctx = malloc(sizeof(JsEventLoopTickContext)); + module->loop = furi_event_loop_alloc(); + tick_ctx->loop = module->loop; + tick_ctx->mjs = mjs; + module->tick_context = tick_ctx; + furi_event_loop_tick_set(module->loop, 10, js_event_loop_tick, tick_ctx); + SubscriptionArray_init(module->subscriptions); + ContractArray_init(module->owned_contracts); + + mjs_set(mjs, event_loop_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module)); + mjs_set(mjs, event_loop_obj, "subscribe", ~0, MJS_MK_FN(js_event_loop_subscribe)); + mjs_set(mjs, event_loop_obj, "run", ~0, MJS_MK_FN(js_event_loop_run)); + mjs_set(mjs, event_loop_obj, "stop", ~0, MJS_MK_FN(js_event_loop_stop)); + mjs_set(mjs, event_loop_obj, "timer", ~0, MJS_MK_FN(js_event_loop_timer)); + mjs_set(mjs, event_loop_obj, "queue", ~0, MJS_MK_FN(js_event_loop_queue)); + + *object = event_loop_obj; + return module; +} + +static void js_event_loop_destroy(void* inst) { + if(inst) { + JsEventLoop* module = inst; + furi_event_loop_stop(module->loop); + + // free subscriptions + SubscriptionArray_it_t sub_iterator; + for(SubscriptionArray_it(sub_iterator, module->subscriptions); + !SubscriptionArray_end_p(sub_iterator); + SubscriptionArray_next(sub_iterator)) { + JsEventLoopSubscription* const* sub = SubscriptionArray_cref(sub_iterator); + free((*sub)->context->arguments); + free((*sub)->context); + free(*sub); + } + SubscriptionArray_clear(module->subscriptions); + + // free owned contracts + ContractArray_it_t iterator; + for(ContractArray_it(iterator, module->owned_contracts); !ContractArray_end_p(iterator); + ContractArray_next(iterator)) { + // unsubscribe object + JsEventLoopContract* contract = *ContractArray_cref(iterator); + if(contract->object_type == JsEventLoopObjectTypeTimer) { + furi_event_loop_timer_stop(contract->object); + } else { + furi_event_loop_unsubscribe(module->loop, contract->object); + } + + // free object + switch(contract->object_type) { + case JsEventLoopObjectTypeTimer: + furi_event_loop_timer_free(contract->object); + break; + case JsEventLoopObjectTypeSemaphore: + furi_semaphore_free(contract->object); + break; + case JsEventLoopObjectTypeQueue: + furi_message_queue_free(contract->object); + break; + default: + furi_crash("unimplemented"); + } + + free(contract); + } + ContractArray_clear(module->owned_contracts); + + furi_event_loop_free(module->loop); + free(module->tick_context); + free(module); + } +} + +extern const ElfApiInterface js_event_loop_hashtable_api_interface; + +static const JsModuleDescriptor js_event_loop_desc = { + "event_loop", + js_event_loop_create, + js_event_loop_destroy, + &js_event_loop_hashtable_api_interface, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_event_loop_desc, +}; + +const FlipperAppPluginDescriptor* js_event_loop_ep(void) { + return &plugin_descriptor; +} + +FuriEventLoop* js_event_loop_get_loop(JsEventLoop* loop) { + // porta: not the proudest function that i ever wrote + furi_check(loop); + return loop->loop; +} diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.h b/applications/system/js_app/modules/js_event_loop/js_event_loop.h new file mode 100644 index 0000000000..7ae608e349 --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.h @@ -0,0 +1,104 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include +#include + +/** + * @file js_event_loop.h + * + * In JS interpreter code, `js_event_loop` always creates and maintains the + * event loop. There are two ways in which other modules can integrate with this + * loop: + * - Via contracts: The user of your module would have to acquire an opaque + * JS value from you and pass it to `js_event_loop`. This is useful for + * events that they user may be interested in. For more info, look at + * `JsEventLoopContract`. Also look at `js_event_loop_get_loop`, which + * you will need to unsubscribe the event loop from your object. + * - Directly: When your module is created, you can acquire an instance of + * `JsEventLoop` which you can use to acquire an instance of + * `FuriEventLoop` that you can manipulate directly, without the JS + * programmer having to pass contracts around. This is useful for + * "behind-the-scenes" events that the user does not need to know about. For + * more info, look at `js_event_loop_get_loop`. + * + * In both cases, your module is responsible for both instantiating, + * unsubscribing and freeing the object that the event loop subscribes to. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct JsEventLoop JsEventLoop; + +typedef enum { + JsEventLoopObjectTypeTimer, + JsEventLoopObjectTypeQueue, + JsEventLoopObjectTypeMutex, + JsEventLoopObjectTypeSemaphore, + JsEventLoopObjectTypeStream, +} JsEventLoopObjectType; + +typedef mjs_val_t ( + *JsEventLoopTransformer)(struct mjs* mjs, FuriEventLoopObject* object, void* context); + +typedef struct { + FuriEventLoopEvent event; + JsEventLoopTransformer transformer; + void* transformer_context; +} JsEventLoopNonTimerContract; + +typedef struct { + FuriEventLoopTimerType type; + uint32_t interval_ticks; +} JsEventLoopTimerContract; + +/** + * @brief Adapter for other JS modules that wish to integrate with the event + * loop JS module + * + * If another module wishes to integrate with `js_event_loop`, it needs to + * implement a function callable from JS that returns an mJS foreign pointer to + * an instance of this structure. This value is then read by `event_loop`'s + * `subscribe` function. + * + * There are two fundamental variants of this structure: + * - `object_type` is `JsEventLoopObjectTypeTimer`: the `timer` field is + * valid, and the `non_timer` field is invalid. + * - `object_type` is something else: the `timer` field is invalid, and the + * `non_timer` field is valid. `non_timer.event` will be passed to + * `furi_event_loop_subscribe`. `non_timer.transformer` will be called to + * transform an object into a JS value (called an item) that's passed to the + * JS callback. This is useful for example to take an item out of a message + * queue and pass it to JS code in a convenient format. If + * `non_timer.transformer` is NULL, the event loop will take semaphores and + * mutexes on its own. + * + * The producer of the contract is responsible for freeing both the contract and + * the object that it points to when the interpreter is torn down. + */ +typedef struct { + JsForeignMagic magic; // +#include + +#include "js_event_loop_api_table_i.h" + +static_assert(!has_hash_collisions(js_event_loop_api_table), "Detected API method hash collision!"); + +extern "C" constexpr HashtableApiInterface js_event_loop_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + js_event_loop_api_table.cbegin(), + js_event_loop_api_table.cend(), +}; diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h b/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h new file mode 100644 index 0000000000..49090caebc --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h @@ -0,0 +1,4 @@ +#include "js_event_loop.h" + +static constexpr auto js_event_loop_api_table = sort( + create_array_t(API_METHOD(js_event_loop_get_loop, FuriEventLoop*, (JsEventLoop*)))); diff --git a/applications/system/js_app/modules/js_flipper.c b/applications/system/js_app/modules/js_flipper.c index 4619a1593c..43c675e107 100644 --- a/applications/system/js_app/modules/js_flipper.c +++ b/applications/system/js_app/modules/js_flipper.c @@ -25,7 +25,8 @@ static void js_flipper_get_battery(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, info.charge)); } -void* js_flipper_create(struct mjs* mjs, mjs_val_t* object) { +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); mjs_val_t flipper_obj = mjs_mk_object(mjs); mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model)); mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name)); diff --git a/applications/system/js_app/modules/js_flipper.h b/applications/system/js_app/modules/js_flipper.h index 3b05389cc7..98979ce582 100644 --- a/applications/system/js_app/modules/js_flipper.h +++ b/applications/system/js_app/modules/js_flipper.h @@ -1,4 +1,4 @@ #pragma once #include "../js_thread_i.h" -void* js_flipper_create(struct mjs* mjs, mjs_val_t* object); +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules); diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c new file mode 100644 index 0000000000..70021968fa --- /dev/null +++ b/applications/system/js_app/modules/js_gpio.c @@ -0,0 +1,345 @@ +#include "../js_modules.h" // IWYU pragma: keep +#include "./js_event_loop/js_event_loop.h" +#include +#include +#include +#include +#include + +#define INTERRUPT_QUEUE_LEN 16 + +/** + * Per-pin control structure + */ +typedef struct { + const GpioPin* pin; + bool had_interrupt; + FuriSemaphore* interrupt_semaphore; + JsEventLoopContract* interrupt_contract; + FuriHalAdcChannel adc_channel; + FuriHalAdcHandle* adc_handle; +} JsGpioPinInst; + +ARRAY_DEF(ManagedPinsArray, JsGpioPinInst*, M_PTR_OPLIST); //-V575 + +/** + * Per-module instance control structure + */ +typedef struct { + FuriEventLoop* loop; + ManagedPinsArray_t managed_pins; + FuriHalAdcHandle* adc_handle; +} JsGpioInst; + +/** + * @brief Interrupt callback + */ +static void js_gpio_int_cb(void* arg) { + furi_assert(arg); + FuriSemaphore* semaphore = arg; + furi_semaphore_release(semaphore); +} + +/** + * @brief Initializes a GPIO pin according to the provided mode object + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * led.init({ direction: "out", outMode: "push_pull" }); + * ``` + */ +static void js_gpio_init(struct mjs* mjs) { + // deconstruct mode object + mjs_val_t mode_arg; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&mode_arg)); + mjs_val_t direction_arg = mjs_get(mjs, mode_arg, "direction", ~0); + mjs_val_t out_mode_arg = mjs_get(mjs, mode_arg, "outMode", ~0); + mjs_val_t in_mode_arg = mjs_get(mjs, mode_arg, "inMode", ~0); + mjs_val_t edge_arg = mjs_get(mjs, mode_arg, "edge", ~0); + mjs_val_t pull_arg = mjs_get(mjs, mode_arg, "pull", ~0); + + // get strings + const char* direction = mjs_get_string(mjs, &direction_arg, NULL); + const char* out_mode = mjs_get_string(mjs, &out_mode_arg, NULL); + const char* in_mode = mjs_get_string(mjs, &in_mode_arg, NULL); + const char* edge = mjs_get_string(mjs, &edge_arg, NULL); + const char* pull = mjs_get_string(mjs, &pull_arg, NULL); + if(!direction) + JS_ERROR_AND_RETURN( + mjs, MJS_BAD_ARGS_ERROR, "Expected string in \"direction\" field of mode object"); + if(!out_mode) out_mode = "open_drain"; + if(!in_mode) in_mode = "plain_digital"; + if(!edge) edge = "rising"; + + // convert strings to mode + GpioMode mode; + if(strcmp(direction, "out") == 0) { + if(strcmp(out_mode, "push_pull") == 0) + mode = GpioModeOutputPushPull; + else if(strcmp(out_mode, "open_drain") == 0) + mode = GpioModeOutputOpenDrain; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid outMode"); + } else if(strcmp(direction, "in") == 0) { + if(strcmp(in_mode, "analog") == 0) { + mode = GpioModeAnalog; + } else if(strcmp(in_mode, "plain_digital") == 0) { + mode = GpioModeInput; + } else if(strcmp(in_mode, "interrupt") == 0) { + if(strcmp(edge, "rising") == 0) + mode = GpioModeInterruptRise; + else if(strcmp(edge, "falling") == 0) + mode = GpioModeInterruptFall; + else if(strcmp(edge, "both") == 0) + mode = GpioModeInterruptRiseFall; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); + } else if(strcmp(in_mode, "event") == 0) { + if(strcmp(edge, "rising") == 0) + mode = GpioModeEventRise; + else if(strcmp(edge, "falling") == 0) + mode = GpioModeEventFall; + else if(strcmp(edge, "both") == 0) + mode = GpioModeEventRiseFall; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid inMode"); + } + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid direction"); + } + + // convert pull + GpioPull pull_mode; + if(!pull) { + pull_mode = GpioPullNo; + } else if(strcmp(pull, "up") == 0) { + pull_mode = GpioPullUp; + } else if(strcmp(pull, "down") == 0) { + pull_mode = GpioPullDown; + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid pull"); + } + + // init GPIO + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + furi_hal_gpio_init(manager_data->pin, mode, pull_mode, GpioSpeedVeryHigh); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Writes a logic value to a GPIO pin + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * led.init({ direction: "out", outMode: "push_pull" }); + * led.write(true); + * ``` + */ +static void js_gpio_write(struct mjs* mjs) { + bool level; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_BOOL(&level)); + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + furi_hal_gpio_write(manager_data->pin, level); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Reads a logic value from a GPIO pin + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let button = gpio.get("pc1"); + * button.init({ direction: "in" }); + * if(button.read()) + * print("hi button!!!!!"); + * ``` + */ +static void js_gpio_read(struct mjs* mjs) { + // get level + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + bool value = furi_hal_gpio_read(manager_data->pin); + mjs_return(mjs, mjs_mk_boolean(mjs, value)); +} + +/** + * @brief Returns a event loop contract that can be used to listen to interrupts + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let button = gpio.get("pc1"); + * let event_loop = require("event_loop"); + * button.init({ direction: "in", pull: "up", inMode: "interrupt", edge: "falling" }); + * event_loop.subscribe(button.interrupt(), function (_) { print("Hi!"); }); + * event_loop.run(); + * ``` + */ +static void js_gpio_interrupt(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + + // interrupt handling + if(!manager_data->had_interrupt) { + furi_hal_gpio_add_int_callback( + manager_data->pin, js_gpio_int_cb, manager_data->interrupt_semaphore); + furi_hal_gpio_enable_int_callback(manager_data->pin); + manager_data->had_interrupt = true; + } + + // make contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = manager_data->interrupt_semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + }, + }; + manager_data->interrupt_contract = contract; + mjs_return(mjs, mjs_mk_foreign(mjs, contract)); +} + +/** + * @brief Reads a voltage from a GPIO pin in analog mode + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let pot = gpio.get("pc0"); + * pot.init({ direction: "in", inMode: "analog" }); + * print("voltage:" pot.read_analog(), "mV"); + * ``` + */ +static void js_gpio_read_analog(struct mjs* mjs) { + // get mV (ADC is configured for 12 bits and 2048 mV max) + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + uint16_t millivolts = + furi_hal_adc_read(manager_data->adc_handle, manager_data->adc_channel) / 2; + mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts)); +} + +/** + * @brief Returns an object that manages a specified pin. + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * ``` + */ +static void js_gpio_get(struct mjs* mjs) { + mjs_val_t name_arg; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&name_arg)); + const char* name_string = mjs_get_string(mjs, &name_arg, NULL); + const GpioPinRecord* pin_record = NULL; + + // parse input argument to a pin pointer + if(name_string) { + pin_record = furi_hal_resources_pin_by_name(name_string); + } else if(mjs_is_number(name_arg)) { + int name_int = mjs_get_int(mjs, name_arg); + pin_record = furi_hal_resources_pin_by_number(name_int); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Must be either a string or a number"); + } + + if(!pin_record) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Pin not found on device"); + if(pin_record->debug) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Pin is used for debugging"); + + // return pin manager object + JsGpioInst* module = JS_GET_CONTEXT(mjs); + mjs_val_t manager = mjs_mk_object(mjs); + JsGpioPinInst* manager_data = malloc(sizeof(JsGpioPinInst)); + manager_data->pin = pin_record->pin; + manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0); + manager_data->adc_handle = module->adc_handle; + manager_data->adc_channel = pin_record->channel; + mjs_own(mjs, &manager); + mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data)); + mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init)); + mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write)); + mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read)); + mjs_set(mjs, manager, "read_analog", ~0, MJS_MK_FN(js_gpio_read_analog)); + mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt)); + mjs_return(mjs, manager); + + // remember pin + ManagedPinsArray_push_back(module->managed_pins, manager_data); +} + +static void* js_gpio_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + JsEventLoop* js_loop = js_module_get(modules, "event_loop"); + if(M_UNLIKELY(!js_loop)) return NULL; + FuriEventLoop* loop = js_event_loop_get_loop(js_loop); + + JsGpioInst* module = malloc(sizeof(JsGpioInst)); + ManagedPinsArray_init(module->managed_pins); + module->adc_handle = furi_hal_adc_acquire(); + module->loop = loop; + furi_hal_adc_configure(module->adc_handle); + + mjs_val_t gpio_obj = mjs_mk_object(mjs); + mjs_set(mjs, gpio_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module)); + mjs_set(mjs, gpio_obj, "get", ~0, MJS_MK_FN(js_gpio_get)); + *object = gpio_obj; + + return (void*)module; +} + +static void js_gpio_destroy(void* inst) { + furi_assert(inst); + JsGpioInst* module = (JsGpioInst*)inst; + + // reset pins + ManagedPinsArray_it_t iterator; + for(ManagedPinsArray_it(iterator, module->managed_pins); !ManagedPinsArray_end_p(iterator); + ManagedPinsArray_next(iterator)) { + JsGpioPinInst* manager_data = *ManagedPinsArray_cref(iterator); + if(manager_data->had_interrupt) { + furi_hal_gpio_disable_int_callback(manager_data->pin); + furi_hal_gpio_remove_int_callback(manager_data->pin); + } + furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); + furi_semaphore_free(manager_data->interrupt_semaphore); + free(manager_data->interrupt_contract); + free(manager_data); + } + + // free buffers + furi_hal_adc_release(module->adc_handle); + ManagedPinsArray_clear(module->managed_pins); + free(module); +} + +static const JsModuleDescriptor js_gpio_desc = { + "gpio", + js_gpio_create, + js_gpio_destroy, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_gpio_desc, +}; + +const FlipperAppPluginDescriptor* js_gpio_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_gui/dialog.c b/applications/system/js_app/modules/js_gui/dialog.c new file mode 100644 index 0000000000..31eee237f6 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/dialog.c @@ -0,0 +1,129 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define QUEUE_LEN 2 + +typedef struct { + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsDialogCtx; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsDialogCtx* context) { + UNUSED(context); + DialogExResult result; + furi_check(furi_message_queue_get(queue, &result, 0) == FuriStatusOk); + const char* string; + if(result == DialogExResultLeft) { + string = "left"; + } else if(result == DialogExResultCenter) { + string = "center"; + } else if(result == DialogExResultRight) { + string = "right"; + } else { + furi_crash(); + } + return mjs_mk_string(mjs, string, ~0, false); +} + +static void input_callback(DialogExResult result, JsDialogCtx* context) { + furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk); +} + +static bool + header_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_header(dialog, value.string, 64, 0, AlignCenter, AlignTop); + return true; +} + +static bool + text_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_text(dialog, value.string, 64, 32, AlignCenter, AlignCenter); + return true; +} + +static bool + left_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_left_button_text(dialog, value.string); + return true; +} +static bool + center_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_center_button_text(dialog, value.string); + return true; +} +static bool + right_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_right_button_text(dialog, value.string); + return true; +} + +static JsDialogCtx* ctx_make(struct mjs* mjs, DialogEx* dialog, mjs_val_t view_obj) { + JsDialogCtx* context = malloc(sizeof(JsDialogCtx)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(DialogExResult)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + }, + }; + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + dialog_ex_set_result_callback(dialog, (DialogExResultCallback)input_callback); + dialog_ex_set_context(dialog, context); + return context; +} + +static void ctx_destroy(DialogEx* input, JsDialogCtx* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)dialog_ex_alloc, + .free = (JsViewFree)dialog_ex_free, + .get_view = (JsViewGetView)dialog_ex_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 5, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "text", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)text_assign}, + (JsViewPropDescriptor){ + .name = "left", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)left_assign}, + (JsViewPropDescriptor){ + .name = "center", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)center_assign}, + (JsViewPropDescriptor){ + .name = "right", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)right_assign}, + }}; + +JS_GUI_VIEW_DEF(dialog, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/empty_screen.c b/applications/system/js_app/modules/js_gui/empty_screen.c new file mode 100644 index 0000000000..9684eabdc1 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/empty_screen.c @@ -0,0 +1,12 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)empty_screen_alloc, + .free = (JsViewFree)empty_screen_free, + .get_view = (JsViewGetView)empty_screen_get_view, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(empty_screen, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c new file mode 100644 index 0000000000..8ac3055d5d --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -0,0 +1,348 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "./js_gui.h" +#include +#include +#include +#include "../js_event_loop/js_event_loop.h" +#include + +#define EVENT_QUEUE_SIZE 16 + +typedef struct { + uint32_t next_view_id; + FuriEventLoop* loop; + Gui* gui; + ViewDispatcher* dispatcher; + // event stuff + JsEventLoopContract custom_contract; + FuriMessageQueue* custom; + JsEventLoopContract navigation_contract; + FuriSemaphore* + navigation; // FIXME: (-nofl) convert into callback once FuriEventLoop starts supporting this +} JsGui; + +// Useful for factories +static JsGui* js_gui; + +typedef struct { + uint32_t id; + const JsViewDescriptor* descriptor; + void* specific_view; + void* custom_data; +} JsGuiViewData; + +/** + * @brief Transformer for custom events + */ +static mjs_val_t + js_gui_vd_custom_transformer(struct mjs* mjs, FuriEventLoopObject* object, void* context) { + UNUSED(context); + furi_check(object); + FuriMessageQueue* queue = object; + uint32_t event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + return mjs_mk_number(mjs, (double)event); +} + +/** + * @brief ViewDispatcher custom event callback + */ +static bool js_gui_vd_custom_callback(void* context, uint32_t event) { + furi_check(context); + JsGui* module = context; + furi_check(furi_message_queue_put(module->custom, &event, 0) == FuriStatusOk); + return true; +} + +/** + * @brief ViewDispatcher navigation event callback + */ +static bool js_gui_vd_nav_callback(void* context) { + furi_check(context); + JsGui* module = context; + furi_semaphore_release(module->navigation); + return true; +} + +/** + * @brief `viewDispatcher.sendCustom` + */ +static void js_gui_vd_send_custom(struct mjs* mjs) { + int32_t event; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&event)); + + JsGui* module = JS_GET_CONTEXT(mjs); + view_dispatcher_send_custom_event(module->dispatcher, (uint32_t)event); +} + +/** + * @brief `viewDispatcher.sendTo` + */ +static void js_gui_vd_send_to(struct mjs* mjs) { + enum { + SendDirToFront, + SendDirToBack, + } send_direction; + JS_ENUM_MAP(send_direction, {"front", SendDirToFront}, {"back", SendDirToBack}); + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ENUM(send_direction, "SendDirection")); + + JsGui* module = JS_GET_CONTEXT(mjs); + if(send_direction == SendDirToBack) { + view_dispatcher_send_to_back(module->dispatcher); + } else { + view_dispatcher_send_to_front(module->dispatcher); + } +} + +/** + * @brief `viewDispatcher.switchTo` + */ +static void js_gui_vd_switch_to(struct mjs* mjs) { + mjs_val_t view; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view)); + JsGuiViewData* view_data = JS_GET_INST(mjs, view); + JsGui* module = JS_GET_CONTEXT(mjs); + view_dispatcher_switch_to_view(module->dispatcher, (uint32_t)view_data->id); +} + +static void* js_gui_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + // get event loop + JsEventLoop* js_loop = js_module_get(modules, "event_loop"); + if(M_UNLIKELY(!js_loop)) return NULL; + FuriEventLoop* loop = js_event_loop_get_loop(js_loop); + + // create C object + JsGui* module = malloc(sizeof(JsGui)); + module->loop = loop; + module->gui = furi_record_open(RECORD_GUI); + module->dispatcher = view_dispatcher_alloc_ex(loop); + module->custom = furi_message_queue_alloc(EVENT_QUEUE_SIZE, sizeof(uint32_t)); + module->navigation = furi_semaphore_alloc(EVENT_QUEUE_SIZE, 0); + view_dispatcher_attach_to_gui(module->dispatcher, module->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_send_to_front(module->dispatcher); + + // subscribe to events and create contracts + view_dispatcher_set_event_callback_context(module->dispatcher, module); + view_dispatcher_set_custom_event_callback(module->dispatcher, js_gui_vd_custom_callback); + view_dispatcher_set_navigation_event_callback(module->dispatcher, js_gui_vd_nav_callback); + module->custom_contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object = module->custom, + .object_type = JsEventLoopObjectTypeQueue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = js_gui_vd_custom_transformer, + }, + }; + module->navigation_contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object = module->navigation, + .object_type = JsEventLoopObjectTypeSemaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + }, + }; + + // create viewDispatcher object + mjs_val_t view_dispatcher = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, view_dispatcher) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, module)); + JS_FIELD("sendCustom", MJS_MK_FN(js_gui_vd_send_custom)); + JS_FIELD("sendTo", MJS_MK_FN(js_gui_vd_send_to)); + JS_FIELD("switchTo", MJS_MK_FN(js_gui_vd_switch_to)); + JS_FIELD("custom", mjs_mk_foreign(mjs, &module->custom_contract)); + JS_FIELD("navigation", mjs_mk_foreign(mjs, &module->navigation_contract)); + } + + // create API object + mjs_val_t api = mjs_mk_object(mjs); + mjs_set(mjs, api, "viewDispatcher", ~0, view_dispatcher); + + *object = api; + js_gui = module; + return module; +} + +static void js_gui_destroy(void* inst) { + furi_assert(inst); + JsGui* module = inst; + + view_dispatcher_free(module->dispatcher); + furi_event_loop_maybe_unsubscribe(module->loop, module->custom); + furi_event_loop_maybe_unsubscribe(module->loop, module->navigation); + furi_message_queue_free(module->custom); + furi_semaphore_free(module->navigation); + + furi_record_close(RECORD_GUI); + free(module); + js_gui = NULL; +} + +/** + * @brief Assigns a `View` property. Not available from JS. + */ +static bool + js_gui_view_assign(struct mjs* mjs, const char* name, mjs_val_t value, JsGuiViewData* data) { + const JsViewDescriptor* descriptor = data->descriptor; + for(size_t i = 0; i < descriptor->prop_cnt; i++) { + JsViewPropDescriptor prop = descriptor->props[i]; + if(strcmp(prop.name, name) != 0) continue; + + // convert JS value to C + JsViewPropValue c_value; + const char* expected_type = NULL; + switch(prop.type) { + case JsViewPropTypeNumber: { + if(!mjs_is_number(value)) { + expected_type = "number"; + break; + } + c_value = (JsViewPropValue){.number = mjs_get_int32(mjs, value)}; + } break; + case JsViewPropTypeString: { + if(!mjs_is_string(value)) { + expected_type = "string"; + break; + } + c_value = (JsViewPropValue){.string = mjs_get_string(mjs, &value, NULL)}; + } break; + case JsViewPropTypeArr: { + if(!mjs_is_array(value)) { + expected_type = "array"; + break; + } + c_value = (JsViewPropValue){.array = value}; + } break; + } + + if(expected_type) { + mjs_prepend_errorf( + mjs, MJS_BAD_ARGS_ERROR, "view prop \"%s\" requires %s value", name, expected_type); + return false; + } else { + return prop.assign(mjs, data->specific_view, c_value, data->custom_data); + } + } + + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "view has no prop named \"%s\"", name); + return false; +} + +/** + * @brief `View.set` + */ +static void js_gui_view_set(struct mjs* mjs) { + const char* name; + mjs_val_t value; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name), JS_ARG_ANY(&value)); + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + bool success = js_gui_view_assign(mjs, name, value, data); + UNUSED(success); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief `View` destructor + */ +static void js_gui_view_destructor(struct mjs* mjs, mjs_val_t obj) { + JsGuiViewData* data = JS_GET_INST(mjs, obj); + view_dispatcher_remove_view(js_gui->dispatcher, data->id); + if(data->descriptor->custom_destroy) + data->descriptor->custom_destroy(data->specific_view, data->custom_data, js_gui->loop); + data->descriptor->free(data->specific_view); + free(data); +} + +/** + * @brief Creates a `View` object from a descriptor. Not available from JS. + */ +static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descriptor) { + void* specific_view = descriptor->alloc(); + View* view = descriptor->get_view(specific_view); + uint32_t view_id = js_gui->next_view_id++; + view_dispatcher_add_view(js_gui->dispatcher, view_id, view); + + // generic view API + mjs_val_t view_obj = mjs_mk_object(mjs); + mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set)); + + // object data + JsGuiViewData* data = malloc(sizeof(JsGuiViewData)); + *data = (JsGuiViewData){ + .descriptor = descriptor, + .id = view_id, + .specific_view = specific_view, + .custom_data = + descriptor->custom_make ? descriptor->custom_make(mjs, specific_view, view_obj) : NULL, + }; + mjs_set(mjs, view_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, data)); + mjs_set(mjs, view_obj, MJS_DESTRUCTOR_PROP_NAME, ~0, MJS_MK_FN(js_gui_view_destructor)); + + return view_obj; +} + +/** + * @brief `ViewFactory.make` + */ +static void js_gui_vf_make(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); + mjs_return(mjs, js_gui_make_view(mjs, descriptor)); +} + +/** + * @brief `ViewFactory.makeWith` + */ +static void js_gui_vf_make_with(struct mjs* mjs) { + mjs_val_t props; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&props)); + const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); + + // make the object like normal + mjs_val_t view_obj = js_gui_make_view(mjs, descriptor); + JsGuiViewData* data = JS_GET_INST(mjs, view_obj); + + // assign properties one by one + mjs_val_t key, iter = MJS_UNDEFINED; + while((key = mjs_next(mjs, props, &iter)) != MJS_UNDEFINED) { + furi_check(mjs_is_string(key)); + const char* name = mjs_get_string(mjs, &key, NULL); + mjs_val_t value = mjs_get(mjs, props, name, ~0); + + if(!js_gui_view_assign(mjs, name, value, data)) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + } + + mjs_return(mjs, view_obj); +} + +mjs_val_t js_gui_make_view_factory(struct mjs* mjs, const JsViewDescriptor* view_descriptor) { + mjs_val_t factory = mjs_mk_object(mjs); + mjs_set(mjs, factory, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, (void*)view_descriptor)); + mjs_set(mjs, factory, "make", ~0, MJS_MK_FN(js_gui_vf_make)); + mjs_set(mjs, factory, "makeWith", ~0, MJS_MK_FN(js_gui_vf_make_with)); + return factory; +} + +extern const ElfApiInterface js_gui_hashtable_api_interface; + +static const JsModuleDescriptor js_gui_desc = { + "gui", + js_gui_create, + js_gui_destroy, + &js_gui_hashtable_api_interface, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_gui_desc, +}; + +const FlipperAppPluginDescriptor* js_gui_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h new file mode 100644 index 0000000000..02198ca4f3 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -0,0 +1,116 @@ +#include "../../js_modules.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + JsViewPropTypeString, + JsViewPropTypeNumber, + JsViewPropTypeArr, +} JsViewPropType; + +typedef union { + const char* string; + int32_t number; + mjs_val_t array; +} JsViewPropValue; + +/** + * @brief Assigns a value to a view property + * + * The name and the type are implicit and defined in the property descriptor + */ +typedef bool ( + *JsViewPropAssign)(struct mjs* mjs, void* specific_view, JsViewPropValue value, void* context); + +/** @brief Property descriptor */ +typedef struct { + const char* name; // get_view -> [custom_make (if set)] -> props[i].assign -> [custom_destroy (if_set)] -> free +// \_______________ creation ________________/ \___ usage ___/ \_________ destruction _________/ + +/** + * @brief Creates a JS `ViewFactory` object + * + * This function is intended to be used by individual view adapter modules that + * wish to create a unified JS API interface in a declarative way. Usually this + * is done via the `JS_GUI_VIEW_DEF` macro which hides all the boilerplate. + * + * The `ViewFactory` object exposes two methods, `make` and `makeWith`, each + * returning a `View` object. These objects fully comply with the expectations + * of the `ViewDispatcher`, TS type definitions and the proposed Flipper JS + * coding style. + */ +mjs_val_t js_gui_make_view_factory(struct mjs* mjs, const JsViewDescriptor* view_descriptor); + +/** + * @brief Defines a module implementing `View` glue code + */ +#define JS_GUI_VIEW_DEF(name, descriptor) \ + static void* view_mod_ctor(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { \ + UNUSED(modules); \ + *object = js_gui_make_view_factory(mjs, descriptor); \ + return NULL; \ + } \ + static const JsModuleDescriptor js_mod_desc = { \ + "gui__" #name, \ + view_mod_ctor, \ + NULL, \ + NULL, \ + }; \ + static const FlipperAppPluginDescriptor plugin_descriptor = { \ + .appid = PLUGIN_APP_ID, \ + .ep_api_version = PLUGIN_API_VERSION, \ + .entry_point = &js_mod_desc, \ + }; \ + const FlipperAppPluginDescriptor* js_view_##name##_ep(void) { \ + return &plugin_descriptor; \ + } + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp b/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp new file mode 100644 index 0000000000..2be9cb3b21 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp @@ -0,0 +1,16 @@ +#include +#include + +#include "js_gui_api_table_i.h" + +static_assert(!has_hash_collisions(js_gui_api_table), "Detected API method hash collision!"); + +extern "C" constexpr HashtableApiInterface js_gui_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + js_gui_api_table.cbegin(), + js_gui_api_table.cend(), +}; diff --git a/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h new file mode 100644 index 0000000000..852b3d1071 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h @@ -0,0 +1,4 @@ +#include "js_gui.h" + +static constexpr auto js_gui_api_table = sort(create_array_t( + API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)))); diff --git a/applications/system/js_app/modules/js_gui/loading.c b/applications/system/js_app/modules/js_gui/loading.c new file mode 100644 index 0000000000..e291824a07 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/loading.c @@ -0,0 +1,12 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)loading_alloc, + .free = (JsViewFree)loading_free, + .get_view = (JsViewGetView)loading_get_view, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(loading, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/submenu.c b/applications/system/js_app/modules/js_gui/submenu.c new file mode 100644 index 0000000000..aecd413be4 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/submenu.c @@ -0,0 +1,87 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define QUEUE_LEN 2 + +typedef struct { + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsSubmenuCtx; + +static mjs_val_t choose_transformer(struct mjs* mjs, FuriMessageQueue* queue, void* context) { + UNUSED(context); + uint32_t index; + furi_check(furi_message_queue_get(queue, &index, 0) == FuriStatusOk); + return mjs_mk_number(mjs, (double)index); +} + +void choose_callback(void* context, uint32_t index) { + JsSubmenuCtx* ctx = context; + furi_check(furi_message_queue_put(ctx->queue, &index, 0) == FuriStatusOk); +} + +static bool + header_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { + UNUSED(mjs); + UNUSED(context); + submenu_set_header(submenu, value.string); + return true; +} + +static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { + UNUSED(mjs); + submenu_reset(submenu); + size_t len = mjs_array_length(mjs, value.array); + for(size_t i = 0; i < len; i++) { + mjs_val_t item = mjs_array_get(mjs, value.array, i); + if(!mjs_is_string(item)) return false; + submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context); + } + return true; +} + +static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) { + UNUSED(input); + JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(uint32_t)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)choose_transformer, + }, + }; + mjs_set(mjs, view_obj, "chosen", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(Submenu* input, JsSubmenuCtx* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)submenu_alloc, + .free = (JsViewFree)submenu_free, + .get_view = (JsViewGetView)submenu_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 2, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "items", + .type = JsViewPropTypeArr, + .assign = (JsViewPropAssign)items_assign}, + }}; +JS_GUI_VIEW_DEF(submenu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/text_box.c b/applications/system/js_app/modules/js_gui/text_box.c new file mode 100644 index 0000000000..4e6c8247ca --- /dev/null +++ b/applications/system/js_app/modules/js_gui/text_box.c @@ -0,0 +1,78 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static bool + text_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, FuriString* context) { + UNUSED(mjs); + furi_string_set(context, value.string); + text_box_set_text(text_box, furi_string_get_cstr(context)); + return true; +} + +static bool font_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, void* context) { + UNUSED(context); + TextBoxFont font; + if(strcasecmp(value.string, "hex") == 0) { + font = TextBoxFontHex; + } else if(strcasecmp(value.string, "text") == 0) { + font = TextBoxFontText; + } else { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "must be one of: \"text\", \"hex\""); + return false; + } + text_box_set_font(text_box, font); + return true; +} + +static bool + focus_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, void* context) { + UNUSED(context); + TextBoxFocus focus; + if(strcasecmp(value.string, "start") == 0) { + focus = TextBoxFocusStart; + } else if(strcasecmp(value.string, "end") == 0) { + focus = TextBoxFocusEnd; + } else { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "must be one of: \"start\", \"end\""); + return false; + } + text_box_set_focus(text_box, focus); + return true; +} + +FuriString* ctx_make(struct mjs* mjs, TextBox* specific_view, mjs_val_t view_obj) { + UNUSED(mjs); + UNUSED(specific_view); + UNUSED(view_obj); + return furi_string_alloc(); +} + +void ctx_destroy(TextBox* specific_view, FuriString* context, FuriEventLoop* loop) { + UNUSED(specific_view); + UNUSED(loop); + furi_string_free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)text_box_alloc, + .free = (JsViewFree)text_box_free, + .get_view = (JsViewGetView)text_box_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "text", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)text_assign}, + (JsViewPropDescriptor){ + .name = "font", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)font_assign}, + (JsViewPropDescriptor){ + .name = "focus", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)focus_assign}, + }}; +JS_GUI_VIEW_DEF(text_box, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/text_input.c b/applications/system/js_app/modules/js_gui/text_input.c new file mode 100644 index 0000000000..575029f8e6 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/text_input.c @@ -0,0 +1,120 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define DEFAULT_BUF_SZ 33 + +typedef struct { + char* buffer; + size_t buffer_size; + FuriString* header; + FuriSemaphore* input_semaphore; + JsEventLoopContract contract; +} JsKbdContext; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriSemaphore* semaphore, JsKbdContext* context) { + furi_check(furi_semaphore_acquire(semaphore, 0) == FuriStatusOk); + return mjs_mk_string(mjs, context->buffer, ~0, true); +} + +static void input_callback(JsKbdContext* context) { + furi_semaphore_release(context->input_semaphore); +} + +static bool + header_assign(struct mjs* mjs, TextInput* input, JsViewPropValue value, JsKbdContext* context) { + UNUSED(mjs); + furi_string_set(context->header, value.string); + text_input_set_header_text(input, furi_string_get_cstr(context->header)); + return true; +} + +static bool min_len_assign( + struct mjs* mjs, + TextInput* input, + JsViewPropValue value, + JsKbdContext* context) { + UNUSED(mjs); + UNUSED(context); + text_input_set_minimum_length(input, (size_t)value.number); + return true; +} + +static bool max_len_assign( + struct mjs* mjs, + TextInput* input, + JsViewPropValue value, + JsKbdContext* context) { + UNUSED(mjs); + context->buffer_size = (size_t)(value.number + 1); + context->buffer = realloc(context->buffer, context->buffer_size); //-V701 + text_input_set_result_callback( + input, + (TextInputCallback)input_callback, + context, + context->buffer, + context->buffer_size, + true); + return true; +} + +static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_obj) { + UNUSED(input); + JsKbdContext* context = malloc(sizeof(JsKbdContext)); + *context = (JsKbdContext){ + .buffer_size = DEFAULT_BUF_SZ, + .buffer = malloc(DEFAULT_BUF_SZ), + .header = furi_string_alloc(), + .input_semaphore = furi_semaphore_alloc(1, 0), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = context->input_semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + UNUSED(mjs); + UNUSED(view_obj); + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(TextInput* input, JsKbdContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_semaphore); + furi_semaphore_free(context->input_semaphore); + furi_string_free(context->header); + free(context->buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)text_input_alloc, + .free = (JsViewFree)text_input_free, + .get_view = (JsViewGetView)text_input_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "minLength", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)min_len_assign}, + (JsViewPropDescriptor){ + .name = "maxLength", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)max_len_assign}, + }}; + +JS_GUI_VIEW_DEF(text_input, &view_descriptor); diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index d8812e61bb..7d54cf9b9f 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -305,7 +305,8 @@ void js_math_trunc(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); } -static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_math_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); mjs_val_t math_obj = mjs_mk_object(mjs); mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); @@ -342,6 +343,7 @@ static const JsModuleDescriptor js_math_desc = { "math", js_math_create, NULL, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_notification.c b/applications/system/js_app/modules/js_notification.c index 2f57c45d1b..994283a098 100644 --- a/applications/system/js_app/modules/js_notification.c +++ b/applications/system/js_app/modules/js_notification.c @@ -75,7 +75,8 @@ static void js_notify_blink(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_notification_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); mjs_val_t notify_obj = mjs_mk_object(mjs); mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification)); @@ -96,6 +97,7 @@ static const JsModuleDescriptor js_notification_desc = { "notification", js_notification_create, js_notification_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index 234eefb432..7aea927837 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -573,7 +573,8 @@ static void js_serial_expect(struct mjs* mjs) { } } -static void* js_serial_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); js_serial->mjs = mjs; mjs_val_t serial_obj = mjs_mk_object(mjs); @@ -606,6 +607,7 @@ static const JsModuleDescriptor js_serial_desc = { "serial", js_serial_create, js_serial_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c new file mode 100644 index 0000000000..1d4053a5f7 --- /dev/null +++ b/applications/system/js_app/modules/js_storage.c @@ -0,0 +1,383 @@ +#include "../js_modules.h" // IWYU pragma: keep +#include + +// ---=== file ops ===--- + +static void js_storage_file_close(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_close(file))); +} + +static void js_storage_file_is_open(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_is_open(file))); +} + +static void js_storage_file_read(struct mjs* mjs) { + enum { + ReadModeAscii, + ReadModeBinary, + } read_mode; + JS_ENUM_MAP(read_mode, {"ascii", ReadModeAscii}, {"binary", ReadModeBinary}); + int32_t length; + JS_FETCH_ARGS_OR_RETURN( + mjs, JS_EXACTLY, JS_ARG_ENUM(read_mode, "ReadMode"), JS_ARG_INT32(&length)); + File* file = JS_GET_CONTEXT(mjs); + char buffer[length]; + size_t actually_read = storage_file_read(file, buffer, length); + if(read_mode == ReadModeAscii) { + mjs_return(mjs, mjs_mk_string(mjs, buffer, actually_read, true)); + } else if(read_mode == ReadModeBinary) { + mjs_return(mjs, mjs_mk_array_buf(mjs, buffer, actually_read)); + } +} + +static void js_storage_file_write(struct mjs* mjs) { + mjs_val_t data; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&data)); + const void* buf; + size_t len; + if(mjs_is_string(data)) { + buf = mjs_get_string(mjs, &data, &len); + } else if(mjs_is_array_buf(data)) { + buf = mjs_array_buf_get_ptr(mjs, data, &len); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: expected string or ArrayBuffer"); + } + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_write(file, buf, len))); +} + +static void js_storage_file_seek_relative(struct mjs* mjs) { + int32_t offset; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, false))); +} + +static void js_storage_file_seek_absolute(struct mjs* mjs) { + int32_t offset; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, true))); +} + +static void js_storage_file_tell(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_tell(file))); +} + +static void js_storage_file_truncate(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_truncate(file))); +} + +static void js_storage_file_size(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_size(file))); +} + +static void js_storage_file_eof(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_eof(file))); +} + +static void js_storage_file_copy_to(struct mjs* mjs) { + File* source = JS_GET_CONTEXT(mjs); + mjs_val_t dest_obj; + int32_t bytes; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&dest_obj), JS_ARG_INT32(&bytes)); + File* destination = JS_GET_INST(mjs, dest_obj); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_copy_to_file(source, destination, bytes))); +} + +// ---=== top-level file ops ===--- + +// common destructor for file and dir objects +static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { + File* file = JS_GET_INST(mjs, obj); + storage_file_free(file); +} + +static void js_storage_open_file(struct mjs* mjs) { + const char* path; + FS_AccessMode access_mode; + FS_OpenMode open_mode; + JS_ENUM_MAP(access_mode, {"r", FSAM_READ}, {"w", FSAM_WRITE}, {"rw", FSAM_READ_WRITE}); + JS_ENUM_MAP( + open_mode, + {"open_existing", FSOM_OPEN_EXISTING}, + {"open_always", FSOM_OPEN_ALWAYS}, + {"open_append", FSOM_OPEN_APPEND}, + {"create_new", FSOM_CREATE_NEW}, + {"create_always", FSOM_CREATE_ALWAYS}); + JS_FETCH_ARGS_OR_RETURN( + mjs, + JS_EXACTLY, + JS_ARG_STR(&path), + JS_ARG_ENUM(access_mode, "AccessMode"), + JS_ARG_ENUM(open_mode, "OpenMode")); + + Storage* storage = JS_GET_CONTEXT(mjs); + File* file = storage_file_alloc(storage); + if(!storage_file_open(file, path, access_mode, open_mode)) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + mjs_val_t file_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, file_obj) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, file)); + JS_FIELD(MJS_DESTRUCTOR_PROP_NAME, MJS_MK_FN(js_storage_file_destructor)); + JS_FIELD("close", MJS_MK_FN(js_storage_file_close)); + JS_FIELD("isOpen", MJS_MK_FN(js_storage_file_is_open)); + JS_FIELD("read", MJS_MK_FN(js_storage_file_read)); + JS_FIELD("write", MJS_MK_FN(js_storage_file_write)); + JS_FIELD("seekRelative", MJS_MK_FN(js_storage_file_seek_relative)); + JS_FIELD("seekAbsolute", MJS_MK_FN(js_storage_file_seek_absolute)); + JS_FIELD("tell", MJS_MK_FN(js_storage_file_tell)); + JS_FIELD("truncate", MJS_MK_FN(js_storage_file_truncate)); + JS_FIELD("size", MJS_MK_FN(js_storage_file_size)); + JS_FIELD("eof", MJS_MK_FN(js_storage_file_eof)); + JS_FIELD("copyTo", MJS_MK_FN(js_storage_file_copy_to)); + } + mjs_return(mjs, file_obj); +} + +static void js_storage_file_exists(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_exists(storage, path))); +} + +// ---=== dir ops ===--- + +static void js_storage_read_directory(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + + Storage* storage = JS_GET_CONTEXT(mjs); + File* dir = storage_file_alloc(storage); + if(!storage_dir_open(dir, path)) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + FileInfo file_info; + char name[128]; + FuriString* file_path = furi_string_alloc_set_str(path); + size_t path_size = furi_string_size(file_path); + uint32_t timestamp; + + mjs_val_t ret = mjs_mk_array(mjs); + while(storage_dir_read(dir, &file_info, name, sizeof(name))) { + furi_string_left(file_path, path_size); + path_append(file_path, name); + furi_check( + storage_common_timestamp(storage, furi_string_get_cstr(file_path), ×tamp) == + FSE_OK); + mjs_val_t obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, obj) { + JS_FIELD("path", mjs_mk_string(mjs, name, ~0, true)); + JS_FIELD("isDirectory", mjs_mk_boolean(mjs, file_info_is_dir(&file_info))); + JS_FIELD("size", mjs_mk_number(mjs, file_info.size)); + JS_FIELD("timestamp", mjs_mk_number(mjs, timestamp)); + } + mjs_array_push(mjs, ret, obj); + } + + storage_file_free(dir); + furi_string_free(file_path); + mjs_return(mjs, ret); +} + +static void js_storage_directory_exists(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_dir_exists(storage, path))); +} + +static void js_storage_make_directory(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage, path))); +} + +// ---=== common ops ===--- + +static void js_storage_file_or_dir_exists(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage, path))); +} + +static void js_storage_stat(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + FileInfo file_info; + uint32_t timestamp; + if((storage_common_stat(storage, path, &file_info) | + storage_common_timestamp(storage, path, ×tamp)) != FSE_OK) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + mjs_val_t ret = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, ret) { + JS_FIELD("path", mjs_mk_string(mjs, path, ~0, 1)); + JS_FIELD("isDirectory", mjs_mk_boolean(mjs, file_info_is_dir(&file_info))); + JS_FIELD("size", mjs_mk_number(mjs, file_info.size)); + JS_FIELD("accessTime", mjs_mk_number(mjs, timestamp)); + } + mjs_return(mjs, ret); +} + +static void js_storage_remove(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage, path))); +} + +static void js_storage_rmrf(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove_recursive(storage, path))); +} + +static void js_storage_rename(struct mjs* mjs) { + const char *old, *new; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&old), JS_ARG_STR(&new)); + Storage* storage = JS_GET_CONTEXT(mjs); + FS_Error status = storage_common_rename(storage, old, new); + mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK)); +} + +static void js_storage_copy(struct mjs* mjs) { + const char *source, *dest; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&source), JS_ARG_STR(&dest)); + Storage* storage = JS_GET_CONTEXT(mjs); + FS_Error status = storage_common_copy(storage, source, dest); + mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK || status == FSE_EXIST)); +} + +static void js_storage_fs_info(struct mjs* mjs) { + const char* fs; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fs)); + Storage* storage = JS_GET_CONTEXT(mjs); + uint64_t total_space, free_space; + if(storage_common_fs_info(storage, fs, &total_space, &free_space) != FSE_OK) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + mjs_val_t ret = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, ret) { + JS_FIELD("totalSpace", mjs_mk_number(mjs, total_space)); + JS_FIELD("freeSpace", mjs_mk_number(mjs, free_space)); + } + mjs_return(mjs, ret); +} + +static void js_storage_next_available_filename(struct mjs* mjs) { + const char *dir_path, *file_name, *file_ext; + int32_t max_len; + JS_FETCH_ARGS_OR_RETURN( + mjs, + JS_EXACTLY, + JS_ARG_STR(&dir_path), + JS_ARG_STR(&file_name), + JS_ARG_STR(&file_ext), + JS_ARG_INT32(&max_len)); + Storage* storage = JS_GET_CONTEXT(mjs); + FuriString* next_name = furi_string_alloc(); + storage_get_next_filename(storage, dir_path, file_name, file_ext, next_name, max_len); + mjs_return(mjs, mjs_mk_string(mjs, furi_string_get_cstr(next_name), ~0, true)); + furi_string_free(next_name); +} + +// ---=== path ops ===--- + +static void js_storage_are_paths_equal(struct mjs* mjs) { + const char *path1, *path2; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path1), JS_ARG_STR(&path2)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_equivalent_path(storage, path1, path2))); +} + +static void js_storage_is_subpath_of(struct mjs* mjs) { + const char *parent, *child; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&parent), JS_ARG_STR(&child)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_is_subdir(storage, parent, child))); +} + +// ---=== module ctor & dtor ===--- + +static void* js_storage_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + Storage* storage = furi_record_open(RECORD_STORAGE); + UNUSED(storage); + *object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, *object) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, storage)); + + // top-level file ops + JS_FIELD("openFile", MJS_MK_FN(js_storage_open_file)); + JS_FIELD("fileExists", MJS_MK_FN(js_storage_file_exists)); + + // dir ops + JS_FIELD("readDirectory", MJS_MK_FN(js_storage_read_directory)); + JS_FIELD("directoryExists", MJS_MK_FN(js_storage_directory_exists)); + JS_FIELD("makeDirectory", MJS_MK_FN(js_storage_make_directory)); + + // common ops + JS_FIELD("fileOrDirExists", MJS_MK_FN(js_storage_file_or_dir_exists)); + JS_FIELD("stat", MJS_MK_FN(js_storage_stat)); + JS_FIELD("remove", MJS_MK_FN(js_storage_remove)); + JS_FIELD("rmrf", MJS_MK_FN(js_storage_rmrf)); + JS_FIELD("rename", MJS_MK_FN(js_storage_rename)); + JS_FIELD("copy", MJS_MK_FN(js_storage_copy)); + JS_FIELD("fsInfo", MJS_MK_FN(js_storage_fs_info)); + JS_FIELD("nextAvailableFilename", MJS_MK_FN(js_storage_next_available_filename)); + + // path ops + JS_FIELD("arePathsEqual", MJS_MK_FN(js_storage_are_paths_equal)); + JS_FIELD("isSubpathOf", MJS_MK_FN(js_storage_is_subpath_of)); + } + return NULL; +} + +static void js_storage_destroy(void* data) { + UNUSED(data); + furi_record_close(RECORD_STORAGE); +} + +// ---=== boilerplate ===--- + +static const JsModuleDescriptor js_storage_desc = { + "storage", + js_storage_create, + js_storage_destroy, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_storage_desc, +}; + +const FlipperAppPluginDescriptor* js_storage_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c deleted file mode 100644 index 5ab9bef77c..0000000000 --- a/applications/system/js_app/modules/js_submenu.c +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include -#include -#include -#include "../js_modules.h" - -typedef struct { - Submenu* submenu; - ViewHolder* view_holder; - FuriApiLock lock; - uint32_t result; - bool accepted; -} JsSubmenuInst; - -static JsSubmenuInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSubmenuInst* submenu = mjs_get_ptr(mjs, obj_inst); - furi_assert(submenu); - return submenu; -} - -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); -} - -static bool check_arg_count(struct mjs* mjs, size_t count) { - size_t num_args = mjs_nargs(mjs); - if(num_args != count) { - ret_bad_args(mjs, "Wrong argument count"); - return false; - } - return true; -} - -static void submenu_callback(void* context, uint32_t id) { - JsSubmenuInst* submenu = context; - submenu->result = id; - submenu->accepted = true; - api_lock_unlock(submenu->lock); -} - -static void submenu_exit(void* context) { - JsSubmenuInst* submenu = context; - submenu->result = 0; - submenu->accepted = false; - api_lock_unlock(submenu->lock); -} - -static void js_submenu_add_item(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; - - mjs_val_t label_arg = mjs_arg(mjs, 0); - const char* label = mjs_get_string(mjs, &label_arg, NULL); - if(!label) { - ret_bad_args(mjs, "Label must be a string"); - return; - } - - mjs_val_t id_arg = mjs_arg(mjs, 1); - if(!mjs_is_number(id_arg)) { - ret_bad_args(mjs, "Id must be a number"); - return; - } - int32_t id = mjs_get_int32(mjs, id_arg); - - submenu_add_item(submenu->submenu, label, id, submenu_callback, submenu); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_submenu_set_header(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; - - mjs_val_t header_arg = mjs_arg(mjs, 0); - const char* header = mjs_get_string(mjs, &header_arg, NULL); - if(!header) { - ret_bad_args(mjs, "Header must be a string"); - return; - } - - submenu_set_header(submenu->submenu, header); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_submenu_show(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - submenu->lock = api_lock_alloc_locked(); - Gui* gui = furi_record_open(RECORD_GUI); - submenu->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(submenu->view_holder, gui); - view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu); - - view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu)); - api_lock_wait_unlock(submenu->lock); - - view_holder_set_view(submenu->view_holder, NULL); - view_holder_free(submenu->view_holder); - furi_record_close(RECORD_GUI); - api_lock_free(submenu->lock); - - submenu_reset(submenu->submenu); - if(submenu->accepted) { - mjs_return(mjs, mjs_mk_number(mjs, submenu->result)); - } else { - mjs_return(mjs, MJS_UNDEFINED); - } -} - -static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) { - JsSubmenuInst* submenu = malloc(sizeof(JsSubmenuInst)); - mjs_val_t submenu_obj = mjs_mk_object(mjs); - mjs_set(mjs, submenu_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, submenu)); - mjs_set(mjs, submenu_obj, "addItem", ~0, MJS_MK_FN(js_submenu_add_item)); - mjs_set(mjs, submenu_obj, "setHeader", ~0, MJS_MK_FN(js_submenu_set_header)); - mjs_set(mjs, submenu_obj, "show", ~0, MJS_MK_FN(js_submenu_show)); - submenu->submenu = submenu_alloc(); - *object = submenu_obj; - return submenu; -} - -static void js_submenu_destroy(void* inst) { - JsSubmenuInst* submenu = inst; - submenu_free(submenu->submenu); - free(submenu); -} - -static const JsModuleDescriptor js_submenu_desc = { - "submenu", - js_submenu_create, - js_submenu_destroy, -}; - -static const FlipperAppPluginDescriptor submenu_plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_submenu_desc, -}; - -const FlipperAppPluginDescriptor* js_submenu_ep(void) { - return &submenu_plugin_descriptor; -} diff --git a/applications/system/js_app/modules/js_tests.c b/applications/system/js_app/modules/js_tests.c new file mode 100644 index 0000000000..f275640000 --- /dev/null +++ b/applications/system/js_app/modules/js_tests.c @@ -0,0 +1,104 @@ +#include "../js_modules.h" // IWYU pragma: keep +#include +#include +#include + +#define TAG "JsTests" + +static void js_tests_fail(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 1); + mjs_val_t message_arg = mjs_arg(mjs, 0); + const char* message = mjs_get_string(mjs, &message_arg, NULL); + furi_check(message); + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "%s", message); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_tests_assert_eq(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 2); + + mjs_val_t expected_arg = mjs_arg(mjs, 0); + mjs_val_t result_arg = mjs_arg(mjs, 1); + + if(mjs_is_number(expected_arg) && mjs_is_number(result_arg)) { + int32_t expected = mjs_get_int32(mjs, expected_arg); + int32_t result = mjs_get_int32(mjs, result_arg); + if(expected == result) { + FURI_LOG_T(TAG, "eq passed (exp=%ld res=%ld)", expected, result); + } else { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "expected %d, found %d", expected, result); + } + } else if(mjs_is_string(expected_arg) && mjs_is_string(result_arg)) { + const char* expected = mjs_get_string(mjs, &expected_arg, NULL); + const char* result = mjs_get_string(mjs, &result_arg, NULL); + if(strcmp(expected, result) == 0) { + FURI_LOG_T(TAG, "eq passed (exp=\"%s\" res=\"%s\")", expected, result); + } else { + mjs_prepend_errorf( + mjs, MJS_INTERNAL_ERROR, "expected \"%s\", found \"%s\"", expected, result); + } + } else if(mjs_is_boolean(expected_arg) && mjs_is_boolean(result_arg)) { + bool expected = mjs_get_bool(mjs, expected_arg); + bool result = mjs_get_bool(mjs, result_arg); + if(expected == result) { + FURI_LOG_T( + TAG, + "eq passed (exp=%s res=%s)", + expected ? "true" : "false", + result ? "true" : "false"); + } else { + mjs_prepend_errorf( + mjs, + MJS_INTERNAL_ERROR, + "expected %s, found %s", + expected ? "true" : "false", + result ? "true" : "false"); + } + } else { + JS_ERROR_AND_RETURN( + mjs, + MJS_INTERNAL_ERROR, + "type mismatch (expected %s, result %s)", + mjs_typeof(expected_arg), + mjs_typeof(result_arg)); + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_tests_assert_float_close(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 3); + + mjs_val_t expected_arg = mjs_arg(mjs, 0); + mjs_val_t result_arg = mjs_arg(mjs, 1); + mjs_val_t epsilon_arg = mjs_arg(mjs, 2); + furi_check(mjs_is_number(expected_arg)); + furi_check(mjs_is_number(result_arg)); + furi_check(mjs_is_number(epsilon_arg)); + double expected = mjs_get_double(mjs, expected_arg); + double result = mjs_get_double(mjs, result_arg); + double epsilon = mjs_get_double(mjs, epsilon_arg); + + if(ABS(expected - result) > epsilon) { + mjs_prepend_errorf( + mjs, + MJS_INTERNAL_ERROR, + "expected %f found %f (tolerance=%f)", + expected, + result, + epsilon); + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +void* js_tests_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + mjs_val_t tests_obj = mjs_mk_object(mjs); + mjs_set(mjs, tests_obj, "fail", ~0, MJS_MK_FN(js_tests_fail)); + mjs_set(mjs, tests_obj, "assert_eq", ~0, MJS_MK_FN(js_tests_assert_eq)); + mjs_set(mjs, tests_obj, "assert_float_close", ~0, MJS_MK_FN(js_tests_assert_float_close)); + *object = tests_obj; + + return (void*)1; +} diff --git a/applications/system/js_app/modules/js_tests.h b/applications/system/js_app/modules/js_tests.h new file mode 100644 index 0000000000..49f752c2b3 --- /dev/null +++ b/applications/system/js_app/modules/js_tests.h @@ -0,0 +1,5 @@ +#pragma once +#include "../js_thread_i.h" +#include "../js_modules.h" + +void* js_tests_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules); diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c deleted file mode 100644 index b90dbc153a..0000000000 --- a/applications/system/js_app/modules/js_textbox.c +++ /dev/null @@ -1,219 +0,0 @@ -#include -#include -#include "../js_modules.h" - -typedef struct { - TextBox* text_box; - ViewHolder* view_holder; - FuriString* text; - bool is_shown; -} JsTextboxInst; - -static JsTextboxInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsTextboxInst* textbox = mjs_get_ptr(mjs, obj_inst); - furi_assert(textbox); - return textbox; -} - -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); -} - -static bool check_arg_count(struct mjs* mjs, size_t count) { - size_t num_args = mjs_nargs(mjs); - if(num_args != count) { - ret_bad_args(mjs, "Wrong argument count"); - return false; - } - return true; -} - -static void js_textbox_set_config(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; - - TextBoxFocus set_focus = TextBoxFocusStart; - mjs_val_t focus_arg = mjs_arg(mjs, 0); - const char* focus = mjs_get_string(mjs, &focus_arg, NULL); - if(!focus) { - ret_bad_args(mjs, "Focus must be a string"); - return; - } else { - if(!strncmp(focus, "start", strlen("start"))) { - set_focus = TextBoxFocusStart; - } else if(!strncmp(focus, "end", strlen("end"))) { - set_focus = TextBoxFocusEnd; - } else { - ret_bad_args(mjs, "Bad focus value"); - return; - } - } - - TextBoxFont set_font = TextBoxFontText; - mjs_val_t font_arg = mjs_arg(mjs, 1); - const char* font = mjs_get_string(mjs, &font_arg, NULL); - if(!font) { - ret_bad_args(mjs, "Font must be a string"); - return; - } else { - if(!strncmp(font, "text", strlen("text"))) { - set_font = TextBoxFontText; - } else if(!strncmp(font, "hex", strlen("hex"))) { - set_font = TextBoxFontHex; - } else { - ret_bad_args(mjs, "Bad font value"); - return; - } - } - - text_box_set_focus(textbox->text_box, set_focus); - text_box_set_font(textbox->text_box, set_font); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_add_text(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; - - mjs_val_t text_arg = mjs_arg(mjs, 0); - size_t text_len = 0; - const char* text = mjs_get_string(mjs, &text_arg, &text_len); - if(!text) { - ret_bad_args(mjs, "Text must be a string"); - return; - } - - // Avoid condition race between GUI and JS thread - text_box_set_text(textbox->text_box, ""); - - size_t new_len = furi_string_size(textbox->text) + text_len; - if(new_len >= 4096) { - furi_string_right(textbox->text, new_len / 2); - } - - furi_string_cat(textbox->text, text); - - text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_clear_text(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - // Avoid condition race between GUI and JS thread - text_box_set_text(textbox->text_box, ""); - - furi_string_reset(textbox->text); - - text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_is_open(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); -} - -static void textbox_callback(void* context, uint32_t arg) { - UNUSED(arg); - JsTextboxInst* textbox = context; - view_holder_set_view(textbox->view_holder, NULL); - textbox->is_shown = false; -} - -static void textbox_exit(void* context) { - JsTextboxInst* textbox = context; - // Using timer to schedule view_holder stop, will not work under high CPU load - furi_timer_pending_callback(textbox_callback, textbox, 0); -} - -static void js_textbox_show(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - if(textbox->is_shown) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); - textbox->is_shown = true; - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_close(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - view_holder_set_view(textbox->view_holder, NULL); - textbox->is_shown = false; - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { - JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); - - mjs_val_t textbox_obj = mjs_mk_object(mjs); - mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); - mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); - mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); - mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); - mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); - mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); - mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); - - textbox->text = furi_string_alloc(); - textbox->text_box = text_box_alloc(); - - Gui* gui = furi_record_open(RECORD_GUI); - textbox->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(textbox->view_holder, gui); - view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); - - *object = textbox_obj; - return textbox; -} - -static void js_textbox_destroy(void* inst) { - JsTextboxInst* textbox = inst; - - view_holder_set_view(textbox->view_holder, NULL); - view_holder_free(textbox->view_holder); - textbox->view_holder = NULL; - - furi_record_close(RECORD_GUI); - - text_box_reset(textbox->text_box); - furi_string_reset(textbox->text); - - text_box_free(textbox->text_box); - furi_string_free(textbox->text); - free(textbox); -} - -static const JsModuleDescriptor js_textbox_desc = { - "textbox", - js_textbox_create, - js_textbox_destroy, -}; - -static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_textbox_desc, -}; - -const FlipperAppPluginDescriptor* js_textbox_ep(void) { - return &textbox_plugin_descriptor; -} diff --git a/applications/system/js_app/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h index b48221343f..b2debbde87 100644 --- a/applications/system/js_app/plugin_api/app_api_table_i.h +++ b/applications/system/js_app/plugin_api/app_api_table_i.h @@ -7,4 +7,5 @@ static constexpr auto app_api_table = sort(create_array_t( API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), - API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)))); + API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), + API_METHOD(js_module_get, void*, (JsModules*, const char*)))); diff --git a/applications/system/js_app/plugin_api/js_plugin_api.h b/applications/system/js_app/plugin_api/js_plugin_api.h index a817d34a90..421b685762 100644 --- a/applications/system/js_app/plugin_api/js_plugin_api.h +++ b/applications/system/js_app/plugin_api/js_plugin_api.h @@ -7,12 +7,16 @@ extern "C" { #endif +typedef void JsModules; + bool js_delay_with_flags(struct mjs* mjs, uint32_t time); void js_flags_set(struct mjs* mjs, uint32_t flags); uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); +void* js_module_get(JsModules* modules, const char* name); + #ifdef __cplusplus } #endif diff --git a/applications/system/js_app/types/badusb/index.d.ts b/applications/system/js_app/types/badusb/index.d.ts new file mode 100644 index 0000000000..2107909673 --- /dev/null +++ b/applications/system/js_app/types/badusb/index.d.ts @@ -0,0 +1,81 @@ +/** + * @brief Special key codes that this module recognizes + */ +export type ModifierKey = "CTRL" | "SHIFT" | "ALT" | "GUI"; + +export type MainKey = + "DOWN" | "LEFT" | "RIGHT" | "UP" | + + "ENTER" | "PAUSE" | "CAPSLOCK" | "DELETE" | "BACKSPACE" | "END" | "ESC" | + "HOME" | "INSERT" | "NUMLOCK" | "PAGEUP" | "PAGEDOWN" | "PRINTSCREEN" | + "SCROLLLOCK" | "SPACE" | "TAB" | "MENU" | + + "F1" | "F2" | "F3" | "F4" | "F5" | "F6" | "F7" | "F8" | "F9" | "F10" | + "F11" | "F12" | "F13" | "F14" | "F15" | "F16" | "F17" | "F18" | "F19" | + "F20" | "F21" | "F22" | "F23" | "F24" | + + "\n" | " " | "!" | "\"" | "#" | "$" | "%" | "&" | "'" | "(" | ")" | "*" | + "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | ">" | "=" | "?" | "@" | "[" | + "]" | "\\" | "^" | "_" | "`" | "{" | "}" | "|" | "~" | + + "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | + + "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | + "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | + "Y" | "Z" | + + "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | + "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | + "y" | "z"; + +export type KeyCode = MainKey | ModifierKey | number; + +/** + * @brief Initializes the module + * @param settings USB device settings. Omit to select default parameters + */ +export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string }): void; + +/** + * @brief Tells whether the virtual USB HID device has successfully connected + */ +export declare function isConnected(): boolean; + +/** + * @brief Presses one or multiple keys at once, then releases them + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function press(...keys: KeyCode[]): void; + +/** + * @brief Presses one or multiple keys at once without releasing them + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function hold(...keys: KeyCode[]): void; + +/** + * @brief Releases one or multiple keys at once + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function release(...keys: KeyCode[]): void; + +/** + * @brief Prints a string by repeatedly pressing and releasing keys + * @param string The string to print + * @param delay How many milliseconds to wait between key presses + */ +export declare function print(string: string, delay?: number): void; + +/** + * @brief Prints a string by repeatedly pressing and releasing keys. Presses + * "Enter" after printing the string + * @param string The string to print + * @param delay How many milliseconds to wait between key presses + */ +export declare function println(): void; diff --git a/applications/system/js_app/types/event_loop/index.d.ts b/applications/system/js_app/types/event_loop/index.d.ts new file mode 100644 index 0000000000..49237782c5 --- /dev/null +++ b/applications/system/js_app/types/event_loop/index.d.ts @@ -0,0 +1,70 @@ +type Lit = undefined | null | {}; + +/** + * Subscription control interface + */ +export interface Subscription { + /** + * Cancels the subscription, preventing any future events managed by the + * subscription from firing + */ + cancel(): void; +} + +/** + * Opaque event source identifier + */ +export type Contract = symbol; + +/** + * A callback can be assigned to an event loop to listen to an event. It may + * return an array with values that will be passed to it as arguments the next + * time that it is called. The first argument is always the subscription + * manager, and the second argument is always the item that trigged the event. + * The type of the item is defined by the event source. + */ +export type Callback = (subscription: Subscription, item: Item, ...args: Args) => Args | undefined | void; + +/** + * Subscribes a callback to an event + * @param contract Event identifier + * @param callback Function to call when the event is triggered + * @param args Initial arguments passed to the callback + */ +export function subscribe(contract: Contract, callback: Callback, ...args: Args): Subscription; +/** + * Runs the event loop until it is stopped (potentially never) + */ +export function run(): void | never; +/** + * Stops the event loop + */ +export function stop(): void; + +/** + * Creates a timer event that can be subscribed to just like any other event + * @param mode Either `"oneshot"` or `"periodic"` + * @param interval Timer interval in milliseconds + */ +export function timer(mode: "oneshot" | "periodic", interval: number): Contract; + +/** + * Message queue + */ +export interface Queue { + /** + * Message event + */ + input: Contract; + /** + * Sends a message to the queue + * @param message message to send + */ + send(message: T): void; +} + +/** + * Creates a message queue + * @param length maximum queue capacity + */ +export function queue(length: number): Queue; diff --git a/applications/system/js_app/types/flipper/index.d.ts b/applications/system/js_app/types/flipper/index.d.ts new file mode 100644 index 0000000000..b1b1d474bc --- /dev/null +++ b/applications/system/js_app/types/flipper/index.d.ts @@ -0,0 +1,14 @@ +/** + * @brief Returns the device model + */ +export declare function getModel(): string; + +/** + * @brief Returns the name of the virtual dolphin + */ +export declare function getName(): string; + +/** + * @brief Returns the battery charge percentage + */ +export declare function getBatteryCharge(): number; diff --git a/applications/system/js_app/types/global.d.ts b/applications/system/js_app/types/global.d.ts new file mode 100644 index 0000000000..ab1660cf66 --- /dev/null +++ b/applications/system/js_app/types/global.d.ts @@ -0,0 +1,178 @@ +/** + * @brief Pauses JavaScript execution for a while + * @param ms How many milliseconds to pause the execution for + */ +declare function delay(ms: number): void; + +/** + * @brief Prints to the GUI console view + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the console view + */ +declare function print(...args: any[]): void; + +/** + * @brief Converts a number to a string + * @param value The number to convert to a string + * @param base Integer base (`2`...`16`), default: 16 + */ +declare function toString(value: number, base?: number): string; + +/** + * @brief Reads a JS value from a file + * + * Reads a file at the specified path, interprets it as a JS value and returns + * said value. + * + * @param path The path to the file + */ +declare function load(path: string): any; + +/** + * @brief mJS Foreign Pointer type + * + * JavaScript code cannot do anything with values of `RawPointer` type except + * acquire them from native code and pass them right back to other parts of + * native code. These values cannot be turned into something meaningful, nor can + * be they modified. + */ +declare type RawPointer = symbol & { "__tag__": "raw_ptr" }; +// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist. + +/** + * @brief Holds raw bytes + */ +declare class ArrayBuffer { + /** + * @brief The pointer to the byte buffer + * @note Like other `RawPointer` values, this value is essentially useless + * to JS code. + */ + getPtr: RawPointer; + /** + * @brief The length of the buffer in bytes + */ + byteLength: number; + /** + * @brief Creates an `ArrayBuffer` that contains a sub-part of the buffer + * @param start The index of the byte in the source buffer to be used as the + * start for the new buffer + * @param end The index of the byte in the source buffer that follows the + * byte to be used as the last byte for the new buffer + */ + slice(start: number, end?: number): ArrayBuffer; +} + +declare function ArrayBuffer(): ArrayBuffer; + +declare type ElementType = "u8" | "i8" | "u16" | "i16" | "u32" | "i32"; + +declare class TypedArray { + /** + * @brief The length of the buffer in bytes + */ + byteLength: number; + /** + * @brief The length of the buffer in typed elements + */ + length: number; + /** + * @brief The underlying `ArrayBuffer` + */ + buffer: ArrayBuffer; +} + +declare class Uint8Array extends TypedArray<"u8"> { } +declare class Int8Array extends TypedArray<"i8"> { } +declare class Uint16Array extends TypedArray<"u16"> { } +declare class Int16Array extends TypedArray<"i16"> { } +declare class Uint32Array extends TypedArray<"u32"> { } +declare class Int32Array extends TypedArray<"i32"> { } + +declare function Uint8Array(data: ArrayBuffer | number | number[]): Uint8Array; +declare function Int8Array(data: ArrayBuffer | number | number[]): Int8Array; +declare function Uint16Array(data: ArrayBuffer | number | number[]): Uint16Array; +declare function Int16Array(data: ArrayBuffer | number | number[]): Int16Array; +declare function Uint32Array(data: ArrayBuffer | number | number[]): Uint32Array; +declare function Int32Array(data: ArrayBuffer | number | number[]): Int32Array; + +declare const console: { + /** + * @brief Prints to the UART logs at the `[I]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + log(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[D]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + debug(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[W]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + warn(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[E]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + error(...args: any[]): void; +}; + +declare class Array { + /** + * @brief Takes items out of the array + * + * Removes elements from the array and returns them in a new array + * + * @param start The index to start taking elements from + * @param deleteCount How many elements to take + * @returns The elements that were taken out of the original array as a new + * array + */ + splice(start: number, deleteCount: number): T[]; + /** + * @brief Adds a value to the end of the array + * @param value The value to add + * @returns New length of the array + */ + push(value: T): number; + /** + * @brief How many elements there are in the array + */ + length: number; +} + +declare class String { + /** + * @brief How many characters there are in the string + */ + length: number; + /** + * @brief Returns the character code at an index in the string + * @param index The index to consult + */ + charCodeAt(index: number): number; + /** + * See `charCodeAt` + */ + at(index: number): number; +} + +declare class Boolean { } + +declare class Function { } + +declare class Number { } + +declare class Object { } + +declare class RegExp { } + +declare interface IArguments { } + +declare type Partial = { [K in keyof O]?: O[K] }; diff --git a/applications/system/js_app/types/gpio/index.d.ts b/applications/system/js_app/types/gpio/index.d.ts new file mode 100644 index 0000000000..18705f8982 --- /dev/null +++ b/applications/system/js_app/types/gpio/index.d.ts @@ -0,0 +1,45 @@ +import type { Contract } from "../event_loop"; + +export interface Mode { + direction: "in" | "out"; + outMode?: "push_pull" | "open_drain"; + inMode?: "analog" | "plain_digital" | "interrupt" | "event"; + edge?: "rising" | "falling" | "both"; + pull?: "up" | "down"; +} + +export interface Pin { + /** + * Configures a pin. This may be done several times. + * @param mode Pin configuration object + */ + init(mode: Mode): void; + /** + * Sets the output value of a pin if it's been configured with + * `direction: "out"`. + * @param value Logic value to output + */ + write(value: boolean): void; + /** + * Gets the input value of a pin if it's been configured with + * `direction: "in"`, but not `inMode: "analog"`. + */ + read(): boolean; + /** + * Gets the input voltage of a pin in millivolts if it's been configured + * with `direction: "in"` and `inMode: "analog"` + */ + read_analog(): number; + /** + * Returns an `event_loop` event that can be used to listen to interrupts, + * as configured by `init` + */ + interrupt(): Contract; +} + +/** + * Returns an object that can be used to manage a GPIO pin. For the list of + * available pins, see https://docs.flipper.net/gpio-and-modules#miFsS + * @param pin Pin name (e.g. `"PC3"`) or number (e.g. `7`) + */ +export function get(pin: string | number): Pin; diff --git a/applications/system/js_app/types/gui/dialog.d.ts b/applications/system/js_app/types/gui/dialog.d.ts new file mode 100644 index 0000000000..6d9c8d43b2 --- /dev/null +++ b/applications/system/js_app/types/gui/dialog.d.ts @@ -0,0 +1,16 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + text: string, + left: string, + center: string, + right: string, +} +declare class Dialog extends View { + input: Contract<"left" | "center" | "right">; +} +declare class DialogFactory extends ViewFactory { } +declare const factory: DialogFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/empty_screen.d.ts b/applications/system/js_app/types/gui/empty_screen.d.ts new file mode 100644 index 0000000000..c71e93b327 --- /dev/null +++ b/applications/system/js_app/types/gui/empty_screen.d.ts @@ -0,0 +1,7 @@ +import type { View, ViewFactory } from "."; + +type Props = {}; +declare class EmptyScreen extends View { } +declare class EmptyScreenFactory extends ViewFactory { } +declare const factory: EmptyScreenFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/index.d.ts b/applications/system/js_app/types/gui/index.d.ts new file mode 100644 index 0000000000..3f95ab7806 --- /dev/null +++ b/applications/system/js_app/types/gui/index.d.ts @@ -0,0 +1,41 @@ +import type { Contract } from "../event_loop"; + +type Properties = { [K: string]: any }; + +export declare class View { + set

(property: P, value: Props[P]): void; +} + +export declare class ViewFactory> { + make(): V; + makeWith(initial: Partial): V; +} + +declare class ViewDispatcher { + /** + * Event source for `sendCustom` events + */ + custom: Contract; + /** + * Event source for navigation events (back key presses) + */ + navigation: Contract; + /** + * Sends a number to the custom event handler + * @param event number to send + */ + sendCustom(event: number): void; + /** + * Switches to a view + * @param assoc View-ViewDispatcher association as returned by `add` + */ + switchTo(assoc: View): void; + /** + * Sends this ViewDispatcher to the front or back, above or below all other + * GUI viewports + * @param direction Either `"front"` or `"back"` + */ + sendTo(direction: "front" | "back"): void; +} + +export const viewDispatcher: ViewDispatcher; diff --git a/applications/system/js_app/types/gui/loading.d.ts b/applications/system/js_app/types/gui/loading.d.ts new file mode 100644 index 0000000000..73a9633494 --- /dev/null +++ b/applications/system/js_app/types/gui/loading.d.ts @@ -0,0 +1,7 @@ +import type { View, ViewFactory } from "."; + +type Props = {}; +declare class Loading extends View { } +declare class LoadingFactory extends ViewFactory { } +declare const factory: LoadingFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/submenu.d.ts b/applications/system/js_app/types/gui/submenu.d.ts new file mode 100644 index 0000000000..59d5358643 --- /dev/null +++ b/applications/system/js_app/types/gui/submenu.d.ts @@ -0,0 +1,13 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + items: string[], +}; +declare class Submenu extends View { + chosen: Contract; +} +declare class SubmenuFactory extends ViewFactory { } +declare const factory: SubmenuFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/text_box.d.ts b/applications/system/js_app/types/gui/text_box.d.ts new file mode 100644 index 0000000000..3dbbac5710 --- /dev/null +++ b/applications/system/js_app/types/gui/text_box.d.ts @@ -0,0 +1,14 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + text: string, + font: "text" | "hex", + focus: "start" | "end", +} +declare class TextBox extends View { + chosen: Contract; +} +declare class TextBoxFactory extends ViewFactory { } +declare const factory: TextBoxFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/text_input.d.ts b/applications/system/js_app/types/gui/text_input.d.ts new file mode 100644 index 0000000000..96652b1d45 --- /dev/null +++ b/applications/system/js_app/types/gui/text_input.d.ts @@ -0,0 +1,14 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + minLength: number, + maxLength: number, +} +declare class TextInput extends View { + input: Contract; +} +declare class TextInputFactory extends ViewFactory { } +declare const factory: TextInputFactory; +export = factory; diff --git a/applications/system/js_app/types/math/index.d.ts b/applications/system/js_app/types/math/index.d.ts new file mode 100644 index 0000000000..25abca4af1 --- /dev/null +++ b/applications/system/js_app/types/math/index.d.ts @@ -0,0 +1,24 @@ +export function abs(n: number): number; +export function acos(n: number): number; +export function acosh(n: number): number; +export function asin(n: number): number; +export function asinh(n: number): number; +export function atan(n: number): number; +export function atan2(a: number, b: number): number; +export function atanh(n: number): number; +export function cbrt(n: number): number; +export function ceil(n: number): number; +export function clz32(n: number): number; +export function cos(n: number): number; +export function exp(n: number): number; +export function floor(n: number): number; +export function max(n: number, m: number): number; +export function min(n: number, m: number): number; +export function pow(n: number, m: number): number; +export function random(): number; +export function sign(n: number): number; +export function sin(n: number): number; +export function sqrt(n: number): number; +export function trunc(n: number): number; +declare const PI: number; +declare const EPSILON: number; diff --git a/applications/system/js_app/types/notification/index.d.ts b/applications/system/js_app/types/notification/index.d.ts new file mode 100644 index 0000000000..947daba218 --- /dev/null +++ b/applications/system/js_app/types/notification/index.d.ts @@ -0,0 +1,20 @@ +/** + * @brief Signals success to the user via the color LED, speaker and vibration + * motor + */ +export declare function success(): void; + +/** + * @brief Signals failure to the user via the color LED, speaker and vibration + * motor + */ +export declare function error(): void; + +export type Color = "red" | "green" | "blue" | "yellow" | "cyan" | "magenta"; + +/** + * @brief Displays a basic color on the color LED + * @param color The color to display, see `Color` + * @param duration The duration, either `"short"` (10ms) or `"long"` (100ms) + */ +export declare function blink(color: Color, duration: "short" | "long"): void; diff --git a/applications/system/js_app/types/serial/index.d.ts b/applications/system/js_app/types/serial/index.d.ts new file mode 100644 index 0000000000..1a7ed6397e --- /dev/null +++ b/applications/system/js_app/types/serial/index.d.ts @@ -0,0 +1,77 @@ +/** + * @brief Initializes the serial port + * @param port The port to initialize (`"lpuart"` or `"start"`) + * @param baudRate + */ +export declare function setup(port: "lpuart" | "usart", baudRate: number): void; + +/** + * @brief Writes data to the serial port + * @param value The data to write: + * - Strings will get sent as ASCII. + * - Numbers will get sent as a single byte. + * - Arrays of numbers will get sent as a sequence of bytes. + * - `ArrayBuffer`s and `TypedArray`s will be sent as a sequence + * of bytes. + */ +export declare function write(value: string | number | number[] | ArrayBuffer | TypedArray): void; + +/** + * @brief Reads data from the serial port + * @param length The number of bytes to read + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. + * @returns The received data interpreted as ASCII, or `undefined` if 0 bytes + * were read. + */ +export declare function read(length: number, timeout?: number): string | undefined; + +/** + * @brief Reads data from the serial port + * + * Data is read one character after another until either a `\r` or `\n` + * character is received, neither of which is included in the result. + * + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. The timeout only + * applies to characters, not entire strings. + * @returns The received data interpreted as ASCII, or `undefined` if 0 bytes + * were read. + */ +export declare function readln(timeout?: number): string; + +/** + * @brief Reads data from the serial port + * @param length The number of bytes to read + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. + * @returns The received data as an ArrayBuffer, or `undefined` if 0 bytes were + * read. + */ +export declare function readBytes(length: number, timeout?: number): ArrayBuffer; + +/** + * @brief Reads data from the serial port, trying to match it to a pattern + * @param patterns A single pattern or an array of patterns: + * - If the argument is a single `string`, this function will + * match against the given string. + * - If the argument is an array of `number`s, this function + * will match against the given sequence of bytes, + * - If the argument is an array of `string`s, this function + * will match against any string out of the ones that were + * provided. + * - If the argument is an array of arrays of `number`s, this + * function will match against any sequence of bytes out of + * the ones that were provided. + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. The timeout only + * applies to characters, not entire strings. + * @returns The index of the matched pattern if multiple were provided, or 0 if + * only one was provided and it matched, or `undefined` if none of the + * patterns matched. + */ +export declare function expect(patterns: string | number[] | string[] | number[][], timeout?: number): number | undefined; diff --git a/applications/system/js_app/types/storage/index.d.ts b/applications/system/js_app/types/storage/index.d.ts new file mode 100644 index 0000000000..0dd29e121c --- /dev/null +++ b/applications/system/js_app/types/storage/index.d.ts @@ -0,0 +1,237 @@ +/** + * File readability mode: + * - `"r"`: read-only + * - `"w"`: write-only + * - `"rw"`: read-write + */ +export type AccessMode = "r" | "w" | "rw"; + +/** + * File creation mode: + * - `"open_existing"`: open file or fail if it doesn't exist + * - `"open_always"`: open file or create a new empty one if it doesn't exist + * - `"open_append"`: open file and set r/w pointer to EOF, or create a new one if it doesn't exist + * - `"create_new"`: create new file or fail if it exists + * - `"create_always"`: truncate and open file, or create a new empty one if it doesn't exist + */ +export type OpenMode = "open_existing" | "open_always" | "open_append" | "create_new" | "create_always"; + +/** Standard UNIX timestamp */ +export type Timestamp = number; + +/** File information structure */ +export declare class FileInfo { + /** + * Full path (e.g. "/ext/test", returned by `stat`) or file name + * (e.g. "test", returned by `readDirectory`) + */ + path: string; + /** + * Is the file a directory? + */ + isDirectory: boolean; + /** + * File size in bytes, or 0 in the case of directories + */ + size: number; + /** + * Time of last access as a UNIX timestamp + */ + accessTime: Timestamp; +} + +/** Filesystem information structure */ +export declare class FsInfo { + /** Total size of the filesystem, in bytes */ + totalSpace: number; + /** Free space in the filesystem, in bytes */ + freeSpace: number; +} + +// file operations + +/** File class */ +export declare class File { + /** + * Closes the file. After this method is called, all other operations + * related to this file become unavailable. + * @returns `true` on success, `false` on failure + */ + close(): boolean; + /** + * Is the file currently open? + */ + isOpen(): boolean; + /** + * Reads bytes from a file opened in read-only or read-write mode + * @param mode The data type to interpret the bytes as: a `string` decoded + * from ASCII data (`"ascii"`), or an `ArrayBuf` (`"binary"`) + * @param bytes How many bytes to read from the file + * @returns an `ArrayBuf` if the mode is `"binary"`, a `string` if the mode + * is `ascii`. The number of bytes that was actually read may be + * fewer than requested. + */ + read(mode: T extends ArrayBuffer ? "binary" : "ascii", bytes: number): T; + /** + * Writes bytes to a file opened in write-only or read-write mode + * @param data The data to write: a string that will be ASCII-encoded, or an + * ArrayBuf + * @returns the amount of bytes that was actually written + */ + write(data: ArrayBuffer | string): number; + /** + * Moves the R/W pointer forward + * @param bytes How many bytes to move the pointer forward by + * @returns `true` on success, `false` on failure + */ + seekRelative(bytes: number): boolean; + /** + * Moves the R/W pointer to an absolute position inside the file + * @param bytes The position inside the file + * @returns `true` on success, `false` on failure + */ + seekAbsolute(bytes: number): boolean; + /** + * Gets the absolute position of the R/W pointer in bytes + */ + tell(): number; + /** + * Discards the data after the current position of the R/W pointer in a file + * opened in either write-only or read-write mode. + * @returns `true` on success, `false` on failure + */ + truncate(): boolean; + /** + * Reads the total size of the file in bytes + */ + size(): number; + /** + * Detects whether the R/W pointer has reached the end of the file + */ + eof(): boolean; + /** + * Copies bytes from the R/W pointer in the current file to the R/W pointer + * in another file + * @param dest The file to copy the bytes into + * @param bytes The number of bytes to copy + * @returns `true` on success, `false` on failure + */ + copyTo(dest: File, bytes: number): boolean; +} + +/** + * Opens a file + * @param path The path to the file + * @param accessMode `"r"`, `"w"` or `"rw"`; see `AccessMode` + * @param openMode `"open_existing"`, `"open_always"`, `"open_append"`, + * `"create_new"` or `"create_always"`; see `OpenMode` + * @returns a `File` on success, or `undefined` on failure + */ +export declare function openFile(path: string, accessMode: AccessMode, openMode: OpenMode): File | undefined; +/** + * Detects whether a file exists + * @param path The path to the file + * @returns `true` on success, `false` on failure + */ +export declare function fileExists(path: string): boolean; + +// directory operations + +/** + * Reads the list of files in a directory + * @param path The path to the directory + * @returns Array of `FileInfo` structures with directory entries, + * or `undefined` on failure + */ +export declare function readDirectory(path: string): FileInfo[] | undefined; +/** + * Detects whether a directory exists + * @param path The path to the directory + */ +export declare function directoryExists(path: string): boolean; +/** + * Creates an empty directory + * @param path The path to the new directory + * @returns `true` on success, `false` on failure + */ +export declare function makeDirectory(path: string): boolean; + +// common (file/dir) operations + +/** + * Detects whether a file or a directory exists + * @param path The path to the file or directory + */ +export declare function fileOrDirExists(path: string): boolean; +/** + * Acquires metadata about a file or directory + * @param path The path to the file or directory + * @returns A `FileInfo` structure or `undefined` on failure + */ +export declare function stat(path: string): FileInfo | undefined; +/** + * Removes a file or an empty directory + * @param path The path to the file or directory + * @returns `true` on success, `false` on failure + */ +export declare function remove(path: string): boolean; +/** + * Removes a file or recursively removes a possibly non-empty directory + * @param path The path to the file or directory + * @returns `true` on success, `false` on failure + */ +export declare function rmrf(path: string): boolean; +/** + * Renames or moves a file or directory + * @param oldPath The old path to the file or directory + * @param newPath The new path that the file or directory will become accessible + * under + * @returns `true` on success, `false` on failure + */ +export declare function rename(oldPath: string, newPath: string): boolean; +/** + * Copies a file or recursively copies a possibly non-empty directory + * @param oldPath The original path to the file or directory + * @param newPath The new path that the copy of the file or directory will be + * accessible under + */ +export declare function copy(oldPath: string, newPath: string): boolean; +/** + * Fetches generic information about a filesystem + * @param filesystem The path to the filesystem (e.g. `"/ext"` or `"/int"`) + */ +export declare function fsInfo(filesystem: string): FsInfo | undefined; +/** + * Chooses the next available filename with a numeric suffix in a directory + * + * ``` + * "/ext/example_dir/example_file123.txt" + * \______________/ \__________/\_/\__/ + * dirPath fileName | | + * | +---- fileExt + * +------- selected by this function + * ``` + * + * @param dirPath The directory to look in + * @param fileName The base of the filename (the part before the numeric suffix) + * @param fileExt The extension of the filename (the part after the numeric suffix) + * @param maxLen The maximum length of the filename with the numeric suffix + * @returns The base of the filename with the next available numeric suffix, + * without the extension or the base directory. + */ +export declare function nextAvailableFilename(dirPath: string, fileName: string, fileExt: string, maxLen: number): string; + +// path operations that do not access the filesystem + +/** + * Determines whether the two paths are equivalent. Respects filesystem-defined + * path equivalence rules. + */ +export declare function arePathsEqual(path1: string, path2: string): boolean; +/** + * Determines whether a path is a subpath of another path. Respects + * filesystem-defined path equivalence rules. + * @param parentPath The parent path + * @param childPath The child path + */ +export declare function isSubpathOf(parentPath: string, childPath: string): boolean; diff --git a/applications/system/js_app/types/tests/index.d.ts b/applications/system/js_app/types/tests/index.d.ts new file mode 100644 index 0000000000..8aaeec5e52 --- /dev/null +++ b/applications/system/js_app/types/tests/index.d.ts @@ -0,0 +1,8 @@ +/** + * Unit test module. Only available if the firmware has been configured with + * `FIRMWARE_APP_SET=unit_tests`. + */ + +export function fail(message: string): never; +export function assert_eq(expected: T, result: T): void | never; +export function assert_float_close(expected: number, result: number, epsilon: number): void | never; diff --git a/documentation/doxygen/Doxyfile.cfg b/documentation/doxygen/Doxyfile.cfg index 90f36415f1..e016317494 100644 --- a/documentation/doxygen/Doxyfile.cfg +++ b/documentation/doxygen/Doxyfile.cfg @@ -1108,7 +1108,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = $(DOXY_SRC_ROOT)/documentation/images # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program diff --git a/documentation/doxygen/js.dox b/documentation/doxygen/js.dox index 33ac078d92..f5c609dd14 100644 --- a/documentation/doxygen/js.dox +++ b/documentation/doxygen/js.dox @@ -11,12 +11,20 @@ This page contains some information on the Flipper Zero scripting engine, which JS modules use the Flipper app plugin system. Each module is compiled into a `.fal` library file and is located on a microSD card. Here is a list of implemented modules: -- @subpage js_badusb — BadUSB module -- @subpage js_serial — Serial module -- @subpage js_math — Math module -- @subpage js_dialog — Dialog module -- @subpage js_submenu — Submenu module -- @subpage js_textbox — Textbox module -- @subpage js_notification — Notifications module +- @subpage js_badusb - BadUSB module +- @subpage js_serial - Serial module +- @subpage js_math - Math module +- @subpage js_notification - Notifications module +- @subpage js_event_loop - Event Loop module +- @subpage js_gpio - GPIO module +- @subpage js_gui - GUI module and its submodules: + - @subpage js_gui__submenu - Submenu view + - @subpage js_gui__loading - Hourglass (Loading) view + - @subpage js_gui__empty_screen - Empty view + - @subpage js_gui__text_input - Keyboard-like text input + - @subpage js_gui__text_box - Simple multiline text box + - @subpage js_gui__dialog - Dialog with up to 3 options + +All modules have corresponding TypeScript declaration files, so you can set up your IDE to show suggestions when writing JS scripts. */ diff --git a/documentation/images/dialog.png b/documentation/images/dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..008ae9ce524fc2e34f234bb64f7bc4fee216a1eb GIT binary patch literal 1377 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 z3VXUZhE&XXd)LwLwu3~&MYckjbH^1e3urW;@tOptGJV_eUB{v*>16Y zegF1&hA(^O)G{*6ab{px#KNH9HA>Mf2wr`C!_DxlqVoTr-|vA@A-uk2^#&$}4k3mH z4Mv8JT{A^LUA|}e>DqbuneP8<-b_ocy1#D5-P`;LEust#Qy3UrIDyo8#tla%?)*P- zFZ;h4TjdWIZ$=ofiT!|3`q{btHEjvkp7V#*_Z{?^AGrn*zi${fWTs5}d*Zq5%&+-> z&)hrSCsX|ObJJclCD-oniGR8$AeogxK$U?ZkcmMcm7l?H+ppci3EfBzv1OQ%@Oqzv z^0#weYSWE2M=(O%2XyA$Gwcs7XZ+qg9YuA6hBm~XQ11>eYQ^=<`VIAQ_w3K_V~Ke8 z`tjR0-VbIY{D0-F&AXUq|6(qlzc1~6?{MA!Ij6bcdGD0m0nW6uf8FDDM}EFJ@o~=I zKNBCnF)g|m`T4bW)Wl=e! z!jtzmx7!@5`F8Qyq0Rs7xA*P*zESyjWbM6)uPa-w_w7W9aDyjnes5VbukgL?vlBiw z?~~p8Dxd6epYhw?94Wr48F(UV<^L-`uT^{3dEPb>5!pAHcQi!aUmrbtdHls!*E8OK z+*>~n$>URO8@gwf{;&F%|GxHTzfIW_)45-*-pze^JH09o$esDBZ3c@>KS&6ezW94aruGm%Eh_Hh3ifrg7TGlixSt;(kEIWz%rA;)78&q Iol`;+0D;-4I{*Lx literal 0 HcmV?d00001 diff --git a/documentation/images/empty.png b/documentation/images/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..844f45093f96cdc2120fcf9d040934888bfa89ac GIT binary patch literal 1005 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#4o z&-Zk345^s&_NpN-g95_=0|$ODLEi}nH-;VDceeT)!-Dj`d<+lxnHf5a7#cK2snH-9 gO$Bt!2rJl^Su$_wn)*l!n12~OUHx3vIVCg!0K_`c6#xJL literal 0 HcmV?d00001 diff --git a/documentation/images/loading.png b/documentation/images/loading.png new file mode 100644 index 0000000000000000000000000000000000000000..f35966f66a809f796d17a1ec1c9a097d2fe767f0 GIT binary patch literal 1173 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 zxO=)dhE&XXd)G0G$x*=JqK4e@&rb#B=|7*SbB818M8mDifA2@L8+Et%m%jY+u*$NY z@j|i8Uj~LAMFxgICI$i3Q3`7iluG?&_z-{m{rrpl8*Y9rlKac?VG1XMf)@kBA{GXP zQ+y4YZ-3_09*br5Rbyz-U}WeJVrW=#c;5M-hcj*GZ~i?$Nem&bVa>3td_Vt>C-*Ul*j^dJ{JM0YRNForY!+dc01$%@4jbAtG?{h9VzyePmPh%Z)PR~8? z!CaWZVG09-3n!40W$>>2es}*j#x+L}3RBq+d~ut5`Zl|PHImZb3?~Z9_>pb3W)Ql= z$2eT9G{Vongi?o%zS-KearFPzIW6$*a*Su*vtn$xBA6U*Xc)I$ztaD0e0sz9R@tyzx literal 0 HcmV?d00001 diff --git a/documentation/images/submenu.png b/documentation/images/submenu.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb64e9748fd70ceeec3b5f62d0ff0af427b499a GIT binary patch literal 1774 zcmb_dYfw{16h4=T5DP(Su@DG_VnqkS2o7&hLY9}tlVgw$!l5fIuYMuJJOqh?Hv;bDXlz+R@QES-+O`s2>t-95W!&-d6Z zjtC1jT|-_20H(B%U6B9=_%8;=OHJq$eGA?YGeYQ@0HmLmKJ0rZk~P3;E^XJ2k8*3( zvc2Cu-e_q!(%pQzQk!5-KX`AQmg^W~E{@=obtwlKQ)VwSB^Kjmg^N1nmF?g)aIWx` zPRC43HUjIn0nV?2fadokg&n0<*hDKmvy=Vz*d`3ts-}+;U{)Z_=lB3z#silILjg|c z*ZoIV)vxh9lo?xIpI}-dSBIa>R|~hj00<-kC&B>iP5~Hyu-p}O_;4)Py;-AE1;>~* z(k}M!7(ace#8x*Z*KZMnAbhW^W|d7qKE2__v0ii&HC=I!^~R6S-6{rM@> zCJQBVG*R0X-T50k;#q*%axUr_f+ef7TJx0B`&?FVkCj*MJ{V>LT#%GawG$}S z)0mUILct6($mSX?S9+s{kW6pr&2i5A%0fx@ezISHZJE-&WGpzJu^v|=)rpi(SJw_! zYlJC13a-nd3-ypWjIGn-nF;H1!)?5Vs>Nwsqi4bW2hzAtpXiS= zo-xnWozppj@AnTgdsI+bOn9~v*-n~9erA@ci|&_@Qw)Pt#Kc(L z*_q3a$(qBC8=4mtU0oD79H>`G)gU%VuB2FbrjIt|DCCn17VKgET-=b4B&v7wh6p1F z*rAVXHGJXb^u#`TKF&c=WI6io?Di~xcl4r5;qy4jo~hg7fA*c?577eLkU7;g9;dC2 zJWZju3w96Wyv5euU_BZm_5|I{O7gWYLu%I2tYc)%*jbsBP#cUCcYXcH-tIAy-)>~A zq=f(>4G7?8{snle;i?QTL%RCHwNi>e!@IW$dJH-Q3}cIAnPWgCacp=_TJiH~ZqK!@?3}<6|%0 z^c=;z7%7;iWd!tSO3O>#C3nTX9nL&*WIqFMhkVnE+fny_3g=umfeS zY5`lulKQ(}ANjh@k4}i+*lJXuOw^@P2)+7w*1rO*6n;@ ziFPvP!Ei-VL6PG00X9OZqm^$DOBBL zP4Nf^qfn6R2p>=S%cB$x;ezb(R78=Y>`ZX%8J3SNR<=>@7a_XQx;B|)cpy3M*sK&c gPsRv%^LQd=Y35d0T5&{E4z$Czwrv?I!B!Q#^Ent8U0)!-xkYp}nX3cf_V}9Iq?_KBabN2qe zZ-4vVI2s#iW9?`S0I-RUiueM64bUS3SgqfVEFrz1+s2DgNE`sRUF#R(u7|A?09LP} zBR);YEtQX@7D_&HcyH+HvF~51ayF-?3*%KEK0dwG`x@f>0**HpVfe?$?MO57qIA67 zxVG3{o~KuuQE(|nyLR-daZNhc11`RY0Go~g@U=E2uexA2qP|LYJWo-|Xb0fXMnE~b z0qh)hjq?YTRkRY(-99e*5H#;Ht-S#tjqx;XXh055d;ooT2TG0qiKW*PjZ%z1Miked z9C*O*&HPv+!+2<=U7^@>N?W07*oZZ309LzAo5bKi5S2zFUk#v*-@-jI1NV2DR*GsZ z5^6jhO0d%*Z)m;Pz*vQMCTAk}(tb2S7M9q~Ov5B|)t)*=KrGigxeXDtG%!->H{~y5 zO2}tN@rQMhqS4P?0I)C>NH}kMLU4S+9b;ZV4P$s=QpikvXHt3+H(-UX6YJ&*=qx{3 z==>1YL;KZXU1sfV!68`mLBqX{n%)=l&Q-+cG@RX&&vVr+`3C{8Hr1W8=G<|2m7)mb z=r>BVbJ=z|0Bkh};d`OeXIBC#NX&9wC|RMMX)GLV5Dd(Qrew?Aq$rNyIUU1p)W@|d zM~}b0qppF~UoNx5Iwnu<+o)*XlQLngm1ErW6Z_wCvJQ35RTq#Moy!b)?U*!#l~uz+ zXCbvz+5*Z2Q+7Yuf>%?+2yk>002NSH)#q?PFxS@0)3b46G)lx4=M-EeUd~sitJHZn z#~9g`{5DRj3pjrWQpOf2W*E`&RcuMk@rtRZTeKD5!wTnzK!n>~Quv1@^S-2J+FXtQ zbB_2=ykv3ZL5BZOGm;d#MAm$(<2NXHS!r35tBSFVhsMlSB_UZF+lIO}r%t|I`F%KLW5zVKAQgUxq3JDaESFI3cm1YWZ0o>UG@q z)%j&>R#R2RWazy5{ZfWjqW?@!he*C=I>c`P;qbwvJ8PhKPO#%_1#Bgz#;ls-5wbMD zdP7m4Ua%a^Rdb}N*{+%LdzC*m-`DIIKzGhcA2LHc41=`2qgW9eTT+1ZnGCxe{t2=n*yO|6ihI{7FV(J|0 zSE3n5IvaxJs@;Swf914LTl zM(;2p-15{=X%tLMvt?#2g}YOmsQChQRv@qMO0p=Pdee`pWQ}+ZOce$W%_0tP6!A{3Ydc7N?!h(faG4h8+1cw3j@i=5-ePTC71BhIb5XiEq_ohM805RpQgKEo-4UL{g1HI z9+{2wU6{B&xiH7M2di2&;@qL)4A&InzrzqTv>b+HLYOelcWF_J7BRX)ww)oNoi93o z8Ml`s7fXwtRA|Zy*~(B^;LI3VporqCS$uQ2r!skneiZg0ejm2GwG0(>=5&gI_xHXa zU9IMCtftJ#K-Y7h-9!%lvoGW^^tRIYhI4r(6$9_W!*q*JmM*N!#BTrb^v4=KQKHTJ z29~eAx8;dnl-nkLF8M`s8Lxgu)4=Bsq@HO~T=SVRsz~iNA1tJ0)Fw$94E^qkh@vM~ zx^WGijZCp`yAs#bsYk`Y5QRJ`fK)20WwUG)^}ph;Ob6M-qd-TtX*T`wq~}hGOR!%) zoWByY01Dptm(6W3MgKp;`gh;Fy9vk*gh}@|eN=ujc-kk{yu*N2tS}e0y`7au)_+Cp ZMk}^Nr5&Jr2mJtn=)=O(d;sRX_+aAtJ?gO0Xj! z7%;&>7FmLdfDj-|wV_rCC|Otx0V4uQAPPuWOlTNj`tV2p_Q#uh-@WhNbI!M%_p7J7 zi~h!)8vy|NCr>zi55Q-dDGcZ=4X%@c2Q-83PbUK70oeRx>4D{1ZZ-yB1M{TQF`wkZ zagkeAzs>gbT^DcP$o_D4YhXyV#pp2)lSBEx4}~ZT!@}KiswUTqYs-b5W@0tWyt91* z2JT%2Kxa1q$4NKT63K!vBrd@@WQb%x4+pB}nq^%;@%#*!``q8~&b`BQkN{otH7IiF zP^~LLIF7ZTlvJrC6eTU-H1-v+>K1PkujIi*+hB=OJeeAx2ZI^a+~ z76lu>a7LUq)yPcVhFnfGM^g?edS6izOM0hRaEXD=peqGcAqkQ=z6WAaSNQpwn}g5g z$pQ}}Y%UuChdr?6h$(W6gT=do^MetQn%J5+OB;WaG;>H1OJSB!TZQNRzM^jWvyE-y zy&}a~Nb2MjRacbC`iaWA_AVs1cR@OqC%0qfGKASoszAkTws!t79%<&^LB=c1J#0916%8VY zaD%}XG7l5iG)(xzQsI@4V>1c$wZwzuxMAC%-u~n=e0Q7>|LqwzgxLz|5kT>B=1)YW zSR>`qlGBZh&{;U_QQ8W_rXsC;mGMljELo00Mr6E1o1qrD!4_4xcnR9E+CAXfUl<3l zeMJYn7idZ^{UN+-zf_$RIM;XwXTZv8!0TH=)X9Ifbm725@utV#cNZ9JE6AR&9LD1n zMDj-cCn^&U>!Lckr|k=xd^=`{0^Ht~BVo77X~G7~9DyGlKl7ez(*UVMimIrOJ^clR z1$9VRr`A0`gEkyQxx8nJY1qM=|Dv|4`z5C>xhgCQ1{-0W?bxrhs1AcDfJOwh3?NkDzG9vgYwE~H*@qthuUuA4T#*7w z?E08?qH-0zz6N0-9A@kqY8H_*Eft9Y1aj##G9!9DMW(ZCV_t3)Ocao3PmK>y%+Qjl zSb@%0cgMWB194BED%~L2Yt&X$I$f0Af8y^WR)Vj*96d(8XH)NxBi5jX%JfL)mcH>HWHx^ zDO97U>5quUx=0~LVeD&GWy|sEXOM!b8A57%-DKb3B};a33g>{t&ZI76?Xl;~V~XpZ z-UED>n^D{aCHV?xTTOeKnAbrI<}W_{3C}svlLt zQ{6og_Zz(xJ*KIia88vl>MUWPf>nlqn zPvajF0g!*W(l~w5lx_jhwz2eLnxVjkCNenDsvSGW>Os+aMgyvNC4%|e2@Oo^xE|@g#QCC zRT*}G_`tB$(Ro~`r=ew~AGFXhWmm9|tllgZW}_hSauC4eP5Q_vy-oRY=YLQ-zhmfH Wr2rOx&q?z;2PeOCcVZq7P5TEYI33~u literal 0 HcmV?d00001 diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md index 3d113807b0..9c59b98224 100644 --- a/documentation/js/js_builtin.md +++ b/documentation/js/js_builtin.md @@ -41,16 +41,10 @@ print("string1", "string2", 123); Same as `print`, but output to serial console only, with corresponding log level. ## to_string -Convert a number to string. +Convert a number to string with an optional base. ### Examples: ```js -to_string(123) -``` -## to_hex_string -Convert a number to string(hex format). - -### Examples: -```js -to_hex_string(0xFF) +to_string(123) // "123" +to_string(123, 16) // "0x7b" ``` diff --git a/documentation/js/js_dialog.md b/documentation/js/js_dialog.md deleted file mode 100644 index eb027e6a73..0000000000 --- a/documentation/js/js_dialog.md +++ /dev/null @@ -1,49 +0,0 @@ -# js_dialog {#js_dialog} - -# Dialog module -```js -let dialog = require("dialog"); -``` -# Methods - -## message -Show a simple message dialog with header, text and "OK" button. - -### Parameters -- Dialog header text -- Dialog text - -### Returns -true if central button was pressed, false if the dialog was closed by back key press - -### Examples: -```js -dialog.message("Dialog demo", "Press OK to start"); -``` - -## custom -More complex dialog with configurable buttons - -### Parameters -Configuration object with the following fields: -- header: Dialog header text -- text: Dialog text -- button_left: (optional) left button name -- button_right: (optional) right button name -- button_center: (optional) central button name - -### Returns -Name of pressed button or empty string if the dialog was closed by back key press - -### Examples: -```js -let dialog_params = ({ - header: "Dialog header", - text: "Dialog text", - button_left: "Left", - button_right: "Right", - button_center: "OK" -}); - -dialog.custom(dialog_params); -``` diff --git a/documentation/js/js_event_loop.md b/documentation/js/js_event_loop.md new file mode 100644 index 0000000000..9519478c0e --- /dev/null +++ b/documentation/js/js_event_loop.md @@ -0,0 +1,144 @@ +# js_event_loop {#js_event_loop} + +# Event Loop module +```js +let eventLoop = require("event_loop"); +``` + +The event loop is central to event-based programming in many frameworks, and our +JS subsystem is no exception. It is a good idea to familiarize yourself with the +event loop first before using any of the advanced modules (e.g. GPIO and GUI). + +## Conceptualizing the event loop +If you ever wrote JavaScript before, you have definitely seen callbacks. It's +when a function accepts another function (usually an anonymous one) as one of +the arguments, which it will call later on, e.g. when an event happens or when +data becomes ready: +```js +setTimeout(function() { console.log("Hello, World!") }, 1000); +``` + +Many JavaScript engines employ a queue that the runtime fetches events from as +they occur, subsequently calling the corresponding callbacks. This is done in a +long-running loop, hence the name "event loop". Here's the pseudocode for a +typical event loop: +```js +while(loop_is_running()) { + if(event_available_in_queue()) { + let event = fetch_event_from_queue(); + let callback = get_callback_associated_with(event); + if(callback) + callback(get_extra_data_for(event)); + } else { + // avoid wasting CPU time + sleep_until_any_event_becomes_available(); + } +} +``` + +Most JS runtimes enclose the event loop within themselves, so that most JS +programmers does not even need to be aware of its existence. This is not the +case with our JS subsystem. + +# Example +This is how one would write something similar to the `setTimeout` example above: +```js +// import module +let eventLoop = require("event_loop"); + +// create an event source that will fire once 1 second after it has been created +let timer = eventLoop.timer("oneshot", 1000); + +// subscribe a callback to the event source +eventLoop.subscribe(timer, function(_subscription, _item, eventLoop) { + print("Hello, World!"); + eventLoop.stop(); +}, eventLoop); // notice this extra argument. we'll come back to this later + +// run the loop until it is stopped +eventLoop.run(); + +// the previous line will only finish executing once `.stop()` is called, hence +// the following line will execute only after "Hello, World!" is printed +print("Stopped"); +``` + +I promised you that we'll come back to the extra argument after the callback +function. Our JavaScript engine does not support closures (anonymous functions +that access values outside of their arguments), so we ask `subscribe` to pass an +outside value (namely, `eventLoop`) as an argument to the callback so that we +can access it. We can modify this extra state: +```js +// this timer will fire every second +let timer = eventLoop.timer("periodic", 1000); +eventLoop.subscribe(timer, function(_subscription, _item, counter, eventLoop) { + print("Counter is at:", counter); + if(counter === 10) + eventLoop.stop(); + // modify the extra arguments that will be passed to us the next time + return [counter + 1, eventLoop]; +}, 0, eventLoop); +``` + +Because we have two extra arguments, if we return anything other than an array +of length 2, the arguments will be kept as-is for the next call. + +The first two arguments that get passed to our callback are: + - The subscription manager that lets us `.cancel()` our subscription + - The event item, used for events that have extra data. Timer events do not, + they just produce `undefined`. + +# API reference +## `run` +Runs the event loop until it is stopped with `stop`. + +## `subscribe` +Subscribes a function to an event. + +### Parameters + - `contract`: an event source identifier + - `callback`: the function to call when the event happens + - extra arguments: will be passed as extra arguments to the callback + +The callback will be called with at least two arguments, plus however many were +passed as extra arguments to `subscribe`. The first argument is the subscription +manager (the same one that `subscribe` itself returns). The second argument is +the event item for events that produce extra data; the ones that don't set this +to `undefined`. The callback may return an array of the same length as the count +of the extra arguments to modify them for the next time that the event handler +is called. Any other returns values are discarded. + +### Returns +A `SubscriptionManager` object: + - `SubscriptionManager.cancel()`: unsubscribes the callback from the event + +### Warning +Each event source may only have one callback associated with it. + +## `stop` +Stops the event loop. + +## `timer` +Produces an event source that fires with a constant interval either once or +indefinitely. + +### Parameters + - `mode`: either `"oneshot"` or `"periodic"` + - `interval`: the timeout (for `"oneshot"`) timers or the period (for + `"periodic"` timers) + +### Returns +A `Contract` object, as expected by `subscribe`'s first parameter. + +## `queue` +Produces a queue that can be used to exchange messages. + +### Parameters + - `length`: the maximum number of items that the queue may contain + +### Returns +A `Queue` object: + - `Queue.send(message)`: + - `message`: a value of any type that will be placed at the end of the queue + - `input`: a `Contract` (event source) that pops items from the front of the + queue diff --git a/documentation/js/js_gpio.md b/documentation/js/js_gpio.md new file mode 100644 index 0000000000..9791fb4ebc --- /dev/null +++ b/documentation/js/js_gpio.md @@ -0,0 +1,77 @@ +# js_gpio {#js_gpio} + +# GPIO module +```js +let eventLoop = require("event_loop"); +let gpio = require("gpio"); +``` + +This module depends on the `event_loop` module, so it _must_ only be imported +after `event_loop` is imported. + +# Example +```js +let eventLoop = require("event_loop"); +let gpio = require("gpio"); + +let led = gpio.get("pc3"); +led.init({ direction: "out", outMode: "push_pull" }); + +led.write(true); +delay(1000); +led.write(false); +delay(1000); +``` + +# API reference +## `get` +Gets a `Pin` object that can be used to manage a pin. + +### Parameters + - `pin`: pin identifier (examples: `"pc3"`, `7`, `"pa6"`, `3`) + +### Returns +A `Pin` object + +## `Pin` object +### `Pin.init()` +Configures a pin + +#### Parameters + - `mode`: `Mode` object: + - `direction` (required): either `"in"` or `"out"` + - `outMode` (required for `direction: "out"`): either `"open_drain"` or + `"push_pull"` + - `inMode` (required for `direction: "in"`): either `"analog"`, + `"plain_digital"`, `"interrupt"` or `"event"` + - `edge` (required for `inMode: "interrupt"` or `"event"`): either + `"rising"`, `"falling"` or `"both"` + - `pull` (optional): either `"up"`, `"down"` or unset + +### `Pin.write()` +Writes a digital value to a pin configured with `direction: "out"` + +#### Parameters + - `value`: boolean logic level to write + +### `Pin.read()` +Reads a digital value from a pin configured with `direction: "in"` and any +`inMode` except `"analog"` + +#### Returns +Boolean logic level + +### `Pin.read_analog()` +Reads an analog voltage level in millivolts from a pin configured with +`direction: "in"` and `inMode: "analog"` + +#### Returns +Voltage on pin in millivolts + +### `Pin.interrupt()` +Attaches an interrupt to a pin configured with `direction: "in"` and +`inMode: "interrupt"` or `"event"` + +#### Returns +An event loop `Contract` object that identifies the interrupt event source. The +event does not produce any extra data. diff --git a/documentation/js/js_gui.md b/documentation/js/js_gui.md new file mode 100644 index 0000000000..4d2d2497a2 --- /dev/null +++ b/documentation/js/js_gui.md @@ -0,0 +1,161 @@ +# js_gui {#js_gui} + +# GUI module +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +``` + +This module depends on the `event_loop` module, so it _must_ only be imported +after `event_loop` is imported. + +## Conceptualizing GUI +### Event loop +It is highly recommended to familiarize yourself with the event loop first +before doing GUI-related things. + +### Canvas +The canvas is just a drawing area with no abstractions over it. Drawing on the +canvas directly (i.e. not through a viewport) is useful in case you want to +implement a custom design element, but this is rather uncommon. + +### Viewport +A viewport is a window into a rectangular portion of the canvas. Applications +always access the canvas through a viewport. + +### View +In Flipper's terminology, a "View" is a fullscreen design element that assumes +control over the entire viewport and all input events. Different types of views +are available (not all of which are unfortunately currently implemented in JS): +| View | Has JS adapter? | +|----------------------|------------------| +| `button_menu` | ❌ | +| `button_panel` | ❌ | +| `byte_input` | ❌ | +| `dialog_ex` | ✅ (as `dialog`) | +| `empty_screen` | ✅ | +| `file_browser` | ❌ | +| `loading` | ✅ | +| `menu` | ❌ | +| `number_input` | ❌ | +| `popup` | ❌ | +| `submenu` | ✅ | +| `text_box` | ✅ | +| `text_input` | ✅ | +| `variable_item_list` | ❌ | +| `widget` | ❌ | + +In JS, each view has its own set of properties (or just "props"). The programmer +can manipulate these properties in two ways: + - Instantiate a `View` using the `makeWith(props)` method, passing an object + with the initial properties + - Call `set(name, value)` to modify a property of an existing `View` + +### View Dispatcher +The view dispatcher holds references to all the views that an application needs +and switches between them as the application makes requests to do so. + +### Scene Manager +The scene manager is an optional add-on to the view dispatcher that makes +managing applications with complex navigation flows easier. It is currently +inaccessible from JS. + +### Approaches +In total, there are three different approaches that you may take when writing +a GUI application: +| Approach | Use cases | Available from JS | +|----------------|------------------------------------------------------------------------------|-------------------| +| ViewPort only | Accessing the graphics API directly, without any of the nice UI abstractions | ❌ | +| ViewDispatcher | Common UI elements that fit with the overall look of the system | ✅ | +| SceneManager | Additional navigation flow management for complex applications | ❌ | + +# Example +An example with three different views using the ViewDispatcher approach: +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let loadingView = require("gui/loading"); +let submenuView = require("gui/submenu"); +let emptyView = require("gui/empty_screen"); + +// Common pattern: declare all the views in an object. This is absolutely not +// required, but adds clarity to the script. +let views = { + // the view dispatcher auto-✨magically✨ remembers views as they are created + loading: loadingView.make(), + empty: emptyView.make(), + demos: submenuView.makeWith({ + items: [ + "Hourglass screen", + "Empty screen", + "Exit app", + ], + }), +}; + +// go to different screens depending on what was selected +eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) { + if (index === 0) { + gui.viewDispatcher.switchTo(views.loading); + } else if (index === 1) { + gui.viewDispatcher.switchTo(views.empty); + } else if (index === 2) { + eventLoop.stop(); + } +}, gui, eventLoop, views); + +// go to the demo chooser screen when the back key is pressed +eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) { + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// run UI +gui.viewDispatcher.switchTo(views.demos); +eventLoop.run(); +``` + +# API reference +## `viewDispatcher` +The `viewDispatcher` constant holds the `ViewDispatcher` singleton. + +### `viewDispatcher.switchTo(view)` +Switches to a view, giving it control over the display and input + +#### Parameters + - `view`: the `View` to switch to + +### `viewDispatcher.sendTo(direction)` +Sends the viewport that the dispatcher manages to the front of the stackup +(effectively making it visible), or to the back (effectively making it +invisible) + +#### Parameters + - `direction`: either `"front"` or `"back"` + +### `viewDispatcher.sendCustom(event)` +Sends a custom number to the `custom` event handler + +#### Parameters + - `event`: number to send + +### `viewDispatcher.custom` +An event loop `Contract` object that identifies the custom event source, +triggered by `ViewDispatcher.sendCustom(event)` + +### `viewDispatcher.navigation` +An event loop `Contract` object that identifies the navigation event source, +triggered when the back key is pressed + +## `ViewFactory` +When you import a module implementing a view, a `ViewFactory` is instantiated. +For example, in the example above, `loadingView`, `submenuView` and `emptyView` +are view factories. + +### `ViewFactory.make()` +Creates an instance of a `View` + +### `ViewFactory.make(props)` +Creates an instance of a `View` and assigns initial properties from `props` + +#### Parameters + - `props`: simple key-value object, e.g. `{ header: "Header" }` diff --git a/documentation/js/js_gui__dialog.md b/documentation/js/js_gui__dialog.md new file mode 100644 index 0000000000..445e711282 --- /dev/null +++ b/documentation/js/js_gui__dialog.md @@ -0,0 +1,53 @@ +# js_gui__dialog {#js_gui__dialog} + +# Dialog GUI view +Displays a dialog with up to three options. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let dialogView = require("gui/dialog"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `header` +Text that appears in bold at the top of the screen + +Type: `string` + +## `text` +Text that appears in the middle of the screen + +Type: `string` + +## `left` +Text for the left button. If unset, the left button does not show up. + +Type: `string` + +## `center` +Text for the center button. If unset, the center button does not show up. + +Type: `string` + +## `right` +Text for the right button. If unset, the right button does not show up. + +Type: `string` + +# View events +## `input` +Fires when the user presses on either of the three possible buttons. The item +contains one of the strings `"left"`, `"center"` or `"right"` depending on the +button. + +Item type: `string` diff --git a/documentation/js/js_gui__empty_screen.md b/documentation/js/js_gui__empty_screen.md new file mode 100644 index 0000000000..f9fd12553a --- /dev/null +++ b/documentation/js/js_gui__empty_screen.md @@ -0,0 +1,22 @@ +# js_gui__empty_screen {#js_gui__empty_screen} + +# Empty Screen GUI View +Displays nothing. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let emptyView = require("gui/empty_screen"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +This view does not have any props. diff --git a/documentation/js/js_gui__loading.md b/documentation/js/js_gui__loading.md new file mode 100644 index 0000000000..52f1cea49e --- /dev/null +++ b/documentation/js/js_gui__loading.md @@ -0,0 +1,23 @@ +# js_gui__loading {#js_gui__loading} + +# Loading GUI View +Displays an animated hourglass icon. Suppresses all `navigation` events, making +it impossible for the user to exit the view by pressing the back key. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let loadingView = require("gui/loading"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +This view does not have any props. diff --git a/documentation/js/js_gui__submenu.md b/documentation/js/js_gui__submenu.md new file mode 100644 index 0000000000..28c1e65af2 --- /dev/null +++ b/documentation/js/js_gui__submenu.md @@ -0,0 +1,37 @@ +# js_gui__submenu {#js_gui__submenu} + +# Submenu GUI view +Displays a scrollable list of clickable textual entries. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let submenuView = require("gui/submenu"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +## `header` +Single line of text that appears above the list + +Type: `string` + +## `items` +The list of options + +Type: `string[]` + +# View events +## `chosen` +Fires when an entry has been chosen by the user. The item contains the index of +the entry. + +Item type: `number` diff --git a/documentation/js/js_gui__text_box.md b/documentation/js/js_gui__text_box.md new file mode 100644 index 0000000000..bdad8d8b36 --- /dev/null +++ b/documentation/js/js_gui__text_box.md @@ -0,0 +1,25 @@ +# js_gui__text_box {#js_gui__text_box} + +# Text box GUI view +Displays a scrollable read-only text field. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let textBoxView = require("gui/text_box"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `text` +Text to show in the text box. + +Type: `string` diff --git a/documentation/js/js_gui__text_input.md b/documentation/js/js_gui__text_input.md new file mode 100644 index 0000000000..030579e2e2 --- /dev/null +++ b/documentation/js/js_gui__text_input.md @@ -0,0 +1,44 @@ +# js_gui__text_input {#js_gui__text_input} + +# Text input GUI view +Displays a keyboard. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let textInputView = require("gui/text_input"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `minLength` +Smallest allowed text length + +Type: `number` + +## `maxLength` +Biggest allowed text length + +Type: `number` + +Default: `32` + +## `header` +Single line of text that appears above the keyboard + +Type: `string` + +# View events +## `input` +Fires when the user selects the "save" button and the text matches the length +constrained by `minLength` and `maxLength`. + +Item type: `string` diff --git a/documentation/js/js_submenu.md b/documentation/js/js_submenu.md deleted file mode 100644 index 580a43bd5c..0000000000 --- a/documentation/js/js_submenu.md +++ /dev/null @@ -1,48 +0,0 @@ -# js_submenu {#js_submenu} - -# Submenu module -```js -let submenu = require("submenu"); -``` -# Methods - -## setHeader -Set the submenu header text. - -### Parameters -- header (string): The submenu header text - -### Example -```js -submenu.setHeader("Select an option:"); -``` - -## addItem -Add a new submenu item. - -### Parameters -- label (string): The submenu item label text -- id (number): The submenu item ID, must be a Uint32 number - -### Example -```js -submenu.addItem("Option 1", 1); -submenu.addItem("Option 2", 2); -submenu.addItem("Option 3", 3); -``` - -## show -Show a submenu that was previously configured using `setHeader()` and `addItem()` methods. - -### Returns -The ID of the submenu item that was selected, or `undefined` if the BACK button was pressed. - -### Example -```js -let selected = submenu.show(); -if (selected === undefined) { - // if BACK button was pressed -} else if (selected === 1) { - // if item with ID 1 was selected -} -``` diff --git a/documentation/js/js_textbox.md b/documentation/js/js_textbox.md deleted file mode 100644 index 61652df1a6..0000000000 --- a/documentation/js/js_textbox.md +++ /dev/null @@ -1,69 +0,0 @@ -# js_textbox {#js_textbox} - -# Textbox module -```js -let textbox = require("textbox"); -``` -# Methods - -## setConfig -Set focus and font for the textbox. - -### Parameters -- focus: "start" to focus on the beginning of the text, or "end" to focus on the end of the text -- font: "text" to use the default proportional font, or "hex" to use a monospaced font, which is convenient for aligned array output in HEX - -### Example -```js -textbox.setConfig("start", "text"); -textbox.addText("Hello world"); -textbox.show(); -``` - -## addText -Add text to the end of the textbox. - -### Parameters -- text (string): The text to add to the end of the textbox - -### Example -```js -textbox.addText("New text 1\nNew text 2"); -``` - -## clearText -Clear the textbox. - -### Example -```js -textbox.clearText(); -``` - -## isOpen -Return true if the textbox is open. - -### Returns -True if the textbox is open, false otherwise. - -### Example -```js -let isOpen = textbox.isOpen(); -``` - -## show -Show the textbox. You can add text to it using the `addText()` method before or after calling the `show()` method. - -### Example -```js -textbox.show(); -``` - -## close -Close the textbox. - -### Example -```js -if (textbox.isOpen()) { - textbox.close(); -} -``` diff --git a/fbt_options.py b/fbt_options.py index e30f7fc2d9..a7705335ad 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -75,6 +75,7 @@ "updater_app", "radio_device_cc1101_ext", "unit_tests", + "js_app", ], } diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index f4f008a71b..b622aa7a1c 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -418,6 +418,18 @@ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* o FURI_CRITICAL_EXIT(); } +bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object) { + furi_check(instance); + furi_check(instance->thread_id == furi_thread_get_current_id()); + FURI_CRITICAL_ENTER(); + + FuriEventLoopItem* const* item = FuriEventLoopTree_cget(instance->tree, object); + bool result = !!item; + + FURI_CRITICAL_EXIT(); + return result; +} + /* * Private Event Loop Item functions */ diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index af5987101d..6c5ba432c7 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -289,6 +289,23 @@ void furi_event_loop_subscribe_mutex( */ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object); +/** + * @brief Checks if the loop is subscribed to an object of any kind + * + * @param instance Event Loop instance + * @param object Object to check + */ +bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object); + +/** + * @brief Convenience function for `if(is_subscribed()) unsubscribe()` + */ +static inline void + furi_event_loop_maybe_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) { + if(furi_event_loop_is_subscribed(instance, object)) + furi_event_loop_unsubscribe(instance, object); +} + #ifdef __cplusplus } #endif diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index bcdcb364ab..f3e28a5bae 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -103,6 +103,7 @@ struct mjs* mjs_create(void* context) { sizeof(struct mjs_object), MJS_OBJECT_ARENA_SIZE, MJS_OBJECT_ARENA_INC_SIZE); + mjs->object_arena.destructor = mjs_obj_destructor; gc_arena_init( &mjs->property_arena, sizeof(struct mjs_property), diff --git a/lib/mjs/mjs_object.c b/lib/mjs/mjs_object.c index 2aea1bd46a..60bacf5145 100644 --- a/lib/mjs/mjs_object.c +++ b/lib/mjs/mjs_object.c @@ -9,6 +9,7 @@ #include "mjs_primitive.h" #include "mjs_string.h" #include "mjs_util.h" +#include "furi.h" #include "common/mg_str.h" @@ -20,6 +21,19 @@ MJS_PRIVATE mjs_val_t mjs_object_to_value(struct mjs_object* o) { } } +MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell) { + struct mjs_object* obj = cell; + mjs_val_t obj_val = mjs_object_to_value(obj); + + struct mjs_property* destructor = mjs_get_own_property( + mjs, obj_val, MJS_DESTRUCTOR_PROP_NAME, strlen(MJS_DESTRUCTOR_PROP_NAME)); + if(!destructor) return; + if(!mjs_is_foreign(destructor->value)) return; + + mjs_custom_obj_destructor_t destructor_fn = mjs_get_ptr(mjs, destructor->value); + if(destructor_fn) destructor_fn(mjs, obj_val); +} + MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) { struct mjs_object* ret = NULL; if(mjs_is_null(v)) { @@ -293,7 +307,8 @@ mjs_val_t * start from the end so the constructed object more closely resembles * the definition. */ - while(def->name != NULL) def++; + while(def->name != NULL) + def++; for(def--; def >= defs; def--) { mjs_val_t v = MJS_UNDEFINED; const char* ptr = (const char*)base + def->offset; diff --git a/lib/mjs/mjs_object.h b/lib/mjs/mjs_object.h index 1c4810385a..870486d06f 100644 --- a/lib/mjs/mjs_object.h +++ b/lib/mjs/mjs_object.h @@ -50,6 +50,11 @@ MJS_PRIVATE mjs_err_t mjs_set_internal( */ MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs); +/* + * Cell destructor for object arena + */ +MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell); + #define MJS_PROTO_PROP_NAME "__p" /* Make it < 5 chars */ #if defined(__cplusplus) diff --git a/lib/mjs/mjs_object_public.h b/lib/mjs/mjs_object_public.h index f9f06c6164..1a021a9d85 100644 --- a/lib/mjs/mjs_object_public.h +++ b/lib/mjs/mjs_object_public.h @@ -119,6 +119,14 @@ int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len); */ mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator); +typedef void (*mjs_custom_obj_destructor_t)(struct mjs* mjs, mjs_val_t object); + +/* + * Destructor property name. If set, must be a foreign pointer to a function + * that will be called just before the object is freed. + */ +#define MJS_DESTRUCTOR_PROP_NAME "__d" + #if defined(__cplusplus) } #endif /* __cplusplus */ diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 9b2160d53d..7943c4cfcb 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.1,, +Version,+,77.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1116,6 +1116,7 @@ Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_event_loop_alloc,FuriEventLoop*, Function,+,furi_event_loop_free,void,FuriEventLoop* +Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" Function,+,furi_event_loop_run,void,FuriEventLoop* Function,+,furi_event_loop_stop,void,FuriEventLoop* @@ -1372,6 +1373,8 @@ Function,-,furi_hal_resources_deinit_early,void, Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, +Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char* +Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,DateTime* @@ -2687,6 +2690,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput* Function,+,text_input_get_view,View*,TextInput* Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" Function,-,tgamma,double,double @@ -2761,6 +2765,7 @@ Function,+,view_allocate_model,void,"View*, ViewModelType, size_t" Function,+,view_commit_model,void,"View*, _Bool" Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*" Function,+,view_dispatcher_alloc,ViewDispatcher*, +Function,+,view_dispatcher_alloc_ex,ViewDispatcher*,FuriEventLoop* Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* diff --git a/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c index 45ca3e6c49..2e3654435c 100644 --- a/targets/f18/furi_hal/furi_hal_resources.c +++ b/targets/f18/furi_hal/furi_hal_resources.c @@ -354,3 +354,19 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { } return -1; } + +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(strcasecmp(name, record->name) == 0) return record; + } + return NULL; +} + +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(record->number == number) return record; + } + return NULL; +} diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 8f6173eb9d..9a0d04cb66 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -121,6 +121,26 @@ void furi_hal_resources_init(void); */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); +/** + * @brief Finds a pin by its name + * + * @param name case-insensitive pin name to look for (e.g. `"Pc3"`, `"pA4"`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name); + +/** + * @brief Finds a pin by its number + * + * @param name pin number to look for (e.g. `7`, `4`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number); + #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index b5dab0f281..c121fc7168 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.1,, +Version,+,77.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1221,6 +1221,7 @@ Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_event_loop_alloc,FuriEventLoop*, Function,+,furi_event_loop_free,void,FuriEventLoop* +Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" Function,+,furi_event_loop_run,void,FuriEventLoop* Function,+,furi_event_loop_stop,void,FuriEventLoop* @@ -1536,6 +1537,8 @@ Function,-,furi_hal_resources_deinit_early,void, Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, +Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char* +Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t Function,+,furi_hal_rfid_comp_set_callback,void,"FuriHalRfidCompCallback, void*" Function,+,furi_hal_rfid_comp_start,void, Function,+,furi_hal_rfid_comp_stop,void, @@ -3531,6 +3534,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput* Function,+,text_input_get_view,View*,TextInput* Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" Function,-,tgamma,double,double @@ -3605,6 +3609,7 @@ Function,+,view_allocate_model,void,"View*, ViewModelType, size_t" Function,+,view_commit_model,void,"View*, _Bool" Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*" Function,+,view_dispatcher_alloc,ViewDispatcher*, +Function,+,view_dispatcher_alloc_ex,ViewDispatcher*,FuriEventLoop* Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index 486c24230e..123ebc4208 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -288,3 +288,19 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { } return -1; } + +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(strcasecmp(name, record->name) == 0) return record; + } + return NULL; +} + +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(record->number == number) return record; + } + return NULL; +} diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index c01b2207ff..ec8794cc1e 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -227,6 +227,26 @@ void furi_hal_resources_init(void); */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); +/** + * @brief Finds a pin by its name + * + * @param name case-insensitive pin name to look for (e.g. `"Pc3"`, `"pA4"`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name); + +/** + * @brief Finds a pin by its number + * + * @param name pin number to look for (e.g. `7`, `4`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number); + #ifdef __cplusplus } #endif diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..2655a8b97b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "checkJs": true, + "module": "CommonJS", + "typeRoots": [ + "./applications/system/js_app/types" + ], + "noLib": true, + }, + "include": [ + "./applications/system/js_app/examples/apps/Scripts", + "./applications/debug/unit_tests/resources/unit_tests/js", + "./applications/system/js_app/types/global.d.ts", + ] +} \ No newline at end of file From fbc3b494b7c750c70c3f7871e9b041c7acd1dfd9 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Tue, 15 Oct 2024 04:05:24 +0800 Subject: [PATCH 145/236] NFC: H World Hotel Chain Room Key Parser (#3946) * quick and easy implementation * delete debug artifacts * log level correction (warning -> info) --- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/hworld.c | 243 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/hworld.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 180be62243..84151dd48b 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -200,6 +200,15 @@ App( sources=["plugins/supported_cards/skylanders.c"], ) +App( + appid="hworld_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="hworld_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/hworld.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/hworld.c b/applications/main/nfc/plugins/supported_cards/hworld.c new file mode 100644 index 0000000000..674e7b9550 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/hworld.c @@ -0,0 +1,243 @@ +// Flipper Zero parser for H World Hotel Key Cards +// H World operates around 10,000 hotels, most of which in mainland China +// Reverse engineering and parser written by @Torron (Github: @zinongli) +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include +#include + +#define TAG "H World" +#define ROOM_SECTOR 1 +#define VIP_SECTOR 5 +#define ROOM_SECTOR_KEY_BLOCK 7 +#define VIP_SECTOR_KEY_BLOCK 23 +#define ACCESS_INFO_BLOCK 5 +#define ROOM_NUM_DECIMAL_BLOCK 6 +#define H_WORLD_YEAR_OFFSET 2000 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static MfClassicKeyPair hworld_standard_keys[] = { + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 000 + {.a = 0x543071543071, .b = 0x5F01015F0101}, // 001 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 002 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 003 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 004 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 005 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 006 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 007 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 008 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 009 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 010 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 011 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 012 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 013 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 014 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 015 +}; + +static MfClassicKeyPair hworld_vip_keys[] = { + {.a = 0x000000000000, .b = 0xFFFFFFFFFFFF}, // 000 + {.a = 0x543071543071, .b = 0x5F01015F0101}, // 001 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 002 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 003 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 004 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 005 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 006 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 007 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 008 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 009 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 010 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 011 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 012 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 013 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 014 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 015 +}; + +static bool hworld_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(ROOM_SECTOR); + + MfClassicKey standard_key = {0}; + bit_lib_num_to_bytes_be( + hworld_standard_keys[ROOM_SECTOR].a, COUNT_OF(standard_key.data), standard_key.data); + + MfClassicAuthContext auth_context; + MfClassicError standard_error = mf_classic_poller_sync_auth( + nfc, block_num, &standard_key, MfClassicKeyTypeA, &auth_context); + + if(standard_error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed static key check for block %u", block_num); + break; + } + + MfClassicKey vip_key = {0}; + bit_lib_num_to_bytes_be( + hworld_vip_keys[VIP_SECTOR].b, COUNT_OF(vip_key.data), vip_key.data); + + MfClassicError vip_error = mf_classic_poller_sync_auth( + nfc, block_num, &vip_key, MfClassicKeyTypeB, &auth_context); + + if(vip_error == MfClassicErrorNone) { + FURI_LOG_D(TAG, "VIP card detected"); + } else { + FURI_LOG_D(TAG, "Standard card detected"); + } + + verified = true; + } while(false); + + return verified; +} + +static bool hworld_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError standard_error = mf_classic_poller_sync_detect_type(nfc, &type); + MfClassicError vip_error = MfClassicErrorNotPresent; + if(standard_error != MfClassicErrorNone) break; + data->type = type; + + MfClassicDeviceKeys standard_keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + hworld_standard_keys[i].a, sizeof(MfClassicKey), standard_keys.key_a[i].data); + FURI_BIT_SET(standard_keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + hworld_standard_keys[i].b, sizeof(MfClassicKey), standard_keys.key_b[i].data); + FURI_BIT_SET(standard_keys.key_b_mask, i); + } + + standard_error = mf_classic_poller_sync_read(nfc, &standard_keys, data); + if(standard_error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Standard card successfully read"); + } else { + MfClassicDeviceKeys vip_keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + hworld_vip_keys[i].a, sizeof(MfClassicKey), vip_keys.key_a[i].data); + FURI_BIT_SET(vip_keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + hworld_vip_keys[i].b, sizeof(MfClassicKey), vip_keys.key_b[i].data); + FURI_BIT_SET(vip_keys.key_b_mask, i); + } + + vip_error = mf_classic_poller_sync_read(nfc, &vip_keys, data); + + if(vip_error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "VIP card successfully read"); + } else { + break; + } + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (standard_error == MfClassicErrorNone) | (vip_error == MfClassicErrorNone); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +bool hworld_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + bool parsed = false; + + do { + // Check card type + if(data->type != MfClassicType1k) break; + + // Check static key for verificaiton + const uint8_t* data_room_sec_key_a_ptr = &data->block[ROOM_SECTOR_KEY_BLOCK].data[0]; + const uint8_t* data_room_sec_key_b_ptr = &data->block[ROOM_SECTOR_KEY_BLOCK].data[10]; + uint64_t data_room_sec_key_a = bit_lib_get_bits_64(data_room_sec_key_a_ptr, 0, 48); + uint64_t data_room_sec_key_b = bit_lib_get_bits_64(data_room_sec_key_b_ptr, 0, 48); + if((data_room_sec_key_a != hworld_standard_keys[ROOM_SECTOR].a) | + (data_room_sec_key_b != hworld_standard_keys[ROOM_SECTOR].b)) + break; + + // Check whether this card is VIP + const uint8_t* data_vip_sec_key_b_ptr = &data->block[VIP_SECTOR_KEY_BLOCK].data[10]; + uint64_t data_vip_sec_key_b = bit_lib_get_bits_64(data_vip_sec_key_b_ptr, 0, 48); + bool is_hworld_vip = (data_vip_sec_key_b == hworld_vip_keys[VIP_SECTOR].b); + uint8_t room_floor = data->block[ACCESS_INFO_BLOCK].data[13]; + uint8_t room_num = data->block[ACCESS_INFO_BLOCK].data[14]; + + // Check in date & time + uint16_t check_in_year = data->block[ACCESS_INFO_BLOCK].data[2] + H_WORLD_YEAR_OFFSET; + uint8_t check_in_month = data->block[ACCESS_INFO_BLOCK].data[3]; + uint8_t check_in_day = data->block[ACCESS_INFO_BLOCK].data[4]; + uint8_t check_in_hour = data->block[ACCESS_INFO_BLOCK].data[5]; + uint8_t check_in_minute = data->block[ACCESS_INFO_BLOCK].data[6]; + + // Expire date & time + uint16_t expire_year = data->block[ACCESS_INFO_BLOCK].data[7] + H_WORLD_YEAR_OFFSET; + uint8_t expire_month = data->block[ACCESS_INFO_BLOCK].data[8]; + uint8_t expire_day = data->block[ACCESS_INFO_BLOCK].data[9]; + uint8_t expire_hour = data->block[ACCESS_INFO_BLOCK].data[10]; + uint8_t expire_minute = data->block[ACCESS_INFO_BLOCK].data[11]; + + furi_string_cat_printf(parsed_data, "\e#H World Card\n"); + furi_string_cat_printf( + parsed_data, "%s\n", is_hworld_vip ? "VIP card" : "Standard room key"); + furi_string_cat_printf(parsed_data, "Room Num: %u%02u\n", room_floor, room_num); + furi_string_cat_printf( + parsed_data, + "Check-in Date: \n%04u-%02d-%02d\n%02d:%02d:00\n", + check_in_year, + check_in_month, + check_in_day, + check_in_hour, + check_in_minute); + furi_string_cat_printf( + parsed_data, + "Expiration Date: \n%04u-%02d-%02d\n%02d:%02d:00", + expire_year, + expire_month, + expire_day, + expire_hour, + expire_minute); + parsed = true; + } while(false); + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin hworld_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = hworld_verify, + .read = hworld_read, + .parse = hworld_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor hworld_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &hworld_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* hworld_plugin_ep(void) { + return &hworld_plugin_descriptor; +} From 4b9b1769f77ab4ac2da65ab8289c8daaae8219ec Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:08:47 +0300 Subject: [PATCH 146/236] Merge remote-tracking branch 'OFW/dev' into dev --- applications/debug/unit_tests/application.fam | 8 + .../resources/unit_tests/js/basic.js | 4 + .../resources/unit_tests/js/event_loop.js | 30 +++ .../resources/unit_tests/js/math.js | 34 +++ .../resources/unit_tests/js/storage.js | 136 ++++++++++ .../debug/unit_tests/tests/js/js_test.c | 88 +++++++ applications/debug/unit_tests/tests/minunit.h | 5 +- .../debug/unit_tests/unit_test_api_table_i.h | 14 +- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/hworld.c | 243 ++++++++++++++++++ applications/services/gui/canvas.h | 15 +- .../services/gui/modules/text_input.h | 7 + applications/services/gui/view_dispatcher.c | 22 +- applications/services/gui/view_dispatcher.h | 13 + applications/services/gui/view_dispatcher_i.h | 1 + applications/services/storage/storage.h | 4 +- documentation/Doxyfile | 2 +- documentation/images/dialog.png | Bin 0 -> 1377 bytes documentation/images/empty.png | Bin 0 -> 1005 bytes documentation/images/loading.png | Bin 0 -> 1173 bytes documentation/images/submenu.png | Bin 0 -> 1774 bytes documentation/images/text_box.png | Bin 0 -> 2350 bytes documentation/images/text_input.png | Bin 0 -> 2044 bytes documentation/js/js_builtin.md | 12 +- documentation/js/js_dialog.md | 49 ---- documentation/js/js_event_loop.md | 144 +++++++++++ documentation/js/js_gpio.md | 77 ++++++ documentation/js/js_gui.md | 161 ++++++++++++ documentation/js/js_gui__dialog.md | 53 ++++ documentation/js/js_gui__empty_screen.md | 22 ++ documentation/js/js_gui__loading.md | 23 ++ documentation/js/js_gui__submenu.md | 37 +++ documentation/js/js_gui__text_box.md | 25 ++ documentation/js/js_gui__text_input.md | 44 ++++ documentation/js/js_submenu.md | 48 ---- documentation/js/js_textbox.md | 69 ----- fbt_options.py | 1 + firmware.scons | 1 + furi/core/event_loop.c | 12 + furi/core/event_loop.h | 17 ++ lib/mjs/mjs_core.c | 1 + lib/mjs/mjs_object.c | 17 +- lib/mjs/mjs_object.h | 5 + lib/mjs/mjs_object_public.h | 8 + .../iso14443_4a/iso14443_4a_listener.c | 5 +- targets/f18/api_symbols.csv | 7 +- targets/f18/furi_hal/furi_hal_resources.c | 16 ++ targets/f18/furi_hal/furi_hal_resources.h | 20 ++ targets/f7/api_symbols.csv | 6 +- targets/f7/furi_hal/furi_hal_resources.c | 16 ++ targets/f7/furi_hal/furi_hal_resources.h | 20 ++ targets/f7/stm32wb55xx_flash.ld | 2 +- targets/f7/stm32wb55xx_ram_fw.ld | 2 +- tsconfig.json | 15 ++ 54 files changed, 1357 insertions(+), 213 deletions(-) create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/basic.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/event_loop.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/math.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/storage.js create mode 100644 applications/debug/unit_tests/tests/js/js_test.c create mode 100644 applications/main/nfc/plugins/supported_cards/hworld.c create mode 100644 documentation/images/dialog.png create mode 100644 documentation/images/empty.png create mode 100644 documentation/images/loading.png create mode 100644 documentation/images/submenu.png create mode 100644 documentation/images/text_box.png create mode 100644 documentation/images/text_input.png delete mode 100644 documentation/js/js_dialog.md create mode 100644 documentation/js/js_event_loop.md create mode 100644 documentation/js/js_gpio.md create mode 100644 documentation/js/js_gui.md create mode 100644 documentation/js/js_gui__dialog.md create mode 100644 documentation/js/js_gui__empty_screen.md create mode 100644 documentation/js/js_gui__loading.md create mode 100644 documentation/js/js_gui__submenu.md create mode 100644 documentation/js/js_gui__text_box.md create mode 100644 documentation/js/js_gui__text_input.md delete mode 100644 documentation/js/js_submenu.md delete mode 100644 documentation/js/js_textbox.md create mode 100644 tsconfig.json diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index c87305847a..dec3283e4e 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -221,6 +221,14 @@ App( requires=["unit_tests"], ) +App( + appid="test_js", + sources=["tests/common/*.c", "tests/js/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests", "js_app"], +) + App( appid="test_strint", sources=["tests/common/*.c", "tests/strint/*.c"], diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js new file mode 100644 index 0000000000..0927595a2c --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -0,0 +1,4 @@ +let tests = require("tests"); + +tests.assert_eq(1337, 1337); +tests.assert_eq("hello", "hello"); diff --git a/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js b/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js new file mode 100644 index 0000000000..0437b82932 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js @@ -0,0 +1,30 @@ +let tests = require("tests"); +let event_loop = require("event_loop"); + +let ext = { + i: 0, + received: false, +}; + +let queue = event_loop.queue(16); + +event_loop.subscribe(queue.input, function (_, item, tests, ext) { + tests.assert_eq(123, item); + ext.received = true; +}, tests, ext); + +event_loop.subscribe(event_loop.timer("periodic", 1), function (_, _item, queue, counter, ext) { + ext.i++; + queue.send(123); + if (counter === 10) + event_loop.stop(); + return [queue, counter + 1, ext]; +}, queue, 1, ext); + +event_loop.subscribe(event_loop.timer("oneshot", 1000), function (_, _item, tests) { + tests.fail("event loop was not stopped"); +}, tests); + +event_loop.run(); +tests.assert_eq(10, ext.i); +tests.assert_eq(true, ext.received); diff --git a/applications/debug/unit_tests/resources/unit_tests/js/math.js b/applications/debug/unit_tests/resources/unit_tests/js/math.js new file mode 100644 index 0000000000..ea8d80f914 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/math.js @@ -0,0 +1,34 @@ +let tests = require("tests"); +let math = require("math"); + +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +// basics +tests.assert_float_close(5, math.abs(-5), math.EPSILON); +tests.assert_float_close(0.5, math.abs(-0.5), math.EPSILON); +tests.assert_float_close(5, math.abs(5), math.EPSILON); +tests.assert_float_close(0.5, math.abs(0.5), math.EPSILON); +tests.assert_float_close(3, math.cbrt(27), math.EPSILON); +tests.assert_float_close(6, math.ceil(5.3), math.EPSILON); +tests.assert_float_close(31, math.clz32(1), math.EPSILON); +tests.assert_float_close(5, math.floor(5.7), math.EPSILON); +tests.assert_float_close(5, math.max(3, 5), math.EPSILON); +tests.assert_float_close(3, math.min(3, 5), math.EPSILON); +tests.assert_float_close(-1, math.sign(-5), math.EPSILON); +tests.assert_float_close(5, math.trunc(5.7), math.EPSILON); + +// trig +tests.assert_float_close(1.0471975511965976, math.acos(0.5), math.EPSILON); +tests.assert_float_close(1.3169578969248166, math.acosh(2), math.EPSILON); +tests.assert_float_close(0.5235987755982988, math.asin(0.5), math.EPSILON); +tests.assert_float_close(1.4436354751788103, math.asinh(2), math.EPSILON); +tests.assert_float_close(0.7853981633974483, math.atan(1), math.EPSILON); +tests.assert_float_close(0.7853981633974483, math.atan2(1, 1), math.EPSILON); +tests.assert_float_close(0.5493061443340549, math.atanh(0.5), math.EPSILON); +tests.assert_float_close(-1, math.cos(math.PI), math.EPSILON * 18); // Error 3.77475828372553223744e-15 +tests.assert_float_close(1, math.sin(math.PI / 2), math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +// powers +tests.assert_float_close(5, math.sqrt(25), math.EPSILON); +tests.assert_float_close(8, math.pow(2, 3), math.EPSILON); +tests.assert_float_close(2.718281828459045, math.exp(1), math.EPSILON * 2); // Error 4.44089209850062616169e-16 diff --git a/applications/debug/unit_tests/resources/unit_tests/js/storage.js b/applications/debug/unit_tests/resources/unit_tests/js/storage.js new file mode 100644 index 0000000000..872b29cfbc --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/storage.js @@ -0,0 +1,136 @@ +let storage = require("storage"); +let tests = require("tests"); + +let baseDir = "/ext/.tmp/unit_tests"; + +tests.assert_eq(true, storage.rmrf(baseDir)); +tests.assert_eq(true, storage.makeDirectory(baseDir)); + +// write +let file = storage.openFile(baseDir + "/helloworld", "w", "create_always"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.write("Hello, World!")); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// read +file = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.size()); +tests.assert_eq("Hello, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// seek +file = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.size()); +tests.assert_eq("Hello, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.seekAbsolute(1)); +tests.assert_eq(true, file.seekRelative(2)); +tests.assert_eq(3, file.tell()); +tests.assert_eq(false, file.eof()); +tests.assert_eq("lo, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.eof()); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// byte-level copy +let src = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +let dst = storage.openFile(baseDir + "/helloworld2", "rw", "create_always"); +tests.assert_eq(true, !!src); +tests.assert_eq(true, src.isOpen()); +tests.assert_eq(true, !!dst); +tests.assert_eq(true, dst.isOpen()); +tests.assert_eq(true, src.copyTo(dst, 10)); +tests.assert_eq(true, dst.seekAbsolute(0)); +tests.assert_eq("Hello, Wor", dst.read("ascii", 128)); +tests.assert_eq(true, src.copyTo(dst, 3)); +tests.assert_eq(true, dst.seekAbsolute(0)); +tests.assert_eq("Hello, World!", dst.read("ascii", 128)); +tests.assert_eq(true, src.eof()); +tests.assert_eq(true, src.close()); +tests.assert_eq(false, src.isOpen()); +tests.assert_eq(true, dst.eof()); +tests.assert_eq(true, dst.close()); +tests.assert_eq(false, dst.isOpen()); + +// truncate +tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld2")); +file = storage.openFile(baseDir + "/helloworld2", "w", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.seekAbsolute(5)); +tests.assert_eq(true, file.truncate()); +tests.assert_eq(true, file.close()); +file = storage.openFile(baseDir + "/helloworld2", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq("Hello", file.read("ascii", 128)); +tests.assert_eq(true, file.close()); + +// existence +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld2")); +tests.assert_eq(false, storage.fileExists(baseDir + "/sus_amogus_123")); +tests.assert_eq(false, storage.directoryExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir)); +tests.assert_eq(true, storage.directoryExists(baseDir)); +tests.assert_eq(true, storage.fileOrDirExists(baseDir)); +tests.assert_eq(true, storage.remove(baseDir + "/helloworld2")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld2")); + +// stat +let stat = storage.stat(baseDir + "/helloworld"); +tests.assert_eq(true, !!stat); +tests.assert_eq(baseDir + "/helloworld", stat.path); +tests.assert_eq(false, stat.isDirectory); +tests.assert_eq(13, stat.size); + +// rename +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.rename(baseDir + "/helloworld", baseDir + "/helloworld123")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.rename(baseDir + "/helloworld123", baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); + +// copy +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld123")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123")); + +// next avail +tests.assert_eq("helloworld1", storage.nextAvailableFilename(baseDir, "helloworld", "", 20)); + +// fs info +let fsInfo = storage.fsInfo("/ext"); +tests.assert_eq(true, !!fsInfo); +tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); // idk \(-_-)/ +fsInfo = storage.fsInfo("/int"); +tests.assert_eq(true, !!fsInfo); +tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); + +// path operations +tests.assert_eq(true, storage.arePathsEqual("/ext/test", "/ext/Test")); +tests.assert_eq(false, storage.arePathsEqual("/ext/test", "/ext/Testttt")); +tests.assert_eq(true, storage.isSubpathOf("/ext/test", "/ext/test/sub")); +tests.assert_eq(false, storage.isSubpathOf("/ext/test/sub", "/ext/test")); + +// dir +let entries = storage.readDirectory(baseDir); +tests.assert_eq(true, !!entries); +// FIXME: (-nofl) this test suite assumes that files are listed by +// `readDirectory` in the exact order that they were created, which is not +// something that is actually guaranteed. +// Possible solution: sort and compare the array. +tests.assert_eq("helloworld", entries[0].path); +tests.assert_eq("helloworld123", entries[1].path); + +tests.assert_eq(true, storage.rmrf(baseDir)); +tests.assert_eq(true, storage.makeDirectory(baseDir)); diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c new file mode 100644 index 0000000000..af590e8995 --- /dev/null +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -0,0 +1,88 @@ +#include "../test.h" // IWYU pragma: keep + +#include +#include +#include + +#include +#include + +#include + +#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js") + +typedef enum { + JsTestsFinished = 1, + JsTestsError = 2, +} JsTestFlag; + +typedef struct { + FuriEventFlag* event_flags; + FuriString* error_string; +} JsTestCallbackContext; + +static void js_test_callback(JsThreadEvent event, const char* msg, void* param) { + JsTestCallbackContext* context = param; + if(event == JsThreadEventPrint) { + FURI_LOG_I("js_test", "%s", msg); + } else if(event == JsThreadEventError || event == JsThreadEventErrorTrace) { + context->error_string = furi_string_alloc_set_str(msg); + furi_event_flag_set(context->event_flags, JsTestsFinished | JsTestsError); + } else if(event == JsThreadEventDone) { + furi_event_flag_set(context->event_flags, JsTestsFinished); + } +} + +static void js_test_run(const char* script_path) { + JsTestCallbackContext* context = malloc(sizeof(JsTestCallbackContext)); + context->event_flags = furi_event_flag_alloc(); + + JsThread* thread = js_thread_run(script_path, js_test_callback, context); + uint32_t flags = furi_event_flag_wait( + context->event_flags, JsTestsFinished, FuriFlagWaitAny, FuriWaitForever); + if(flags & FuriFlagError) { + // getting the flags themselves should not fail + furi_crash(); + } + + FuriString* error_string = context->error_string; + + js_thread_stop(thread); + furi_event_flag_free(context->event_flags); + free(context); + + if(flags & JsTestsError) { + // memory leak: not freeing the FuriString if the tests fail, + // because mu_fail executes a return + // + // who cares tho? + mu_fail(furi_string_get_cstr(error_string)); + } +} + +MU_TEST(js_test_basic) { + js_test_run(JS_SCRIPT_PATH("basic")); +} +MU_TEST(js_test_math) { + js_test_run(JS_SCRIPT_PATH("math")); +} +MU_TEST(js_test_event_loop) { + js_test_run(JS_SCRIPT_PATH("event_loop")); +} +MU_TEST(js_test_storage) { + js_test_run(JS_SCRIPT_PATH("storage")); +} + +MU_TEST_SUITE(test_js) { + MU_RUN_TEST(js_test_basic); + MU_RUN_TEST(js_test_math); + MU_RUN_TEST(js_test_event_loop); + MU_RUN_TEST(js_test_storage); +} + +int run_minunit_test_js(void) { + MU_RUN_SUITE(test_js); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_js) diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index 9310cfc9c9..9ca3bb403d 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -31,7 +31,7 @@ extern "C" { #include #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf -#define __func__ __FUNCTION__ +#define __func__ __FUNCTION__ //-V1059 #endif #elif defined(__unix__) || defined(__unix) || defined(unix) || \ @@ -56,7 +56,7 @@ extern "C" { #endif #if __GNUC__ >= 5 && !defined(__STDC_VERSION__) -#define __func__ __extension__ __FUNCTION__ +#define __func__ __extension__ __FUNCTION__ //-V1059 #endif #else @@ -102,6 +102,7 @@ void minunit_printf_warning(const char* format, ...); MU__SAFE_BLOCK(minunit_setup = setup_fun; minunit_teardown = teardown_fun;) /* Test runner */ +//-V:MU_RUN_TEST:550 #define MU_RUN_TEST(test) \ MU__SAFE_BLOCK( \ if(minunit_real_timer == 0 && minunit_proc_timer == 0) { \ diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 50524e5b7d..10b0890225 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -7,7 +7,7 @@ #include #include -#include +#include static constexpr auto unit_tests_api_table = sort(create_array_t( API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), @@ -33,13 +33,9 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( xQueueGenericSend, BaseType_t, (QueueHandle_t, const void* const, TickType_t, const BaseType_t)), - API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)), - API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)), API_METHOD( - furi_event_loop_subscribe_message_queue, - void, - (FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)), - API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)), - API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)), - API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)), + js_thread_run, + JsThread*, + (const char* script_path, JsThreadCallback callback, void* context)), + API_METHOD(js_thread_stop, void, (JsThread * worker)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index ae5372910c..58660253f1 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -272,6 +272,15 @@ App( sources=["plugins/supported_cards/skylanders.c"], ) +App( + appid="hworld_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="hworld_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/hworld.c"], +) + App( appid="nfc_cli", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/hworld.c b/applications/main/nfc/plugins/supported_cards/hworld.c new file mode 100644 index 0000000000..674e7b9550 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/hworld.c @@ -0,0 +1,243 @@ +// Flipper Zero parser for H World Hotel Key Cards +// H World operates around 10,000 hotels, most of which in mainland China +// Reverse engineering and parser written by @Torron (Github: @zinongli) +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include +#include + +#define TAG "H World" +#define ROOM_SECTOR 1 +#define VIP_SECTOR 5 +#define ROOM_SECTOR_KEY_BLOCK 7 +#define VIP_SECTOR_KEY_BLOCK 23 +#define ACCESS_INFO_BLOCK 5 +#define ROOM_NUM_DECIMAL_BLOCK 6 +#define H_WORLD_YEAR_OFFSET 2000 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static MfClassicKeyPair hworld_standard_keys[] = { + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 000 + {.a = 0x543071543071, .b = 0x5F01015F0101}, // 001 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 002 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 003 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 004 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 005 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 006 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 007 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 008 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 009 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 010 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 011 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 012 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 013 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 014 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 015 +}; + +static MfClassicKeyPair hworld_vip_keys[] = { + {.a = 0x000000000000, .b = 0xFFFFFFFFFFFF}, // 000 + {.a = 0x543071543071, .b = 0x5F01015F0101}, // 001 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 002 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 003 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 004 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 005 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 006 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 007 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 008 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 009 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 010 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 011 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 012 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 013 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 014 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 015 +}; + +static bool hworld_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(ROOM_SECTOR); + + MfClassicKey standard_key = {0}; + bit_lib_num_to_bytes_be( + hworld_standard_keys[ROOM_SECTOR].a, COUNT_OF(standard_key.data), standard_key.data); + + MfClassicAuthContext auth_context; + MfClassicError standard_error = mf_classic_poller_sync_auth( + nfc, block_num, &standard_key, MfClassicKeyTypeA, &auth_context); + + if(standard_error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed static key check for block %u", block_num); + break; + } + + MfClassicKey vip_key = {0}; + bit_lib_num_to_bytes_be( + hworld_vip_keys[VIP_SECTOR].b, COUNT_OF(vip_key.data), vip_key.data); + + MfClassicError vip_error = mf_classic_poller_sync_auth( + nfc, block_num, &vip_key, MfClassicKeyTypeB, &auth_context); + + if(vip_error == MfClassicErrorNone) { + FURI_LOG_D(TAG, "VIP card detected"); + } else { + FURI_LOG_D(TAG, "Standard card detected"); + } + + verified = true; + } while(false); + + return verified; +} + +static bool hworld_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError standard_error = mf_classic_poller_sync_detect_type(nfc, &type); + MfClassicError vip_error = MfClassicErrorNotPresent; + if(standard_error != MfClassicErrorNone) break; + data->type = type; + + MfClassicDeviceKeys standard_keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + hworld_standard_keys[i].a, sizeof(MfClassicKey), standard_keys.key_a[i].data); + FURI_BIT_SET(standard_keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + hworld_standard_keys[i].b, sizeof(MfClassicKey), standard_keys.key_b[i].data); + FURI_BIT_SET(standard_keys.key_b_mask, i); + } + + standard_error = mf_classic_poller_sync_read(nfc, &standard_keys, data); + if(standard_error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Standard card successfully read"); + } else { + MfClassicDeviceKeys vip_keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + hworld_vip_keys[i].a, sizeof(MfClassicKey), vip_keys.key_a[i].data); + FURI_BIT_SET(vip_keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + hworld_vip_keys[i].b, sizeof(MfClassicKey), vip_keys.key_b[i].data); + FURI_BIT_SET(vip_keys.key_b_mask, i); + } + + vip_error = mf_classic_poller_sync_read(nfc, &vip_keys, data); + + if(vip_error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "VIP card successfully read"); + } else { + break; + } + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (standard_error == MfClassicErrorNone) | (vip_error == MfClassicErrorNone); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +bool hworld_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + bool parsed = false; + + do { + // Check card type + if(data->type != MfClassicType1k) break; + + // Check static key for verificaiton + const uint8_t* data_room_sec_key_a_ptr = &data->block[ROOM_SECTOR_KEY_BLOCK].data[0]; + const uint8_t* data_room_sec_key_b_ptr = &data->block[ROOM_SECTOR_KEY_BLOCK].data[10]; + uint64_t data_room_sec_key_a = bit_lib_get_bits_64(data_room_sec_key_a_ptr, 0, 48); + uint64_t data_room_sec_key_b = bit_lib_get_bits_64(data_room_sec_key_b_ptr, 0, 48); + if((data_room_sec_key_a != hworld_standard_keys[ROOM_SECTOR].a) | + (data_room_sec_key_b != hworld_standard_keys[ROOM_SECTOR].b)) + break; + + // Check whether this card is VIP + const uint8_t* data_vip_sec_key_b_ptr = &data->block[VIP_SECTOR_KEY_BLOCK].data[10]; + uint64_t data_vip_sec_key_b = bit_lib_get_bits_64(data_vip_sec_key_b_ptr, 0, 48); + bool is_hworld_vip = (data_vip_sec_key_b == hworld_vip_keys[VIP_SECTOR].b); + uint8_t room_floor = data->block[ACCESS_INFO_BLOCK].data[13]; + uint8_t room_num = data->block[ACCESS_INFO_BLOCK].data[14]; + + // Check in date & time + uint16_t check_in_year = data->block[ACCESS_INFO_BLOCK].data[2] + H_WORLD_YEAR_OFFSET; + uint8_t check_in_month = data->block[ACCESS_INFO_BLOCK].data[3]; + uint8_t check_in_day = data->block[ACCESS_INFO_BLOCK].data[4]; + uint8_t check_in_hour = data->block[ACCESS_INFO_BLOCK].data[5]; + uint8_t check_in_minute = data->block[ACCESS_INFO_BLOCK].data[6]; + + // Expire date & time + uint16_t expire_year = data->block[ACCESS_INFO_BLOCK].data[7] + H_WORLD_YEAR_OFFSET; + uint8_t expire_month = data->block[ACCESS_INFO_BLOCK].data[8]; + uint8_t expire_day = data->block[ACCESS_INFO_BLOCK].data[9]; + uint8_t expire_hour = data->block[ACCESS_INFO_BLOCK].data[10]; + uint8_t expire_minute = data->block[ACCESS_INFO_BLOCK].data[11]; + + furi_string_cat_printf(parsed_data, "\e#H World Card\n"); + furi_string_cat_printf( + parsed_data, "%s\n", is_hworld_vip ? "VIP card" : "Standard room key"); + furi_string_cat_printf(parsed_data, "Room Num: %u%02u\n", room_floor, room_num); + furi_string_cat_printf( + parsed_data, + "Check-in Date: \n%04u-%02d-%02d\n%02d:%02d:00\n", + check_in_year, + check_in_month, + check_in_day, + check_in_hour, + check_in_minute); + furi_string_cat_printf( + parsed_data, + "Expiration Date: \n%04u-%02d-%02d\n%02d:%02d:00", + expire_year, + expire_month, + expire_day, + expire_hour, + expire_minute); + parsed = true; + } while(false); + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin hworld_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = hworld_verify, + .read = hworld_read, + .parse = hworld_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor hworld_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &hworld_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* hworld_plugin_ep(void) { + return &hworld_plugin_descriptor; +} diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 86f7e9808e..efd3146871 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -298,15 +298,14 @@ void canvas_draw_xbm( /** Draw rotated XBM bitmap * - * @param canvas Canvas instance - * @param x x coordinate - * @param y y coordinate - * @param[in] width bitmap width - * @param[in] height bitmap height - * @param[in] rotation bitmap rotation - * @param bitmap pointer to XBM bitmap data + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param[in] width bitmap width + * @param[in] height bitmap height + * @param[in] rotation bitmap rotation + * @param bitmap_data pointer to XBM bitmap data */ - void canvas_draw_xbm_ex( Canvas* canvas, int32_t x, diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index 6733c01ccc..1c020d1869 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -65,6 +65,13 @@ void text_input_set_result_callback( size_t text_buffer_size, bool clear_default_text); +/** + * @brief Sets the minimum length of a TextInput + * @param [in] text_input TextInput + * @param [in] minimum_length Minimum input length + */ +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length); + void text_input_set_validator( TextInput* text_input, TextInputValidatorCallback callback, diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 63878fc190..6db4d82412 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -5,6 +5,12 @@ #define VIEW_DISPATCHER_QUEUE_LEN (16U) ViewDispatcher* view_dispatcher_alloc(void) { + ViewDispatcher* dispatcher = view_dispatcher_alloc_ex(furi_event_loop_alloc()); + dispatcher->is_event_loop_owned = true; + return dispatcher; +} + +ViewDispatcher* view_dispatcher_alloc_ex(FuriEventLoop* loop) { ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher)); view_dispatcher->view_port = view_port_alloc(); @@ -16,7 +22,7 @@ ViewDispatcher* view_dispatcher_alloc(void) { ViewDict_init(view_dispatcher->views); - view_dispatcher->event_loop = furi_event_loop_alloc(); + view_dispatcher->event_loop = loop; view_dispatcher->input_queue = furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(InputEvent)); @@ -57,7 +63,7 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) { furi_message_queue_free(view_dispatcher->input_queue); furi_message_queue_free(view_dispatcher->event_queue); - furi_event_loop_free(view_dispatcher->event_loop); + if(view_dispatcher->is_event_loop_owned) furi_event_loop_free(view_dispatcher->event_loop); // Free dispatcher free(view_dispatcher); } @@ -85,6 +91,7 @@ void view_dispatcher_set_tick_event_callback( ViewDispatcherTickEventCallback callback, uint32_t tick_period) { furi_check(view_dispatcher); + furi_check(view_dispatcher->is_event_loop_owned); view_dispatcher->tick_event_callback = callback; view_dispatcher->tick_period = tick_period; } @@ -106,11 +113,12 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) { uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever : view_dispatcher->tick_period; - furi_event_loop_tick_set( - view_dispatcher->event_loop, - tick_period, - view_dispatcher_handle_tick_event, - view_dispatcher); + if(view_dispatcher->is_event_loop_owned) + furi_event_loop_tick_set( + view_dispatcher->event_loop, + tick_period, + view_dispatcher_handle_tick_event, + view_dispatcher); furi_event_loop_run(view_dispatcher->event_loop); diff --git a/applications/services/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h index 9fbf897918..5820bcad3b 100644 --- a/applications/services/gui/view_dispatcher.h +++ b/applications/services/gui/view_dispatcher.h @@ -47,6 +47,15 @@ typedef void (*ViewDispatcherTickEventCallback)(void* context); */ ViewDispatcher* view_dispatcher_alloc(void); +/** Allocate ViewDispatcher instance with an externally owned event loop. If + * this constructor is used instead of `view_dispatcher_alloc`, the burden of + * freeing the event loop is placed on the caller. + * + * @param loop pointer to FuriEventLoop instance + * @return pointer to ViewDispatcher instance + */ +ViewDispatcher* view_dispatcher_alloc_ex(FuriEventLoop* loop); + /** Free ViewDispatcher instance * * @warning All added views MUST be removed using view_dispatcher_remove_view() @@ -97,6 +106,10 @@ void view_dispatcher_set_navigation_event_callback( /** Set tick event handler * + * @warning Requires the event loop to be owned by the view dispatcher, i.e. + * it should have been instantiated with `view_dispatcher_alloc`, not + * `view_dispatcher_alloc_ex`. + * * @param view_dispatcher ViewDispatcher instance * @param callback ViewDispatcherTickEventCallback * @param tick_period callback call period diff --git a/applications/services/gui/view_dispatcher_i.h b/applications/services/gui/view_dispatcher_i.h index c6c8dc665c..3d84b54995 100644 --- a/applications/services/gui/view_dispatcher_i.h +++ b/applications/services/gui/view_dispatcher_i.h @@ -14,6 +14,7 @@ DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) // NOLINT struct ViewDispatcher { + bool is_event_loop_owned; FuriEventLoop* event_loop; FuriMessageQueue* input_queue; FuriMessageQueue* event_queue; diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index ea0ff24ade..c28a5e10f5 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -377,7 +377,7 @@ void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, Furi * @param storage pointer to a storage API instance. * @param source pointer to a zero-terminated string containing the source path. * @param dest pointer to a zero-terminated string containing the destination path. - * @return FSE_OK if the migration was successfull completed, any other error code on failure. + * @return FSE_OK if the migration was successfully completed, any other error code on failure. */ FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest); @@ -425,7 +425,7 @@ bool storage_common_is_subdir(Storage* storage, const char* parent, const char* /******************* Error Functions *******************/ /** - * @brief Get the textual description of a numeric error identifer. + * @brief Get the textual description of a numeric error identifier. * * @param error_id numeric identifier of the error in question. * @return pointer to a statically allocated zero-terminated string containing the respective error text. diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 5031f8d594..e3cc3f6fa2 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -1106,7 +1106,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = $(DOXY_SRC_ROOT)/documentation/images # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program diff --git a/documentation/images/dialog.png b/documentation/images/dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..008ae9ce524fc2e34f234bb64f7bc4fee216a1eb GIT binary patch literal 1377 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 z3VXUZhE&XXd)LwLwu3~&MYckjbH^1e3urW;@tOptGJV_eUB{v*>16Y zegF1&hA(^O)G{*6ab{px#KNH9HA>Mf2wr`C!_DxlqVoTr-|vA@A-uk2^#&$}4k3mH z4Mv8JT{A^LUA|}e>DqbuneP8<-b_ocy1#D5-P`;LEust#Qy3UrIDyo8#tla%?)*P- zFZ;h4TjdWIZ$=ofiT!|3`q{btHEjvkp7V#*_Z{?^AGrn*zi${fWTs5}d*Zq5%&+-> z&)hrSCsX|ObJJclCD-oniGR8$AeogxK$U?ZkcmMcm7l?H+ppci3EfBzv1OQ%@Oqzv z^0#weYSWE2M=(O%2XyA$Gwcs7XZ+qg9YuA6hBm~XQ11>eYQ^=<`VIAQ_w3K_V~Ke8 z`tjR0-VbIY{D0-F&AXUq|6(qlzc1~6?{MA!Ij6bcdGD0m0nW6uf8FDDM}EFJ@o~=I zKNBCnF)g|m`T4bW)Wl=e! z!jtzmx7!@5`F8Qyq0Rs7xA*P*zESyjWbM6)uPa-w_w7W9aDyjnes5VbukgL?vlBiw z?~~p8Dxd6epYhw?94Wr48F(UV<^L-`uT^{3dEPb>5!pAHcQi!aUmrbtdHls!*E8OK z+*>~n$>URO8@gwf{;&F%|GxHTzfIW_)45-*-pze^JH09o$esDBZ3c@>KS&6ezW94aruGm%Eh_Hh3ifrg7TGlixSt;(kEIWz%rA;)78&q Iol`;+0D;-4I{*Lx literal 0 HcmV?d00001 diff --git a/documentation/images/empty.png b/documentation/images/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..844f45093f96cdc2120fcf9d040934888bfa89ac GIT binary patch literal 1005 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#4o z&-Zk345^s&_NpN-g95_=0|$ODLEi}nH-;VDceeT)!-Dj`d<+lxnHf5a7#cK2snH-9 gO$Bt!2rJl^Su$_wn)*l!n12~OUHx3vIVCg!0K_`c6#xJL literal 0 HcmV?d00001 diff --git a/documentation/images/loading.png b/documentation/images/loading.png new file mode 100644 index 0000000000000000000000000000000000000000..f35966f66a809f796d17a1ec1c9a097d2fe767f0 GIT binary patch literal 1173 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 zxO=)dhE&XXd)G0G$x*=JqK4e@&rb#B=|7*SbB818M8mDifA2@L8+Et%m%jY+u*$NY z@j|i8Uj~LAMFxgICI$i3Q3`7iluG?&_z-{m{rrpl8*Y9rlKac?VG1XMf)@kBA{GXP zQ+y4YZ-3_09*br5Rbyz-U}WeJVrW=#c;5M-hcj*GZ~i?$Nem&bVa>3td_Vt>C-*Ul*j^dJ{JM0YRNForY!+dc01$%@4jbAtG?{h9VzyePmPh%Z)PR~8? z!CaWZVG09-3n!40W$>>2es}*j#x+L}3RBq+d~ut5`Zl|PHImZb3?~Z9_>pb3W)Ql= z$2eT9G{Vongi?o%zS-KearFPzIW6$*a*Su*vtn$xBA6U*Xc)I$ztaD0e0sz9R@tyzx literal 0 HcmV?d00001 diff --git a/documentation/images/submenu.png b/documentation/images/submenu.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb64e9748fd70ceeec3b5f62d0ff0af427b499a GIT binary patch literal 1774 zcmb_dYfw{16h4=T5DP(Su@DG_VnqkS2o7&hLY9}tlVgw$!l5fIuYMuJJOqh?Hv;bDXlz+R@QES-+O`s2>t-95W!&-d6Z zjtC1jT|-_20H(B%U6B9=_%8;=OHJq$eGA?YGeYQ@0HmLmKJ0rZk~P3;E^XJ2k8*3( zvc2Cu-e_q!(%pQzQk!5-KX`AQmg^W~E{@=obtwlKQ)VwSB^Kjmg^N1nmF?g)aIWx` zPRC43HUjIn0nV?2fadokg&n0<*hDKmvy=Vz*d`3ts-}+;U{)Z_=lB3z#silILjg|c z*ZoIV)vxh9lo?xIpI}-dSBIa>R|~hj00<-kC&B>iP5~Hyu-p}O_;4)Py;-AE1;>~* z(k}M!7(ace#8x*Z*KZMnAbhW^W|d7qKE2__v0ii&HC=I!^~R6S-6{rM@> zCJQBVG*R0X-T50k;#q*%axUr_f+ef7TJx0B`&?FVkCj*MJ{V>LT#%GawG$}S z)0mUILct6($mSX?S9+s{kW6pr&2i5A%0fx@ezISHZJE-&WGpzJu^v|=)rpi(SJw_! zYlJC13a-nd3-ypWjIGn-nF;H1!)?5Vs>Nwsqi4bW2hzAtpXiS= zo-xnWozppj@AnTgdsI+bOn9~v*-n~9erA@ci|&_@Qw)Pt#Kc(L z*_q3a$(qBC8=4mtU0oD79H>`G)gU%VuB2FbrjIt|DCCn17VKgET-=b4B&v7wh6p1F z*rAVXHGJXb^u#`TKF&c=WI6io?Di~xcl4r5;qy4jo~hg7fA*c?577eLkU7;g9;dC2 zJWZju3w96Wyv5euU_BZm_5|I{O7gWYLu%I2tYc)%*jbsBP#cUCcYXcH-tIAy-)>~A zq=f(>4G7?8{snle;i?QTL%RCHwNi>e!@IW$dJH-Q3}cIAnPWgCacp=_TJiH~ZqK!@?3}<6|%0 z^c=;z7%7;iWd!tSO3O>#C3nTX9nL&*WIqFMhkVnE+fny_3g=umfeS zY5`lulKQ(}ANjh@k4}i+*lJXuOw^@P2)+7w*1rO*6n;@ ziFPvP!Ei-VL6PG00X9OZqm^$DOBBL zP4Nf^qfn6R2p>=S%cB$x;ezb(R78=Y>`ZX%8J3SNR<=>@7a_XQx;B|)cpy3M*sK&c gPsRv%^LQd=Y35d0T5&{E4z$Czwrv?I!B!Q#^Ent8U0)!-xkYp}nX3cf_V}9Iq?_KBabN2qe zZ-4vVI2s#iW9?`S0I-RUiueM64bUS3SgqfVEFrz1+s2DgNE`sRUF#R(u7|A?09LP} zBR);YEtQX@7D_&HcyH+HvF~51ayF-?3*%KEK0dwG`x@f>0**HpVfe?$?MO57qIA67 zxVG3{o~KuuQE(|nyLR-daZNhc11`RY0Go~g@U=E2uexA2qP|LYJWo-|Xb0fXMnE~b z0qh)hjq?YTRkRY(-99e*5H#;Ht-S#tjqx;XXh055d;ooT2TG0qiKW*PjZ%z1Miked z9C*O*&HPv+!+2<=U7^@>N?W07*oZZ309LzAo5bKi5S2zFUk#v*-@-jI1NV2DR*GsZ z5^6jhO0d%*Z)m;Pz*vQMCTAk}(tb2S7M9q~Ov5B|)t)*=KrGigxeXDtG%!->H{~y5 zO2}tN@rQMhqS4P?0I)C>NH}kMLU4S+9b;ZV4P$s=QpikvXHt3+H(-UX6YJ&*=qx{3 z==>1YL;KZXU1sfV!68`mLBqX{n%)=l&Q-+cG@RX&&vVr+`3C{8Hr1W8=G<|2m7)mb z=r>BVbJ=z|0Bkh};d`OeXIBC#NX&9wC|RMMX)GLV5Dd(Qrew?Aq$rNyIUU1p)W@|d zM~}b0qppF~UoNx5Iwnu<+o)*XlQLngm1ErW6Z_wCvJQ35RTq#Moy!b)?U*!#l~uz+ zXCbvz+5*Z2Q+7Yuf>%?+2yk>002NSH)#q?PFxS@0)3b46G)lx4=M-EeUd~sitJHZn z#~9g`{5DRj3pjrWQpOf2W*E`&RcuMk@rtRZTeKD5!wTnzK!n>~Quv1@^S-2J+FXtQ zbB_2=ykv3ZL5BZOGm;d#MAm$(<2NXHS!r35tBSFVhsMlSB_UZF+lIO}r%t|I`F%KLW5zVKAQgUxq3JDaESFI3cm1YWZ0o>UG@q z)%j&>R#R2RWazy5{ZfWjqW?@!he*C=I>c`P;qbwvJ8PhKPO#%_1#Bgz#;ls-5wbMD zdP7m4Ua%a^Rdb}N*{+%LdzC*m-`DIIKzGhcA2LHc41=`2qgW9eTT+1ZnGCxe{t2=n*yO|6ihI{7FV(J|0 zSE3n5IvaxJs@;Swf914LTl zM(;2p-15{=X%tLMvt?#2g}YOmsQChQRv@qMO0p=Pdee`pWQ}+ZOce$W%_0tP6!A{3Ydc7N?!h(faG4h8+1cw3j@i=5-ePTC71BhIb5XiEq_ohM805RpQgKEo-4UL{g1HI z9+{2wU6{B&xiH7M2di2&;@qL)4A&InzrzqTv>b+HLYOelcWF_J7BRX)ww)oNoi93o z8Ml`s7fXwtRA|Zy*~(B^;LI3VporqCS$uQ2r!skneiZg0ejm2GwG0(>=5&gI_xHXa zU9IMCtftJ#K-Y7h-9!%lvoGW^^tRIYhI4r(6$9_W!*q*JmM*N!#BTrb^v4=KQKHTJ z29~eAx8;dnl-nkLF8M`s8Lxgu)4=Bsq@HO~T=SVRsz~iNA1tJ0)Fw$94E^qkh@vM~ zx^WGijZCp`yAs#bsYk`Y5QRJ`fK)20WwUG)^}ph;Ob6M-qd-TtX*T`wq~}hGOR!%) zoWByY01Dptm(6W3MgKp;`gh;Fy9vk*gh}@|eN=ujc-kk{yu*N2tS}e0y`7au)_+Cp ZMk}^Nr5&Jr2mJtn=)=O(d;sRX_+aAtJ?gO0Xj! z7%;&>7FmLdfDj-|wV_rCC|Otx0V4uQAPPuWOlTNj`tV2p_Q#uh-@WhNbI!M%_p7J7 zi~h!)8vy|NCr>zi55Q-dDGcZ=4X%@c2Q-83PbUK70oeRx>4D{1ZZ-yB1M{TQF`wkZ zagkeAzs>gbT^DcP$o_D4YhXyV#pp2)lSBEx4}~ZT!@}KiswUTqYs-b5W@0tWyt91* z2JT%2Kxa1q$4NKT63K!vBrd@@WQb%x4+pB}nq^%;@%#*!``q8~&b`BQkN{otH7IiF zP^~LLIF7ZTlvJrC6eTU-H1-v+>K1PkujIi*+hB=OJeeAx2ZI^a+~ z76lu>a7LUq)yPcVhFnfGM^g?edS6izOM0hRaEXD=peqGcAqkQ=z6WAaSNQpwn}g5g z$pQ}}Y%UuChdr?6h$(W6gT=do^MetQn%J5+OB;WaG;>H1OJSB!TZQNRzM^jWvyE-y zy&}a~Nb2MjRacbC`iaWA_AVs1cR@OqC%0qfGKASoszAkTws!t79%<&^LB=c1J#0916%8VY zaD%}XG7l5iG)(xzQsI@4V>1c$wZwzuxMAC%-u~n=e0Q7>|LqwzgxLz|5kT>B=1)YW zSR>`qlGBZh&{;U_QQ8W_rXsC;mGMljELo00Mr6E1o1qrD!4_4xcnR9E+CAXfUl<3l zeMJYn7idZ^{UN+-zf_$RIM;XwXTZv8!0TH=)X9Ifbm725@utV#cNZ9JE6AR&9LD1n zMDj-cCn^&U>!Lckr|k=xd^=`{0^Ht~BVo77X~G7~9DyGlKl7ez(*UVMimIrOJ^clR z1$9VRr`A0`gEkyQxx8nJY1qM=|Dv|4`z5C>xhgCQ1{-0W?bxrhs1AcDfJOwh3?NkDzG9vgYwE~H*@qthuUuA4T#*7w z?E08?qH-0zz6N0-9A@kqY8H_*Eft9Y1aj##G9!9DMW(ZCV_t3)Ocao3PmK>y%+Qjl zSb@%0cgMWB194BED%~L2Yt&X$I$f0Af8y^WR)Vj*96d(8XH)NxBi5jX%JfL)mcH>HWHx^ zDO97U>5quUx=0~LVeD&GWy|sEXOM!b8A57%-DKb3B};a33g>{t&ZI76?Xl;~V~XpZ z-UED>n^D{aCHV?xTTOeKnAbrI<}W_{3C}svlLt zQ{6og_Zz(xJ*KIia88vl>MUWPf>nlqn zPvajF0g!*W(l~w5lx_jhwz2eLnxVjkCNenDsvSGW>Os+aMgyvNC4%|e2@Oo^xE|@g#QCC zRT*}G_`tB$(Ro~`r=ew~AGFXhWmm9|tllgZW}_hSauC4eP5Q_vy-oRY=YLQ-zhmfH Wr2rOx&q?z;2PeOCcVZq7P5TEYI33~u literal 0 HcmV?d00001 diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md index 3d113807b0..9c59b98224 100644 --- a/documentation/js/js_builtin.md +++ b/documentation/js/js_builtin.md @@ -41,16 +41,10 @@ print("string1", "string2", 123); Same as `print`, but output to serial console only, with corresponding log level. ## to_string -Convert a number to string. +Convert a number to string with an optional base. ### Examples: ```js -to_string(123) -``` -## to_hex_string -Convert a number to string(hex format). - -### Examples: -```js -to_hex_string(0xFF) +to_string(123) // "123" +to_string(123, 16) // "0x7b" ``` diff --git a/documentation/js/js_dialog.md b/documentation/js/js_dialog.md deleted file mode 100644 index eb027e6a73..0000000000 --- a/documentation/js/js_dialog.md +++ /dev/null @@ -1,49 +0,0 @@ -# js_dialog {#js_dialog} - -# Dialog module -```js -let dialog = require("dialog"); -``` -# Methods - -## message -Show a simple message dialog with header, text and "OK" button. - -### Parameters -- Dialog header text -- Dialog text - -### Returns -true if central button was pressed, false if the dialog was closed by back key press - -### Examples: -```js -dialog.message("Dialog demo", "Press OK to start"); -``` - -## custom -More complex dialog with configurable buttons - -### Parameters -Configuration object with the following fields: -- header: Dialog header text -- text: Dialog text -- button_left: (optional) left button name -- button_right: (optional) right button name -- button_center: (optional) central button name - -### Returns -Name of pressed button or empty string if the dialog was closed by back key press - -### Examples: -```js -let dialog_params = ({ - header: "Dialog header", - text: "Dialog text", - button_left: "Left", - button_right: "Right", - button_center: "OK" -}); - -dialog.custom(dialog_params); -``` diff --git a/documentation/js/js_event_loop.md b/documentation/js/js_event_loop.md new file mode 100644 index 0000000000..9519478c0e --- /dev/null +++ b/documentation/js/js_event_loop.md @@ -0,0 +1,144 @@ +# js_event_loop {#js_event_loop} + +# Event Loop module +```js +let eventLoop = require("event_loop"); +``` + +The event loop is central to event-based programming in many frameworks, and our +JS subsystem is no exception. It is a good idea to familiarize yourself with the +event loop first before using any of the advanced modules (e.g. GPIO and GUI). + +## Conceptualizing the event loop +If you ever wrote JavaScript before, you have definitely seen callbacks. It's +when a function accepts another function (usually an anonymous one) as one of +the arguments, which it will call later on, e.g. when an event happens or when +data becomes ready: +```js +setTimeout(function() { console.log("Hello, World!") }, 1000); +``` + +Many JavaScript engines employ a queue that the runtime fetches events from as +they occur, subsequently calling the corresponding callbacks. This is done in a +long-running loop, hence the name "event loop". Here's the pseudocode for a +typical event loop: +```js +while(loop_is_running()) { + if(event_available_in_queue()) { + let event = fetch_event_from_queue(); + let callback = get_callback_associated_with(event); + if(callback) + callback(get_extra_data_for(event)); + } else { + // avoid wasting CPU time + sleep_until_any_event_becomes_available(); + } +} +``` + +Most JS runtimes enclose the event loop within themselves, so that most JS +programmers does not even need to be aware of its existence. This is not the +case with our JS subsystem. + +# Example +This is how one would write something similar to the `setTimeout` example above: +```js +// import module +let eventLoop = require("event_loop"); + +// create an event source that will fire once 1 second after it has been created +let timer = eventLoop.timer("oneshot", 1000); + +// subscribe a callback to the event source +eventLoop.subscribe(timer, function(_subscription, _item, eventLoop) { + print("Hello, World!"); + eventLoop.stop(); +}, eventLoop); // notice this extra argument. we'll come back to this later + +// run the loop until it is stopped +eventLoop.run(); + +// the previous line will only finish executing once `.stop()` is called, hence +// the following line will execute only after "Hello, World!" is printed +print("Stopped"); +``` + +I promised you that we'll come back to the extra argument after the callback +function. Our JavaScript engine does not support closures (anonymous functions +that access values outside of their arguments), so we ask `subscribe` to pass an +outside value (namely, `eventLoop`) as an argument to the callback so that we +can access it. We can modify this extra state: +```js +// this timer will fire every second +let timer = eventLoop.timer("periodic", 1000); +eventLoop.subscribe(timer, function(_subscription, _item, counter, eventLoop) { + print("Counter is at:", counter); + if(counter === 10) + eventLoop.stop(); + // modify the extra arguments that will be passed to us the next time + return [counter + 1, eventLoop]; +}, 0, eventLoop); +``` + +Because we have two extra arguments, if we return anything other than an array +of length 2, the arguments will be kept as-is for the next call. + +The first two arguments that get passed to our callback are: + - The subscription manager that lets us `.cancel()` our subscription + - The event item, used for events that have extra data. Timer events do not, + they just produce `undefined`. + +# API reference +## `run` +Runs the event loop until it is stopped with `stop`. + +## `subscribe` +Subscribes a function to an event. + +### Parameters + - `contract`: an event source identifier + - `callback`: the function to call when the event happens + - extra arguments: will be passed as extra arguments to the callback + +The callback will be called with at least two arguments, plus however many were +passed as extra arguments to `subscribe`. The first argument is the subscription +manager (the same one that `subscribe` itself returns). The second argument is +the event item for events that produce extra data; the ones that don't set this +to `undefined`. The callback may return an array of the same length as the count +of the extra arguments to modify them for the next time that the event handler +is called. Any other returns values are discarded. + +### Returns +A `SubscriptionManager` object: + - `SubscriptionManager.cancel()`: unsubscribes the callback from the event + +### Warning +Each event source may only have one callback associated with it. + +## `stop` +Stops the event loop. + +## `timer` +Produces an event source that fires with a constant interval either once or +indefinitely. + +### Parameters + - `mode`: either `"oneshot"` or `"periodic"` + - `interval`: the timeout (for `"oneshot"`) timers or the period (for + `"periodic"` timers) + +### Returns +A `Contract` object, as expected by `subscribe`'s first parameter. + +## `queue` +Produces a queue that can be used to exchange messages. + +### Parameters + - `length`: the maximum number of items that the queue may contain + +### Returns +A `Queue` object: + - `Queue.send(message)`: + - `message`: a value of any type that will be placed at the end of the queue + - `input`: a `Contract` (event source) that pops items from the front of the + queue diff --git a/documentation/js/js_gpio.md b/documentation/js/js_gpio.md new file mode 100644 index 0000000000..9791fb4ebc --- /dev/null +++ b/documentation/js/js_gpio.md @@ -0,0 +1,77 @@ +# js_gpio {#js_gpio} + +# GPIO module +```js +let eventLoop = require("event_loop"); +let gpio = require("gpio"); +``` + +This module depends on the `event_loop` module, so it _must_ only be imported +after `event_loop` is imported. + +# Example +```js +let eventLoop = require("event_loop"); +let gpio = require("gpio"); + +let led = gpio.get("pc3"); +led.init({ direction: "out", outMode: "push_pull" }); + +led.write(true); +delay(1000); +led.write(false); +delay(1000); +``` + +# API reference +## `get` +Gets a `Pin` object that can be used to manage a pin. + +### Parameters + - `pin`: pin identifier (examples: `"pc3"`, `7`, `"pa6"`, `3`) + +### Returns +A `Pin` object + +## `Pin` object +### `Pin.init()` +Configures a pin + +#### Parameters + - `mode`: `Mode` object: + - `direction` (required): either `"in"` or `"out"` + - `outMode` (required for `direction: "out"`): either `"open_drain"` or + `"push_pull"` + - `inMode` (required for `direction: "in"`): either `"analog"`, + `"plain_digital"`, `"interrupt"` or `"event"` + - `edge` (required for `inMode: "interrupt"` or `"event"`): either + `"rising"`, `"falling"` or `"both"` + - `pull` (optional): either `"up"`, `"down"` or unset + +### `Pin.write()` +Writes a digital value to a pin configured with `direction: "out"` + +#### Parameters + - `value`: boolean logic level to write + +### `Pin.read()` +Reads a digital value from a pin configured with `direction: "in"` and any +`inMode` except `"analog"` + +#### Returns +Boolean logic level + +### `Pin.read_analog()` +Reads an analog voltage level in millivolts from a pin configured with +`direction: "in"` and `inMode: "analog"` + +#### Returns +Voltage on pin in millivolts + +### `Pin.interrupt()` +Attaches an interrupt to a pin configured with `direction: "in"` and +`inMode: "interrupt"` or `"event"` + +#### Returns +An event loop `Contract` object that identifies the interrupt event source. The +event does not produce any extra data. diff --git a/documentation/js/js_gui.md b/documentation/js/js_gui.md new file mode 100644 index 0000000000..4d2d2497a2 --- /dev/null +++ b/documentation/js/js_gui.md @@ -0,0 +1,161 @@ +# js_gui {#js_gui} + +# GUI module +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +``` + +This module depends on the `event_loop` module, so it _must_ only be imported +after `event_loop` is imported. + +## Conceptualizing GUI +### Event loop +It is highly recommended to familiarize yourself with the event loop first +before doing GUI-related things. + +### Canvas +The canvas is just a drawing area with no abstractions over it. Drawing on the +canvas directly (i.e. not through a viewport) is useful in case you want to +implement a custom design element, but this is rather uncommon. + +### Viewport +A viewport is a window into a rectangular portion of the canvas. Applications +always access the canvas through a viewport. + +### View +In Flipper's terminology, a "View" is a fullscreen design element that assumes +control over the entire viewport and all input events. Different types of views +are available (not all of which are unfortunately currently implemented in JS): +| View | Has JS adapter? | +|----------------------|------------------| +| `button_menu` | ❌ | +| `button_panel` | ❌ | +| `byte_input` | ❌ | +| `dialog_ex` | ✅ (as `dialog`) | +| `empty_screen` | ✅ | +| `file_browser` | ❌ | +| `loading` | ✅ | +| `menu` | ❌ | +| `number_input` | ❌ | +| `popup` | ❌ | +| `submenu` | ✅ | +| `text_box` | ✅ | +| `text_input` | ✅ | +| `variable_item_list` | ❌ | +| `widget` | ❌ | + +In JS, each view has its own set of properties (or just "props"). The programmer +can manipulate these properties in two ways: + - Instantiate a `View` using the `makeWith(props)` method, passing an object + with the initial properties + - Call `set(name, value)` to modify a property of an existing `View` + +### View Dispatcher +The view dispatcher holds references to all the views that an application needs +and switches between them as the application makes requests to do so. + +### Scene Manager +The scene manager is an optional add-on to the view dispatcher that makes +managing applications with complex navigation flows easier. It is currently +inaccessible from JS. + +### Approaches +In total, there are three different approaches that you may take when writing +a GUI application: +| Approach | Use cases | Available from JS | +|----------------|------------------------------------------------------------------------------|-------------------| +| ViewPort only | Accessing the graphics API directly, without any of the nice UI abstractions | ❌ | +| ViewDispatcher | Common UI elements that fit with the overall look of the system | ✅ | +| SceneManager | Additional navigation flow management for complex applications | ❌ | + +# Example +An example with three different views using the ViewDispatcher approach: +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let loadingView = require("gui/loading"); +let submenuView = require("gui/submenu"); +let emptyView = require("gui/empty_screen"); + +// Common pattern: declare all the views in an object. This is absolutely not +// required, but adds clarity to the script. +let views = { + // the view dispatcher auto-✨magically✨ remembers views as they are created + loading: loadingView.make(), + empty: emptyView.make(), + demos: submenuView.makeWith({ + items: [ + "Hourglass screen", + "Empty screen", + "Exit app", + ], + }), +}; + +// go to different screens depending on what was selected +eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) { + if (index === 0) { + gui.viewDispatcher.switchTo(views.loading); + } else if (index === 1) { + gui.viewDispatcher.switchTo(views.empty); + } else if (index === 2) { + eventLoop.stop(); + } +}, gui, eventLoop, views); + +// go to the demo chooser screen when the back key is pressed +eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) { + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// run UI +gui.viewDispatcher.switchTo(views.demos); +eventLoop.run(); +``` + +# API reference +## `viewDispatcher` +The `viewDispatcher` constant holds the `ViewDispatcher` singleton. + +### `viewDispatcher.switchTo(view)` +Switches to a view, giving it control over the display and input + +#### Parameters + - `view`: the `View` to switch to + +### `viewDispatcher.sendTo(direction)` +Sends the viewport that the dispatcher manages to the front of the stackup +(effectively making it visible), or to the back (effectively making it +invisible) + +#### Parameters + - `direction`: either `"front"` or `"back"` + +### `viewDispatcher.sendCustom(event)` +Sends a custom number to the `custom` event handler + +#### Parameters + - `event`: number to send + +### `viewDispatcher.custom` +An event loop `Contract` object that identifies the custom event source, +triggered by `ViewDispatcher.sendCustom(event)` + +### `viewDispatcher.navigation` +An event loop `Contract` object that identifies the navigation event source, +triggered when the back key is pressed + +## `ViewFactory` +When you import a module implementing a view, a `ViewFactory` is instantiated. +For example, in the example above, `loadingView`, `submenuView` and `emptyView` +are view factories. + +### `ViewFactory.make()` +Creates an instance of a `View` + +### `ViewFactory.make(props)` +Creates an instance of a `View` and assigns initial properties from `props` + +#### Parameters + - `props`: simple key-value object, e.g. `{ header: "Header" }` diff --git a/documentation/js/js_gui__dialog.md b/documentation/js/js_gui__dialog.md new file mode 100644 index 0000000000..445e711282 --- /dev/null +++ b/documentation/js/js_gui__dialog.md @@ -0,0 +1,53 @@ +# js_gui__dialog {#js_gui__dialog} + +# Dialog GUI view +Displays a dialog with up to three options. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let dialogView = require("gui/dialog"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `header` +Text that appears in bold at the top of the screen + +Type: `string` + +## `text` +Text that appears in the middle of the screen + +Type: `string` + +## `left` +Text for the left button. If unset, the left button does not show up. + +Type: `string` + +## `center` +Text for the center button. If unset, the center button does not show up. + +Type: `string` + +## `right` +Text for the right button. If unset, the right button does not show up. + +Type: `string` + +# View events +## `input` +Fires when the user presses on either of the three possible buttons. The item +contains one of the strings `"left"`, `"center"` or `"right"` depending on the +button. + +Item type: `string` diff --git a/documentation/js/js_gui__empty_screen.md b/documentation/js/js_gui__empty_screen.md new file mode 100644 index 0000000000..f9fd12553a --- /dev/null +++ b/documentation/js/js_gui__empty_screen.md @@ -0,0 +1,22 @@ +# js_gui__empty_screen {#js_gui__empty_screen} + +# Empty Screen GUI View +Displays nothing. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let emptyView = require("gui/empty_screen"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +This view does not have any props. diff --git a/documentation/js/js_gui__loading.md b/documentation/js/js_gui__loading.md new file mode 100644 index 0000000000..52f1cea49e --- /dev/null +++ b/documentation/js/js_gui__loading.md @@ -0,0 +1,23 @@ +# js_gui__loading {#js_gui__loading} + +# Loading GUI View +Displays an animated hourglass icon. Suppresses all `navigation` events, making +it impossible for the user to exit the view by pressing the back key. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let loadingView = require("gui/loading"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +This view does not have any props. diff --git a/documentation/js/js_gui__submenu.md b/documentation/js/js_gui__submenu.md new file mode 100644 index 0000000000..28c1e65af2 --- /dev/null +++ b/documentation/js/js_gui__submenu.md @@ -0,0 +1,37 @@ +# js_gui__submenu {#js_gui__submenu} + +# Submenu GUI view +Displays a scrollable list of clickable textual entries. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let submenuView = require("gui/submenu"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +## `header` +Single line of text that appears above the list + +Type: `string` + +## `items` +The list of options + +Type: `string[]` + +# View events +## `chosen` +Fires when an entry has been chosen by the user. The item contains the index of +the entry. + +Item type: `number` diff --git a/documentation/js/js_gui__text_box.md b/documentation/js/js_gui__text_box.md new file mode 100644 index 0000000000..bdad8d8b36 --- /dev/null +++ b/documentation/js/js_gui__text_box.md @@ -0,0 +1,25 @@ +# js_gui__text_box {#js_gui__text_box} + +# Text box GUI view +Displays a scrollable read-only text field. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let textBoxView = require("gui/text_box"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `text` +Text to show in the text box. + +Type: `string` diff --git a/documentation/js/js_gui__text_input.md b/documentation/js/js_gui__text_input.md new file mode 100644 index 0000000000..030579e2e2 --- /dev/null +++ b/documentation/js/js_gui__text_input.md @@ -0,0 +1,44 @@ +# js_gui__text_input {#js_gui__text_input} + +# Text input GUI view +Displays a keyboard. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let textInputView = require("gui/text_input"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `minLength` +Smallest allowed text length + +Type: `number` + +## `maxLength` +Biggest allowed text length + +Type: `number` + +Default: `32` + +## `header` +Single line of text that appears above the keyboard + +Type: `string` + +# View events +## `input` +Fires when the user selects the "save" button and the text matches the length +constrained by `minLength` and `maxLength`. + +Item type: `string` diff --git a/documentation/js/js_submenu.md b/documentation/js/js_submenu.md deleted file mode 100644 index 580a43bd5c..0000000000 --- a/documentation/js/js_submenu.md +++ /dev/null @@ -1,48 +0,0 @@ -# js_submenu {#js_submenu} - -# Submenu module -```js -let submenu = require("submenu"); -``` -# Methods - -## setHeader -Set the submenu header text. - -### Parameters -- header (string): The submenu header text - -### Example -```js -submenu.setHeader("Select an option:"); -``` - -## addItem -Add a new submenu item. - -### Parameters -- label (string): The submenu item label text -- id (number): The submenu item ID, must be a Uint32 number - -### Example -```js -submenu.addItem("Option 1", 1); -submenu.addItem("Option 2", 2); -submenu.addItem("Option 3", 3); -``` - -## show -Show a submenu that was previously configured using `setHeader()` and `addItem()` methods. - -### Returns -The ID of the submenu item that was selected, or `undefined` if the BACK button was pressed. - -### Example -```js -let selected = submenu.show(); -if (selected === undefined) { - // if BACK button was pressed -} else if (selected === 1) { - // if item with ID 1 was selected -} -``` diff --git a/documentation/js/js_textbox.md b/documentation/js/js_textbox.md deleted file mode 100644 index 61652df1a6..0000000000 --- a/documentation/js/js_textbox.md +++ /dev/null @@ -1,69 +0,0 @@ -# js_textbox {#js_textbox} - -# Textbox module -```js -let textbox = require("textbox"); -``` -# Methods - -## setConfig -Set focus and font for the textbox. - -### Parameters -- focus: "start" to focus on the beginning of the text, or "end" to focus on the end of the text -- font: "text" to use the default proportional font, or "hex" to use a monospaced font, which is convenient for aligned array output in HEX - -### Example -```js -textbox.setConfig("start", "text"); -textbox.addText("Hello world"); -textbox.show(); -``` - -## addText -Add text to the end of the textbox. - -### Parameters -- text (string): The text to add to the end of the textbox - -### Example -```js -textbox.addText("New text 1\nNew text 2"); -``` - -## clearText -Clear the textbox. - -### Example -```js -textbox.clearText(); -``` - -## isOpen -Return true if the textbox is open. - -### Returns -True if the textbox is open, false otherwise. - -### Example -```js -let isOpen = textbox.isOpen(); -``` - -## show -Show the textbox. You can add text to it using the `addText()` method before or after calling the `show()` method. - -### Example -```js -textbox.show(); -``` - -## close -Close the textbox. - -### Example -```js -if (textbox.isOpen()) { - textbox.close(); -} -``` diff --git a/fbt_options.py b/fbt_options.py index 8fbd78faa8..f3396f98cd 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -75,6 +75,7 @@ "updater_app", "radio_device_cc1101_ext", "unit_tests", + "js_app", ], } diff --git a/firmware.scons b/firmware.scons index 62b1184ebd..4c5e058739 100644 --- a/firmware.scons +++ b/firmware.scons @@ -216,6 +216,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( sources, LIBS=fwenv["TARGET_CFG"].linker_dependencies, ) +Depends(fwelf, fwenv["LINKER_SCRIPT_PATH"]) # Firmware depends on everything child builders returned # Depends(fwelf, lib_targets) diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index f4f008a71b..b622aa7a1c 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -418,6 +418,18 @@ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* o FURI_CRITICAL_EXIT(); } +bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object) { + furi_check(instance); + furi_check(instance->thread_id == furi_thread_get_current_id()); + FURI_CRITICAL_ENTER(); + + FuriEventLoopItem* const* item = FuriEventLoopTree_cget(instance->tree, object); + bool result = !!item; + + FURI_CRITICAL_EXIT(); + return result; +} + /* * Private Event Loop Item functions */ diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index af5987101d..6c5ba432c7 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -289,6 +289,23 @@ void furi_event_loop_subscribe_mutex( */ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object); +/** + * @brief Checks if the loop is subscribed to an object of any kind + * + * @param instance Event Loop instance + * @param object Object to check + */ +bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object); + +/** + * @brief Convenience function for `if(is_subscribed()) unsubscribe()` + */ +static inline void + furi_event_loop_maybe_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) { + if(furi_event_loop_is_subscribed(instance, object)) + furi_event_loop_unsubscribe(instance, object); +} + #ifdef __cplusplus } #endif diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index bcdcb364ab..f3e28a5bae 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -103,6 +103,7 @@ struct mjs* mjs_create(void* context) { sizeof(struct mjs_object), MJS_OBJECT_ARENA_SIZE, MJS_OBJECT_ARENA_INC_SIZE); + mjs->object_arena.destructor = mjs_obj_destructor; gc_arena_init( &mjs->property_arena, sizeof(struct mjs_property), diff --git a/lib/mjs/mjs_object.c b/lib/mjs/mjs_object.c index 2aea1bd46a..60bacf5145 100644 --- a/lib/mjs/mjs_object.c +++ b/lib/mjs/mjs_object.c @@ -9,6 +9,7 @@ #include "mjs_primitive.h" #include "mjs_string.h" #include "mjs_util.h" +#include "furi.h" #include "common/mg_str.h" @@ -20,6 +21,19 @@ MJS_PRIVATE mjs_val_t mjs_object_to_value(struct mjs_object* o) { } } +MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell) { + struct mjs_object* obj = cell; + mjs_val_t obj_val = mjs_object_to_value(obj); + + struct mjs_property* destructor = mjs_get_own_property( + mjs, obj_val, MJS_DESTRUCTOR_PROP_NAME, strlen(MJS_DESTRUCTOR_PROP_NAME)); + if(!destructor) return; + if(!mjs_is_foreign(destructor->value)) return; + + mjs_custom_obj_destructor_t destructor_fn = mjs_get_ptr(mjs, destructor->value); + if(destructor_fn) destructor_fn(mjs, obj_val); +} + MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) { struct mjs_object* ret = NULL; if(mjs_is_null(v)) { @@ -293,7 +307,8 @@ mjs_val_t * start from the end so the constructed object more closely resembles * the definition. */ - while(def->name != NULL) def++; + while(def->name != NULL) + def++; for(def--; def >= defs; def--) { mjs_val_t v = MJS_UNDEFINED; const char* ptr = (const char*)base + def->offset; diff --git a/lib/mjs/mjs_object.h b/lib/mjs/mjs_object.h index 1c4810385a..870486d06f 100644 --- a/lib/mjs/mjs_object.h +++ b/lib/mjs/mjs_object.h @@ -50,6 +50,11 @@ MJS_PRIVATE mjs_err_t mjs_set_internal( */ MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs); +/* + * Cell destructor for object arena + */ +MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell); + #define MJS_PROTO_PROP_NAME "__p" /* Make it < 5 chars */ #if defined(__cplusplus) diff --git a/lib/mjs/mjs_object_public.h b/lib/mjs/mjs_object_public.h index f9f06c6164..1a021a9d85 100644 --- a/lib/mjs/mjs_object_public.h +++ b/lib/mjs/mjs_object_public.h @@ -119,6 +119,14 @@ int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len); */ mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator); +typedef void (*mjs_custom_obj_destructor_t)(struct mjs* mjs, mjs_val_t object); + +/* + * Destructor property name. If set, must be a foreign pointer to a function + * that will be called just before the object is freed. + */ +#define MJS_DESTRUCTOR_PROP_NAME "__d" + #if defined(__cplusplus) } #endif /* __cplusplus */ diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 32cc8f1987..2519fb90c2 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -65,10 +65,8 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(instance->state == Iso14443_4aListenerStateIdle) { if(bit_buffer_get_size_bytes(rx_buffer) == 2 && bit_buffer_get_byte(rx_buffer, 0) == ISO14443_4A_CMD_READ_ATS) { - if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) != + if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) == Iso14443_4aErrorNone) { - command = NfcCommandContinue; - } else { instance->state = Iso14443_4aListenerStateActive; } } @@ -93,7 +91,6 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(instance->callback) { command = instance->callback(instance->generic_event, instance->context); } - command = NfcCommandContinue; } return command; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e6b025c8c5..7943c4cfcb 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.0,, +Version,+,77.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1116,6 +1116,7 @@ Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_event_loop_alloc,FuriEventLoop*, Function,+,furi_event_loop_free,void,FuriEventLoop* +Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" Function,+,furi_event_loop_run,void,FuriEventLoop* Function,+,furi_event_loop_stop,void,FuriEventLoop* @@ -1372,6 +1373,8 @@ Function,-,furi_hal_resources_deinit_early,void, Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, +Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char* +Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,DateTime* @@ -2687,6 +2690,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput* Function,+,text_input_get_view,View*,TextInput* Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" Function,-,tgamma,double,double @@ -2761,6 +2765,7 @@ Function,+,view_allocate_model,void,"View*, ViewModelType, size_t" Function,+,view_commit_model,void,"View*, _Bool" Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*" Function,+,view_dispatcher_alloc,ViewDispatcher*, +Function,+,view_dispatcher_alloc_ex,ViewDispatcher*,FuriEventLoop* Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* diff --git a/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c index 45ca3e6c49..2e3654435c 100644 --- a/targets/f18/furi_hal/furi_hal_resources.c +++ b/targets/f18/furi_hal/furi_hal_resources.c @@ -354,3 +354,19 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { } return -1; } + +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(strcasecmp(name, record->name) == 0) return record; + } + return NULL; +} + +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(record->number == number) return record; + } + return NULL; +} diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 8f6173eb9d..9a0d04cb66 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -121,6 +121,26 @@ void furi_hal_resources_init(void); */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); +/** + * @brief Finds a pin by its name + * + * @param name case-insensitive pin name to look for (e.g. `"Pc3"`, `"pA4"`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name); + +/** + * @brief Finds a pin by its number + * + * @param name pin number to look for (e.g. `7`, `4`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number); + #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index b6128aa307..8358958f48 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.0,, +Version,+,77.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1256,6 +1256,7 @@ Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_event_loop_alloc,FuriEventLoop*, Function,+,furi_event_loop_free,void,FuriEventLoop* +Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" Function,+,furi_event_loop_run,void,FuriEventLoop* Function,+,furi_event_loop_stop,void,FuriEventLoop* @@ -1576,6 +1577,8 @@ Function,-,furi_hal_resources_deinit_early,void, Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, +Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char* +Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t Function,+,furi_hal_rfid_comp_set_callback,void,"FuriHalRfidCompCallback, void*" Function,+,furi_hal_rfid_comp_start,void, Function,+,furi_hal_rfid_comp_stop,void, @@ -3691,6 +3694,7 @@ Function,+,view_allocate_model,void,"View*, ViewModelType, size_t" Function,+,view_commit_model,void,"View*, _Bool" Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*" Function,+,view_dispatcher_alloc,ViewDispatcher*, +Function,+,view_dispatcher_alloc_ex,ViewDispatcher*,FuriEventLoop* Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index 486c24230e..123ebc4208 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -288,3 +288,19 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { } return -1; } + +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(strcasecmp(name, record->name) == 0) return record; + } + return NULL; +} + +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(record->number == number) return record; + } + return NULL; +} diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index c01b2207ff..ec8794cc1e 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -227,6 +227,26 @@ void furi_hal_resources_init(void); */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); +/** + * @brief Finds a pin by its name + * + * @param name case-insensitive pin name to look for (e.g. `"Pc3"`, `"pA4"`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name); + +/** + * @brief Finds a pin by its number + * + * @param name pin number to look for (e.g. `7`, `4`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number); + #ifdef __cplusplus } #endif diff --git a/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld index 3fb7896459..524da6fc32 100644 --- a/targets/f7/stm32wb55xx_flash.ld +++ b/targets/f7/stm32wb55xx_flash.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x1000; /* required amount of stack */ +_stack_size = 0x200; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K diff --git a/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld index cae30b6e99..f0e8ad678c 100644 --- a/targets/f7/stm32wb55xx_ram_fw.ld +++ b/targets/f7/stm32wb55xx_ram_fw.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x1000; /* required amount of stack */ +_stack_size = 0x200; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..2655a8b97b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "checkJs": true, + "module": "CommonJS", + "typeRoots": [ + "./applications/system/js_app/types" + ], + "noLib": true, + }, + "include": [ + "./applications/system/js_app/examples/apps/Scripts", + "./applications/debug/unit_tests/resources/unit_tests/js", + "./applications/system/js_app/types/global.d.ts", + ] +} \ No newline at end of file From 45bc0e1ce604cf5d78ce27ab33acce8968c94551 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:43:52 +0300 Subject: [PATCH 147/236] after merge fix p1 --- applications/services/gui/modules/text_input.h | 2 -- lib/nfc/helpers/iso14443_4_layer.c | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index 1c020d1869..b6198f9695 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -77,8 +77,6 @@ void text_input_set_validator( TextInputValidatorCallback callback, void* callback_context); -void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length); - TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input); void* text_input_get_validator_callback_context(TextInput* text_input); diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 9d817e0500..ef9baaabcb 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -33,7 +33,7 @@ #define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) #define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) -#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & mask) == mask) +#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & (mask)) == (mask)) #define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) From edc58c5a0e5c89743caece0ff623cd53f9634bf8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 15 Oct 2024 03:16:21 +0300 Subject: [PATCH 148/236] merge p2 --- applications/system/js_app/application.fam | 102 ++- applications/system/js_app/js_app.c | 2 +- applications/system/js_app/js_modules.c | 114 +++- applications/system/js_app/js_modules.h | 264 +++++++- applications/system/js_app/js_thread.c | 19 +- applications/system/js_app/js_thread.h | 8 + .../system/js_app/modules/js_badusb.c | 8 +- .../system/js_app/modules/js_dialog.c | 218 ------- .../modules/js_event_loop/js_event_loop.c | 451 +++++++++++++ .../modules/js_event_loop/js_event_loop.h | 104 +++ .../js_event_loop/js_event_loop_api_table.cpp | 16 + .../js_event_loop/js_event_loop_api_table_i.h | 4 + .../system/js_app/modules/js_flipper.c | 3 +- .../system/js_app/modules/js_flipper.h | 2 +- applications/system/js_app/modules/js_gpio.c | 614 ++++++++---------- .../system/js_app/modules/js_gui/dialog.c | 129 ++++ .../js_app/modules/js_gui/empty_screen.c | 12 + .../system/js_app/modules/js_gui/js_gui.c | 348 ++++++++++ .../system/js_app/modules/js_gui/js_gui.h | 116 ++++ .../modules/js_gui/js_gui_api_table.cpp | 16 + .../modules/js_gui/js_gui_api_table_i.h | 4 + .../system/js_app/modules/js_gui/loading.c | 12 + .../system/js_app/modules/js_gui/submenu.c | 87 +++ .../system/js_app/modules/js_gui/text_box.c | 78 +++ .../system/js_app/modules/js_gui/text_input.c | 120 ++++ .../system/js_app/modules/js_keyboard.c | 206 ------ applications/system/js_app/modules/js_math.c | 4 +- .../system/js_app/modules/js_notification.c | 4 +- .../system/js_app/modules/js_serial.c | 4 +- .../system/js_app/modules/js_storage.c | 495 ++++++++------ .../system/js_app/modules/js_submenu.c | 147 ----- applications/system/js_app/modules/js_tests.c | 104 +++ applications/system/js_app/modules/js_tests.h | 5 + .../system/js_app/modules/js_textbox.c | 219 ------- .../js_app/plugin_api/app_api_table_i.h | 3 +- .../system/js_app/plugin_api/js_plugin_api.h | 4 + .../system/js_app/types/badusb/index.d.ts | 81 +++ .../system/js_app/types/event_loop/index.d.ts | 70 ++ .../system/js_app/types/flipper/index.d.ts | 14 + applications/system/js_app/types/global.d.ts | 178 +++++ .../system/js_app/types/gpio/index.d.ts | 45 ++ .../system/js_app/types/gui/dialog.d.ts | 16 + .../system/js_app/types/gui/empty_screen.d.ts | 7 + .../system/js_app/types/gui/index.d.ts | 41 ++ .../system/js_app/types/gui/loading.d.ts | 7 + .../system/js_app/types/gui/submenu.d.ts | 13 + .../system/js_app/types/gui/text_box.d.ts | 14 + .../system/js_app/types/gui/text_input.d.ts | 14 + .../system/js_app/types/math/index.d.ts | 24 + .../js_app/types/notification/index.d.ts | 20 + .../system/js_app/types/serial/index.d.ts | 77 +++ .../system/js_app/types/storage/index.d.ts | 237 +++++++ .../system/js_app/types/tests/index.d.ts | 8 + 53 files changed, 3484 insertions(+), 1428 deletions(-) delete mode 100644 applications/system/js_app/modules/js_dialog.c create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop.c create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop.h create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop_api_table.cpp create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h create mode 100644 applications/system/js_app/modules/js_gui/dialog.c create mode 100644 applications/system/js_app/modules/js_gui/empty_screen.c create mode 100644 applications/system/js_app/modules/js_gui/js_gui.c create mode 100644 applications/system/js_app/modules/js_gui/js_gui.h create mode 100644 applications/system/js_app/modules/js_gui/js_gui_api_table.cpp create mode 100644 applications/system/js_app/modules/js_gui/js_gui_api_table_i.h create mode 100644 applications/system/js_app/modules/js_gui/loading.c create mode 100644 applications/system/js_app/modules/js_gui/submenu.c create mode 100644 applications/system/js_app/modules/js_gui/text_box.c create mode 100644 applications/system/js_app/modules/js_gui/text_input.c delete mode 100644 applications/system/js_app/modules/js_keyboard.c delete mode 100644 applications/system/js_app/modules/js_submenu.c create mode 100644 applications/system/js_app/modules/js_tests.c create mode 100644 applications/system/js_app/modules/js_tests.h delete mode 100644 applications/system/js_app/modules/js_textbox.c create mode 100644 applications/system/js_app/types/badusb/index.d.ts create mode 100644 applications/system/js_app/types/event_loop/index.d.ts create mode 100644 applications/system/js_app/types/flipper/index.d.ts create mode 100644 applications/system/js_app/types/global.d.ts create mode 100644 applications/system/js_app/types/gpio/index.d.ts create mode 100644 applications/system/js_app/types/gui/dialog.d.ts create mode 100644 applications/system/js_app/types/gui/empty_screen.d.ts create mode 100644 applications/system/js_app/types/gui/index.d.ts create mode 100644 applications/system/js_app/types/gui/loading.d.ts create mode 100644 applications/system/js_app/types/gui/submenu.d.ts create mode 100644 applications/system/js_app/types/gui/text_box.d.ts create mode 100644 applications/system/js_app/types/gui/text_input.d.ts create mode 100644 applications/system/js_app/types/math/index.d.ts create mode 100644 applications/system/js_app/types/notification/index.d.ts create mode 100644 applications/system/js_app/types/serial/index.d.ts create mode 100644 applications/system/js_app/types/storage/index.d.ts create mode 100644 applications/system/js_app/types/tests/index.d.ts diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 24571aed24..36fd7b16c4 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -16,120 +16,116 @@ App( ) App( - appid="js_dialog", + appid="js_event_loop", apptype=FlipperAppType.PLUGIN, - entry_point="js_dialog_ep", + entry_point="js_event_loop_ep", requires=["js_app"], - sources=["modules/js_dialog.c"], + sources=[ + "modules/js_event_loop/js_event_loop.c", + "modules/js_event_loop/js_event_loop_api_table.cpp", + ], ) App( - appid="js_notification", + appid="js_gui", apptype=FlipperAppType.PLUGIN, - entry_point="js_notification_ep", - requires=["js_app"], - sources=["modules/js_notification.c"], + entry_point="js_gui_ep", + requires=["js_app", "js_event_loop"], + sources=["modules/js_gui/js_gui.c", "modules/js_gui/js_gui_api_table.cpp"], ) App( - appid="js_badusb", + appid="js_gui__loading", apptype=FlipperAppType.PLUGIN, - entry_point="js_badusb_ep", - requires=["js_app"], - sources=["modules/js_badusb.c"], + entry_point="js_view_loading_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/loading.c"], ) App( - appid="js_serial", + appid="js_gui__empty_screen", apptype=FlipperAppType.PLUGIN, - entry_point="js_serial_ep", - requires=["js_app"], - sources=["modules/js_serial.c"], + entry_point="js_view_empty_screen_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/empty_screen.c"], ) App( - appid="js_storage", + appid="js_gui__submenu", apptype=FlipperAppType.PLUGIN, - entry_point="js_storage_ep", - requires=["js_app"], - sources=["modules/js_storage.c"], + entry_point="js_view_submenu_ep", + requires=["js_app", "js_gui"], + sources=["modules/js_gui/submenu.c"], ) App( - appid="js_usbdisk", + appid="js_gui__text_input", apptype=FlipperAppType.PLUGIN, - entry_point="js_usbdisk_ep", - requires=["js_app"], - sources=["modules/js_usbdisk/*.c"], + entry_point="js_view_text_input_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/text_input.c"], ) App( - appid="js_submenu", + appid="js_gui__text_box", apptype=FlipperAppType.PLUGIN, - entry_point="js_submenu_ep", + entry_point="js_view_text_box_ep", requires=["js_app"], - sources=["modules/js_submenu.c"], + sources=["modules/js_gui/text_box.c"], ) App( - appid="js_blebeacon", + appid="js_gui__dialog", apptype=FlipperAppType.PLUGIN, - entry_point="js_blebeacon_ep", + entry_point="js_view_dialog_ep", requires=["js_app"], - sources=["modules/js_blebeacon.c"], + sources=["modules/js_gui/dialog.c"], ) App( - appid="js_math", + appid="js_notification", apptype=FlipperAppType.PLUGIN, - entry_point="js_math_ep", + entry_point="js_notification_ep", requires=["js_app"], - sources=["modules/js_math.c"], + sources=["modules/js_notification.c"], ) App( - appid="js_keyboard", + appid="js_badusb", apptype=FlipperAppType.PLUGIN, - entry_point="js_keyboard_ep", + entry_point="js_badusb_ep", requires=["js_app"], - sources=["modules/js_keyboard.c"], + sources=["modules/js_badusb.c"], ) + App( - appid="js_subghz", + appid="js_serial", apptype=FlipperAppType.PLUGIN, - entry_point="js_subghz_ep", + entry_point="js_serial_ep", requires=["js_app"], - sources=["modules/js_subghz/*.c"], + sources=["modules/js_serial.c"], ) App( appid="js_gpio", apptype=FlipperAppType.PLUGIN, entry_point="js_gpio_ep", - requires=["js_app"], + requires=["js_app", "js_event_loop"], sources=["modules/js_gpio.c"], ) App( - appid="js_textbox", - apptype=FlipperAppType.PLUGIN, - entry_point="js_textbox_ep", - requires=["js_app"], - sources=["modules/js_textbox.c"], -) - -App( - appid="js_widget", + appid="js_math", apptype=FlipperAppType.PLUGIN, - entry_point="js_widget_ep", + entry_point="js_math_ep", requires=["js_app"], - sources=["modules/js_widget.c"], + sources=["modules/js_math.c"], ) App( - appid="js_vgm", + appid="js_storage", apptype=FlipperAppType.PLUGIN, - entry_point="js_vgm_ep", + entry_point="js_storage_ep", requires=["js_app"], - sources=["modules/js_vgm/*.c", "modules/js_vgm/ICM42688P/*.c"], + sources=["modules/js_storage.c"], ) diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index 20c4ce7336..c321150df7 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -114,7 +114,7 @@ int32_t js_app(void* arg) { FuriString* start_text = furi_string_alloc_printf("Running %s", furi_string_get_cstr(name)); console_view_print(app->console_view, furi_string_get_cstr(start_text)); - console_view_print(app->console_view, "------------"); + console_view_print(app->console_view, "-------------"); furi_string_free(name); furi_string_free(start_text); diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 9ab6cb1407..38ff46f752 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -1,7 +1,11 @@ #include #include "js_modules.h" -#include +#include + #include "modules/js_flipper.h" +#ifdef FW_CFG_unit_tests +#include "modules/js_tests.h" +#endif #define TAG "JS modules" @@ -9,54 +13,72 @@ #define MODULES_PATH "/ext/apps_data/js_app/plugins" typedef struct { - JsModeConstructor create; - JsModeDestructor destroy; + FuriString* name; + const JsModuleConstructor create; + const JsModuleDestructor destroy; void* context; } JsModuleData; -DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST); +// not using: +// - a dict because ordering is required +// - a bptree because it forces a sorted ordering +// - an rbtree because i deemed it more tedious to implement, and with the +// amount of modules in use (under 10 in the overwhelming majority of cases) +// i bet it's going to be slower than a plain array +ARRAY_DEF(JsModuleArray, JsModuleData, M_POD_OPLIST); +#define M_OPL_JsModuleArray_t() ARRAY_OPLIST(JsModuleArray) static const JsModuleDescriptor modules_builtin[] = { - {"flipper", js_flipper_create, NULL}, + {"flipper", js_flipper_create, NULL, NULL}, +#ifdef FW_CFG_unit_tests + {"tests", js_tests_create, NULL, NULL}, +#endif }; struct JsModules { struct mjs* mjs; - JsModuleDict_t module_dict; + JsModuleArray_t modules; PluginManager* plugin_manager; + CompositeApiResolver* resolver; }; JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver) { JsModules* modules = malloc(sizeof(JsModules)); modules->mjs = mjs; - JsModuleDict_init(modules->module_dict); + JsModuleArray_init(modules->modules); modules->plugin_manager = plugin_manager_alloc( PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); + modules->resolver = resolver; + return modules; } -void js_modules_destroy(JsModules* modules) { - JsModuleDict_it_t it; - for(JsModuleDict_it(it, modules->module_dict); !JsModuleDict_end_p(it); - JsModuleDict_next(it)) { - const JsModuleDict_itref_t* module_itref = JsModuleDict_cref(it); - if(module_itref->value.destroy) { - module_itref->value.destroy(module_itref->value.context); +void js_modules_destroy(JsModules* instance) { + for + M_EACH(module, instance->modules, JsModuleArray_t) { + FURI_LOG_T(TAG, "Tearing down %s", furi_string_get_cstr(module->name)); + if(module->destroy) module->destroy(module->context); + furi_string_free(module->name); } - } - plugin_manager_free(modules->plugin_manager); - JsModuleDict_clear(modules->module_dict); - free(modules); + plugin_manager_free(instance->plugin_manager); + JsModuleArray_clear(instance->modules); + free(instance); +} + +JsModuleData* js_find_loaded_module(JsModules* instance, const char* name) { + for + M_EACH(module, instance->modules, JsModuleArray_t) { + if(furi_string_cmp_str(module->name, name) == 0) return module; + } + return NULL; } mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) { - FuriString* module_name = furi_string_alloc_set_str(name); // Check if module is already installed - JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name); + JsModuleData* module_inst = js_find_loaded_module(modules, name); if(module_inst) { //-V547 - furi_string_free(module_name); mjs_prepend_errorf( modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name); return MJS_UNDEFINED; @@ -73,8 +95,11 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le if(strncmp(name, modules_builtin[i].name, name_compare_len) == 0) { JsModuleData module = { - .create = modules_builtin[i].create, .destroy = modules_builtin[i].destroy}; - JsModuleDict_set_at(modules->module_dict, module_name, module); + .create = modules_builtin[i].create, + .destroy = modules_builtin[i].destroy, + .name = furi_string_alloc_set_str(name), + }; + JsModuleArray_push_at(modules->modules, 0, module); module_found = true; FURI_LOG_I(TAG, "Using built-in module %s", name); break; @@ -83,39 +108,57 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le // External module load if(!module_found) { + FuriString* deslashed_name = furi_string_alloc_set_str(name); + furi_string_replace_all_str(deslashed_name, "/", "__"); FuriString* module_path = furi_string_alloc(); - furi_string_printf(module_path, "%s/js_%s.fal", MODULES_PATH, name); - FURI_LOG_I(TAG, "Loading external module %s", furi_string_get_cstr(module_path)); + furi_string_printf( + module_path, "%s/js_%s.fal", MODULES_PATH, furi_string_get_cstr(deslashed_name)); + FURI_LOG_I( + TAG, "Loading external module %s from %s", name, furi_string_get_cstr(module_path)); do { uint32_t plugin_cnt_last = plugin_manager_get_count(modules->plugin_manager); PluginManagerError load_error = plugin_manager_load_single( modules->plugin_manager, furi_string_get_cstr(module_path)); if(load_error != PluginManagerErrorNone) { + FURI_LOG_E( + TAG, + "Module %s load error. It may depend on other modules that are not yet loaded.", + name); break; } const JsModuleDescriptor* plugin = plugin_manager_get_ep(modules->plugin_manager, plugin_cnt_last); furi_assert(plugin); - if(strncmp(name, plugin->name, name_len) != 0) { - FURI_LOG_E(TAG, "Module name missmatch %s", plugin->name); + if(furi_string_cmp_str(deslashed_name, plugin->name) != 0) { + FURI_LOG_E(TAG, "Module name mismatch %s", plugin->name); break; } - JsModuleData module = {.create = plugin->create, .destroy = plugin->destroy}; - JsModuleDict_set_at(modules->module_dict, module_name, module); + JsModuleData module = { + .create = plugin->create, + .destroy = plugin->destroy, + .name = furi_string_alloc_set_str(name), + }; + JsModuleArray_push_at(modules->modules, 0, module); + + if(plugin->api_interface) { + FURI_LOG_I(TAG, "Added module API to composite resolver: %s", plugin->name); + composite_api_resolver_add(modules->resolver, plugin->api_interface); + } module_found = true; } while(0); furi_string_free(module_path); + furi_string_free(deslashed_name); } // Run module constructor mjs_val_t module_object = MJS_UNDEFINED; if(module_found) { - module_inst = JsModuleDict_get(modules->module_dict, module_name); + module_inst = js_find_loaded_module(modules, name); furi_assert(module_inst); if(module_inst->create) { //-V779 - module_inst->context = module_inst->create(modules->mjs, &module_object); + module_inst->context = module_inst->create(modules->mjs, &module_object, modules); } } @@ -123,7 +166,12 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name); } - furi_string_free(module_name); - return module_object; } + +void* js_module_get(JsModules* modules, const char* name) { + FuriString* module_name = furi_string_alloc_set_str(name); + JsModuleData* module_inst = js_find_loaded_module(modules, name); + furi_string_free(module_name); + return module_inst ? module_inst->context : NULL; +} diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 77e50786f4..788715872e 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -1,4 +1,6 @@ #pragma once + +#include #include "js_thread_i.h" #include #include @@ -7,19 +9,269 @@ #define PLUGIN_APP_ID "js" #define PLUGIN_API_VERSION 1 -typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); -typedef void (*JsModeDestructor)(void* inst); +/** + * @brief Returns the foreign pointer in `obj["_"]` + */ +#define JS_GET_INST(mjs, obj) mjs_get_ptr(mjs, mjs_get(mjs, obj, INST_PROP_NAME, ~0)) +/** + * @brief Returns the foreign pointer in `this["_"]` + */ +#define JS_GET_CONTEXT(mjs) JS_GET_INST(mjs, mjs_get_this(mjs)) + +/** + * @brief Syntax sugar for constructing an object + * + * @example + * ```c + * mjs_val_t my_obj = mjs_mk_object(mjs); + * JS_ASSIGN_MULTI(mjs, my_obj) { + * JS_FIELD("method1", MJS_MK_FN(js_storage_file_is_open)); + * JS_FIELD("method2", MJS_MK_FN(js_storage_file_is_open)); + * } + * ``` + */ +#define JS_ASSIGN_MULTI(mjs, object) \ + for(struct { \ + struct mjs* mjs; \ + mjs_val_t val; \ + int i; \ + } _ass_multi = {mjs, object, 0}; \ + _ass_multi.i == 0; \ + _ass_multi.i++) +#define JS_FIELD(name, value) mjs_set(_ass_multi.mjs, _ass_multi.val, name, ~0, value) + +/** + * @brief The first word of structures that foreign pointer JS values point to + * + * This is used to detect situations where JS code mistakenly passes an opaque + * foreign pointer of one type as an argument to a native function which expects + * a struct of another type. + * + * It is recommended to use this functionality in conjunction with the following + * convenience verification macros: + * - `JS_ARG_STRUCT()` + * - `JS_ARG_OBJ_WITH_STRUCT()` + * + * @warning In order for the mechanism to work properly, your struct must store + * the magic value in the first word. + */ +typedef enum { + JsForeignMagicStart = 0x15BAD000, + JsForeignMagic_JsEventLoopContract, +} JsForeignMagic; + +// Are you tired of your silly little JS+C glue code functions being 75% +// argument validation code and 25% actual logic? Introducing: ASS (Argument +// Schema for Scripts)! ASS is a set of macros that reduce the typical +// boilerplate code of "check argument count, get arguments, validate arguments, +// extract C values from arguments" down to just one line! + +/** + * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies + * that the function requires exactly as many arguments as were specified. + */ +#define JS_EXACTLY == +/** + * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies + * that the function requires at least as many arguments as were specified. + */ +#define JS_AT_LEAST >= + +#define JS_ENUM_MAP(var_name, ...) \ + static const JsEnumMapping var_name##_mapping[] = { \ + {NULL, sizeof(var_name)}, \ + __VA_ARGS__, \ + {NULL, 0}, \ + }; typedef struct { - char* name; - JsModeConstructor create; - JsModeDestructor destroy; -} JsModuleDescriptor; + const char* name; + size_t value; +} JsEnumMapping; + +typedef struct { + void* out; + int (*validator)(mjs_val_t); + void (*converter)(struct mjs*, mjs_val_t*, void* out, const void* extra); + const char* expected_type; + bool (*extended_validator)(struct mjs*, mjs_val_t, const void* extra); + const void* extra_data; +} _js_arg_decl; + +static inline void _js_to_int32(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(int32_t*)out = mjs_get_int32(mjs, *in); +} +#define JS_ARG_INT32(out) ((_js_arg_decl){out, mjs_is_number, _js_to_int32, "number", NULL, NULL}) + +static inline void _js_to_ptr(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(void**)out = mjs_get_ptr(mjs, *in); +} +#define JS_ARG_PTR(out) \ + ((_js_arg_decl){out, mjs_is_foreign, _js_to_ptr, "opaque pointer", NULL, NULL}) + +static inline void _js_to_string(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(const char**)out = mjs_get_string(mjs, in, NULL); +} +#define JS_ARG_STR(out) ((_js_arg_decl){out, mjs_is_string, _js_to_string, "string", NULL, NULL}) + +static inline void _js_to_bool(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(bool*)out = !!mjs_get_bool(mjs, *in); +} +#define JS_ARG_BOOL(out) ((_js_arg_decl){out, mjs_is_boolean, _js_to_bool, "boolean", NULL, NULL}) + +static inline void _js_passthrough(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + UNUSED(mjs); + *(mjs_val_t*)out = *in; +} +#define JS_ARG_ANY(out) ((_js_arg_decl){out, NULL, _js_passthrough, "any", NULL, NULL}) +#define JS_ARG_OBJ(out) ((_js_arg_decl){out, mjs_is_object, _js_passthrough, "any", NULL, NULL}) +#define JS_ARG_FN(out) \ + ((_js_arg_decl){out, mjs_is_function, _js_passthrough, "function", NULL, NULL}) +#define JS_ARG_ARR(out) ((_js_arg_decl){out, mjs_is_array, _js_passthrough, "array", NULL, NULL}) + +static inline bool _js_validate_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { + JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; + JsForeignMagic struct_magic = *(JsForeignMagic*)mjs_get_ptr(mjs, val); + return struct_magic == expected_magic; +} +#define JS_ARG_STRUCT(type, out) \ + ((_js_arg_decl){ \ + out, \ + mjs_is_foreign, \ + _js_to_ptr, \ + #type, \ + _js_validate_struct, \ + (void*)JsForeignMagic##_##type}) + +static inline bool _js_validate_obj_w_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { + JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; + JsForeignMagic struct_magic = *(JsForeignMagic*)JS_GET_INST(mjs, val); + return struct_magic == expected_magic; +} +#define JS_ARG_OBJ_WITH_STRUCT(type, out) \ + ((_js_arg_decl){ \ + out, \ + mjs_is_object, \ + _js_passthrough, \ + #type, \ + _js_validate_obj_w_struct, \ + (void*)JsForeignMagic##_##type}) + +static inline bool _js_validate_enum(struct mjs* mjs, mjs_val_t val, const void* extra) { + for(const JsEnumMapping* mapping = (JsEnumMapping*)extra + 1; mapping->name; mapping++) + if(strcmp(mapping->name, mjs_get_string(mjs, &val, NULL)) == 0) return true; + return false; +} +static inline void + _js_convert_enum(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) { + const JsEnumMapping* mapping = (JsEnumMapping*)extra; + size_t size = mapping->value; // get enum size from first entry + for(mapping++; mapping->name; mapping++) { + if(strcmp(mapping->name, mjs_get_string(mjs, val, NULL)) == 0) { + if(size == 1) + *(uint8_t*)out = mapping->value; + else if(size == 2) + *(uint16_t*)out = mapping->value; + else if(size == 4) + *(uint32_t*)out = mapping->value; + else if(size == 8) + *(uint64_t*)out = mapping->value; + return; + } + } + // unreachable, thanks to _js_validate_enum +} +#define JS_ARG_ENUM(var_name, name) \ + ((_js_arg_decl){ \ + &var_name, \ + mjs_is_string, \ + _js_convert_enum, \ + name " enum", \ + _js_validate_enum, \ + var_name##_mapping}) + +//-V:JS_FETCH_ARGS_OR_RETURN:1008 +/** + * @brief Fetches and validates the arguments passed to a JS function + * + * Example: `int32_t my_arg; JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&my_arg));` + * + * @warning This macro executes `return;` by design in case of an argument count + * mismatch or a validation failure + */ +#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \ + _js_arg_decl _js_args[] = {__VA_ARGS__}; \ + int _js_arg_cnt = COUNT_OF(_js_args); \ + mjs_val_t _js_arg_vals[_js_arg_cnt]; \ + if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "expected %s%d arguments, got %d", \ + #arg_operator, \ + _js_arg_cnt, \ + mjs_nargs(mjs)); \ + for(int _i = 0; _i < _js_arg_cnt; _i++) { \ + _js_arg_vals[_i] = mjs_arg(mjs, _i); \ + if(_js_args[_i].validator) \ + if(!_js_args[_i].validator(_js_arg_vals[_i])) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "argument %d: expected %s", \ + _i, \ + _js_args[_i].expected_type); \ + if(_js_args[_i].extended_validator) \ + if(!_js_args[_i].extended_validator(mjs, _js_arg_vals[_i], _js_args[_i].extra_data)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "argument %d: expected %s", \ + _i, \ + _js_args[_i].expected_type); \ + _js_args[_i].converter( \ + mjs, &_js_arg_vals[_i], _js_args[_i].out, _js_args[_i].extra_data); \ + } + +/** + * @brief Prepends an error, sets the JS return value to `undefined` and returns + * from the C function + * @warning This macro executes `return;` by design + */ +#define JS_ERROR_AND_RETURN(mjs, error_code, ...) \ + do { \ + mjs_prepend_errorf(mjs, error_code, __VA_ARGS__); \ + mjs_return(mjs, MJS_UNDEFINED); \ + return; \ + } while(0) typedef struct JsModules JsModules; +typedef void* (*JsModuleConstructor)(struct mjs* mjs, mjs_val_t* object, JsModules* modules); +typedef void (*JsModuleDestructor)(void* inst); + +typedef struct { + char* name; + JsModuleConstructor create; + JsModuleDestructor destroy; + const ElfApiInterface* api_interface; +} JsModuleDescriptor; + JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver); void js_modules_destroy(JsModules* modules); mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len); + +/** + * @brief Gets a module instance by its name + * This is useful when a module wants to access a stateful API of another + * module. + * @returns Pointer to module context, NULL if the module is not instantiated + */ +void* js_module_get(JsModules* modules, const char* name); diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index e1c21e9fbd..95cad84f6b 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -196,17 +196,11 @@ static void js_require(struct mjs* mjs) { } static void js_global_to_string(struct mjs* mjs) { - double num = mjs_get_double(mjs, mjs_arg(mjs, 0)); - char tmp_str[] = "-2147483648"; - itoa(num, tmp_str, 10); - mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); - mjs_return(mjs, ret); -} - -static void js_global_to_hex_string(struct mjs* mjs) { + int base = 10; + if(mjs_nargs(mjs) > 1) base = mjs_get_int(mjs, mjs_arg(mjs, 1)); double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); - char tmp_str[] = "-FFFFFFFF"; - itoa(num, tmp_str, 16); + char tmp_str[] = "-2147483648"; + itoa(num, tmp_str, base); mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); mjs_return(mjs, ret); } @@ -340,8 +334,7 @@ static int32_t js_thread(void* arg) { } mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print)); mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay)); - mjs_set(mjs, global, "to_string", ~0, MJS_MK_FN(js_global_to_string)); - mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string)); + mjs_set(mjs, global, "toString", ~0, MJS_MK_FN(js_global_to_string)); mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address)); mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require)); mjs_set(mjs, global, "parse_int", ~0, MJS_MK_FN(js_parse_int)); @@ -400,8 +393,8 @@ static int32_t js_thread(void* arg) { } } - js_modules_destroy(worker->modules); mjs_destroy(mjs); + js_modules_destroy(worker->modules); composite_api_resolver_free(worker->resolver); diff --git a/applications/system/js_app/js_thread.h b/applications/system/js_app/js_thread.h index 969715ec1a..581a449192 100644 --- a/applications/system/js_app/js_thread.h +++ b/applications/system/js_app/js_thread.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + typedef struct JsThread JsThread; typedef enum { @@ -14,3 +18,7 @@ typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* con JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context); void js_thread_stop(JsThread* worker); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index d2e30b3342..85a70b493d 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -102,8 +102,8 @@ static bool setup_parse_params( } mjs_val_t vid_obj = mjs_get(mjs, arg, "vid", ~0); mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0); - mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0); - mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0); + mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfrName", ~0); + mjs_val_t prod_obj = mjs_get(mjs, arg, "prodName", ~0); mjs_val_t layout_obj = mjs_get(mjs, arg, "layout_path", ~0); if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) { @@ -486,7 +486,8 @@ static void js_badusb_alt_println(struct mjs* mjs) { badusb_print(mjs, true, true); } -static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst)); mjs_val_t badusb_obj = mjs_mk_object(mjs); mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb)); @@ -514,6 +515,7 @@ static const JsModuleDescriptor js_badusb_desc = { "badusb", js_badusb_create, js_badusb_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c deleted file mode 100644 index be2b5a95a8..0000000000 --- a/applications/system/js_app/modules/js_dialog.c +++ /dev/null @@ -1,218 +0,0 @@ -#include -#include "../js_modules.h" -#include - -// File icon -#include -static const uint8_t _I_file_10px_0[] = { - 0x00, 0x7f, 0x00, 0xa1, 0x00, 0x2d, 0x01, 0xe1, 0x01, 0x0d, 0x01, - 0x01, 0x01, 0x7d, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0x01, -}; -static const uint8_t* const _I_file_10px[] = {_I_file_10px_0}; - -static const Icon I_file_10px = - {.width = 10, .height = 10, .frame_count = 1, .frame_rate = 0, .frames = _I_file_10px}; -// File icon end - -static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { - size_t num_args = mjs_nargs(mjs); - if(num_args != 2) { - return false; - } - mjs_val_t header_obj = mjs_arg(mjs, 0); - mjs_val_t msg_obj = mjs_arg(mjs, 1); - if((!mjs_is_string(header_obj)) || (!mjs_is_string(msg_obj))) { - return false; - } - - size_t arg_len = 0; - *hdr = mjs_get_string(mjs, &header_obj, &arg_len); - if(arg_len == 0) { - *hdr = NULL; - } - - *msg = mjs_get_string(mjs, &msg_obj, &arg_len); - if(arg_len == 0) { - *msg = NULL; - } - - return true; -} - -static void js_dialog_message(struct mjs* mjs) { - const char* dialog_header = NULL; - const char* dialog_msg = NULL; - if(!js_dialog_msg_parse_params(mjs, &dialog_header, &dialog_msg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_buttons(message, NULL, "OK", NULL); - if(dialog_header) { - dialog_message_set_header(message, dialog_header, 64, 3, AlignCenter, AlignTop); - } - if(dialog_msg) { - dialog_message_set_text(message, dialog_msg, 64, 26, AlignCenter, AlignTop); - } - DialogMessageButton result = dialog_message_show(dialogs, message); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - mjs_return(mjs, mjs_mk_boolean(mjs, result == DialogMessageButtonCenter)); -} - -static void js_dialog_custom(struct mjs* mjs) { - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - - bool params_correct = false; - - do { - if(mjs_nargs(mjs) != 1) { - break; - } - mjs_val_t params_obj = mjs_arg(mjs, 0); - if(!mjs_is_object(params_obj)) { - break; - } - - mjs_val_t text_obj = mjs_get(mjs, params_obj, "header", ~0); - size_t arg_len = 0; - const char* text_str = mjs_get_string(mjs, &text_obj, &arg_len); - if(arg_len == 0) { - text_str = NULL; - } - if(text_str) { - dialog_message_set_header(message, text_str, 64, 3, AlignCenter, AlignTop); - } - - text_obj = mjs_get(mjs, params_obj, "text", ~0); - text_str = mjs_get_string(mjs, &text_obj, &arg_len); - if(arg_len == 0) { - text_str = NULL; - } - if(text_str) { - dialog_message_set_text(message, text_str, 64, 26, AlignCenter, AlignTop); - } - - mjs_val_t btn_obj[3] = { - mjs_get(mjs, params_obj, "button_left", ~0), - mjs_get(mjs, params_obj, "button_center", ~0), - mjs_get(mjs, params_obj, "button_right", ~0), - }; - const char* btn_text[3] = {NULL, NULL, NULL}; - - for(uint8_t i = 0; i < 3; i++) { - if(!mjs_is_string(btn_obj[i])) { - continue; - } - btn_text[i] = mjs_get_string(mjs, &btn_obj[i], &arg_len); - if(arg_len == 0) { - btn_text[i] = NULL; - } - } - - dialog_message_set_buttons(message, btn_text[0], btn_text[1], btn_text[2]); - - DialogMessageButton result = dialog_message_show(dialogs, message); - mjs_val_t return_obj = MJS_UNDEFINED; - if(result == DialogMessageButtonLeft) { - return_obj = mjs_mk_string(mjs, btn_text[0], ~0, true); - } else if(result == DialogMessageButtonCenter) { - return_obj = mjs_mk_string(mjs, btn_text[1], ~0, true); - } else if(result == DialogMessageButtonRight) { - return_obj = mjs_mk_string(mjs, btn_text[2], ~0, true); - } else { - return_obj = mjs_mk_string(mjs, "", ~0, true); - } - - mjs_return(mjs, return_obj); - params_correct = true; - } while(0); - - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - - if(!params_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - } -} - -static void js_dialog_pick_file(struct mjs* mjs) { - if(mjs_nargs(mjs) != 2) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Wrong arguments"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - mjs_val_t base_path_obj = mjs_arg(mjs, 0); - if(!mjs_is_string(base_path_obj)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base path must be a string"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - size_t base_path_len = 0; - const char* base_path = mjs_get_string(mjs, &base_path_obj, &base_path_len); - if((base_path_len == 0) || (base_path == NULL)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Bad base path argument"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - mjs_val_t extension_obj = mjs_arg(mjs, 1); - if(!mjs_is_string(extension_obj)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Extension must be a string"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - size_t extension_len = 0; - const char* extension = mjs_get_string(mjs, &extension_obj, &extension_len); - if((extension_len == 0) || (extension == NULL)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Bad extension argument"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - const DialogsFileBrowserOptions browser_options = { - .extension = extension, - .icon = &I_file_10px, - .base_path = base_path, - }; - FuriString* path = furi_string_alloc_set(base_path); - if(dialog_file_browser_show(dialogs, path, path, &browser_options)) { - mjs_return(mjs, mjs_mk_string(mjs, furi_string_get_cstr(path), ~0, true)); - } else { - mjs_return(mjs, MJS_UNDEFINED); - } - furi_string_free(path); - furi_record_close(RECORD_DIALOGS); -} - -static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { - mjs_val_t dialog_obj = mjs_mk_object(mjs); - mjs_set(mjs, dialog_obj, "message", ~0, MJS_MK_FN(js_dialog_message)); - mjs_set(mjs, dialog_obj, "custom", ~0, MJS_MK_FN(js_dialog_custom)); - mjs_set(mjs, dialog_obj, "pickFile", ~0, MJS_MK_FN(js_dialog_pick_file)); - *object = dialog_obj; - - return (void*)1; -} - -static const JsModuleDescriptor js_dialog_desc = { - "dialog", - js_dialog_create, - NULL, -}; - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_dialog_desc, -}; - -const FlipperAppPluginDescriptor* js_dialog_ep(void) { - return &plugin_descriptor; -} diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.c b/applications/system/js_app/modules/js_event_loop/js_event_loop.c new file mode 100644 index 0000000000..c4f0d1beec --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.c @@ -0,0 +1,451 @@ +#include "js_event_loop.h" +#include "../../js_modules.h" // IWYU pragma: keep +#include +#include + +/** + * @brief Number of arguments that callbacks receive from this module that they can't modify + */ +#define SYSTEM_ARGS 2 + +/** + * @brief Context passed to the generic event callback + */ +typedef struct { + JsEventLoopObjectType object_type; + + struct mjs* mjs; + mjs_val_t callback; + // NOTE: not using an mlib array because resizing is not needed. + mjs_val_t* arguments; + size_t arity; + + JsEventLoopTransformer transformer; + void* transformer_context; +} JsEventLoopCallbackContext; + +/** + * @brief Contains data needed to cancel a subscription + */ +typedef struct { + FuriEventLoop* loop; + JsEventLoopObjectType object_type; + FuriEventLoopObject* object; + JsEventLoopCallbackContext* context; + JsEventLoopContract* contract; + void* subscriptions; // SubscriptionArray_t, which we can't reference in this definition +} JsEventLoopSubscription; + +typedef struct { + FuriEventLoop* loop; + struct mjs* mjs; +} JsEventLoopTickContext; + +ARRAY_DEF(SubscriptionArray, JsEventLoopSubscription*, M_PTR_OPLIST); //-V575 +ARRAY_DEF(ContractArray, JsEventLoopContract*, M_PTR_OPLIST); //-V575 + +/** + * @brief Per-module instance control structure + */ +struct JsEventLoop { + FuriEventLoop* loop; + SubscriptionArray_t subscriptions; + ContractArray_t owned_contracts; //mjs, + &result, + context->callback, + MJS_UNDEFINED, + context->arity, + context->arguments); + + // save returned args for next call + if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return; + for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) { + mjs_disown(context->mjs, &context->arguments[i + SYSTEM_ARGS]); + context->arguments[i + SYSTEM_ARGS] = mjs_array_get(context->mjs, result, i); + mjs_own(context->mjs, &context->arguments[i + SYSTEM_ARGS]); + } +} + +/** + * @brief Handles non-timer events + */ +static bool js_event_loop_callback(void* object, void* param) { + JsEventLoopCallbackContext* context = param; + + if(context->transformer) { + mjs_disown(context->mjs, &context->arguments[1]); + context->arguments[1] = + context->transformer(context->mjs, object, context->transformer_context); + mjs_own(context->mjs, &context->arguments[1]); + } else { + // default behavior: take semaphores and mutexes + switch(context->object_type) { + case JsEventLoopObjectTypeSemaphore: { + FuriSemaphore* semaphore = object; + furi_check(furi_semaphore_acquire(semaphore, 0) == FuriStatusOk); + } break; + default: + // the corresponding check has been performed when we were given the contract + furi_crash(); + } + } + + js_event_loop_callback_generic(param); + + return true; +} + +/** + * @brief Cancels an event subscription + */ +static void js_event_loop_subscription_cancel(struct mjs* mjs) { + JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs); + + if(subscription->object_type == JsEventLoopObjectTypeTimer) { + furi_event_loop_timer_stop(subscription->object); + } else { + furi_event_loop_unsubscribe(subscription->loop, subscription->object); + } + + free(subscription->context->arguments); + free(subscription->context); + + // find and remove ourselves from the array + SubscriptionArray_it_t iterator; + for(SubscriptionArray_it(iterator, subscription->subscriptions); + !SubscriptionArray_end_p(iterator); + SubscriptionArray_next(iterator)) { + JsEventLoopSubscription* item = *SubscriptionArray_cref(iterator); + if(item == subscription) break; + } + SubscriptionArray_remove(subscription->subscriptions, iterator); + free(subscription); + + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Subscribes a JavaScript function to an event + */ +static void js_event_loop_subscribe(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + // get arguments + JsEventLoopContract* contract; + mjs_val_t callback; + JS_FETCH_ARGS_OR_RETURN( + mjs, JS_AT_LEAST, JS_ARG_STRUCT(JsEventLoopContract, &contract), JS_ARG_FN(&callback)); + + // create subscription object + JsEventLoopSubscription* subscription = malloc(sizeof(JsEventLoopSubscription)); + JsEventLoopCallbackContext* context = malloc(sizeof(JsEventLoopCallbackContext)); + subscription->loop = module->loop; + subscription->object_type = contract->object_type; + subscription->context = context; + subscription->subscriptions = module->subscriptions; + if(contract->object_type == JsEventLoopObjectTypeTimer) subscription->contract = contract; + mjs_val_t subscription_obj = mjs_mk_object(mjs); + mjs_set(mjs, subscription_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, subscription)); + mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel)); + + // create callback context + context->object_type = contract->object_type; + context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2; + context->arguments = calloc(context->arity, sizeof(mjs_val_t)); + context->arguments[0] = subscription_obj; + context->arguments[1] = MJS_UNDEFINED; + for(size_t i = SYSTEM_ARGS; i < context->arity; i++) { + mjs_val_t arg = mjs_arg(mjs, i - SYSTEM_ARGS + 2); + context->arguments[i] = arg; + mjs_own(mjs, &context->arguments[i]); + } + context->mjs = mjs; + context->callback = callback; + mjs_own(mjs, &context->callback); + mjs_own(mjs, &context->arguments[0]); + mjs_own(mjs, &context->arguments[1]); + + // queue and stream contracts must have a transform callback, others are allowed to delegate + // the obvious default behavior to this module + if(contract->object_type == JsEventLoopObjectTypeQueue || + contract->object_type == JsEventLoopObjectTypeStream) { + furi_check(contract->non_timer.transformer); + } + context->transformer = contract->non_timer.transformer; + context->transformer_context = contract->non_timer.transformer_context; + + // subscribe + switch(contract->object_type) { + case JsEventLoopObjectTypeTimer: { + FuriEventLoopTimer* timer = furi_event_loop_timer_alloc( + module->loop, js_event_loop_callback_generic, contract->timer.type, context); + furi_event_loop_timer_start(timer, contract->timer.interval_ticks); + contract->object = timer; + } break; + case JsEventLoopObjectTypeSemaphore: + furi_event_loop_subscribe_semaphore( + module->loop, + contract->object, + contract->non_timer.event, + js_event_loop_callback, + context); + break; + case JsEventLoopObjectTypeQueue: + furi_event_loop_subscribe_message_queue( + module->loop, + contract->object, + contract->non_timer.event, + js_event_loop_callback, + context); + break; + default: + furi_crash("unimplemented"); + } + + subscription->object = contract->object; + SubscriptionArray_push_back(module->subscriptions, subscription); + mjs_return(mjs, subscription_obj); +} + +/** + * @brief Runs the event loop until it is stopped + */ +static void js_event_loop_run(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + furi_event_loop_run(module->loop); +} + +/** + * @brief Stops a running event loop + */ +static void js_event_loop_stop(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + furi_event_loop_stop(module->loop); +} + +/** + * @brief Creates a timer event that can be subscribed to just like any other + * event + */ +static void js_event_loop_timer(struct mjs* mjs) { + // get arguments + const char* mode_str; + int32_t interval; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&mode_str), JS_ARG_INT32(&interval)); + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + FuriEventLoopTimerType mode; + if(strcasecmp(mode_str, "periodic") == 0) { + mode = FuriEventLoopTimerTypePeriodic; + } else if(strcasecmp(mode_str, "oneshot") == 0) { + mode = FuriEventLoopTimerTypeOnce; + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: unknown mode"); + } + + // make timer contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeTimer, + .object = NULL, + .timer = + { + .interval_ticks = furi_ms_to_ticks((uint32_t)interval), + .type = mode, + }, + }; + ContractArray_push_back(module->owned_contracts, contract); + mjs_return(mjs, mjs_mk_foreign(mjs, contract)); +} + +/** + * @brief Queue transformer. Takes `mjs_val_t` pointers out of a queue and + * returns their dereferenced value + */ +static mjs_val_t + js_event_loop_queue_transformer(struct mjs* mjs, FuriEventLoopObject* object, void* context) { + UNUSED(context); + mjs_val_t* message_ptr; + furi_check(furi_message_queue_get(object, &message_ptr, 0) == FuriStatusOk); + mjs_val_t message = *message_ptr; + mjs_disown(mjs, message_ptr); + free(message_ptr); + return message; +} + +/** + * @brief Sends a message to a queue + */ +static void js_event_loop_queue_send(struct mjs* mjs) { + // get arguments + mjs_val_t message; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&message)); + JsEventLoopContract* contract = JS_GET_CONTEXT(mjs); + + // send message + mjs_val_t* message_ptr = malloc(sizeof(mjs_val_t)); + *message_ptr = message; + mjs_own(mjs, message_ptr); + furi_message_queue_put(contract->object, &message_ptr, 0); + + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Creates a queue + */ +static void js_event_loop_queue(struct mjs* mjs) { + // get arguments + int32_t length; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&length)); + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + // make queue contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + // we could store `mjs_val_t`s in the queue directly if not for mJS' requirement to have consistent pointers to owned values + .object = furi_message_queue_alloc((size_t)length, sizeof(mjs_val_t*)), + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = js_event_loop_queue_transformer, + }, + }; + ContractArray_push_back(module->owned_contracts, contract); + + // return object with control methods + mjs_val_t queue = mjs_mk_object(mjs); + mjs_set(mjs, queue, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, contract)); + mjs_set(mjs, queue, "input", ~0, mjs_mk_foreign(mjs, contract)); + mjs_set(mjs, queue, "send", ~0, MJS_MK_FN(js_event_loop_queue_send)); + mjs_return(mjs, queue); +} + +static void js_event_loop_tick(void* param) { + JsEventLoopTickContext* context = param; + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0); + if(flags & FuriFlagError) { + return; + } + if(flags & ThreadEventStop) { + furi_event_loop_stop(context->loop); + mjs_exit(context->mjs); + } +} + +static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + mjs_val_t event_loop_obj = mjs_mk_object(mjs); + JsEventLoop* module = malloc(sizeof(JsEventLoop)); + JsEventLoopTickContext* tick_ctx = malloc(sizeof(JsEventLoopTickContext)); + module->loop = furi_event_loop_alloc(); + tick_ctx->loop = module->loop; + tick_ctx->mjs = mjs; + module->tick_context = tick_ctx; + furi_event_loop_tick_set(module->loop, 10, js_event_loop_tick, tick_ctx); + SubscriptionArray_init(module->subscriptions); + ContractArray_init(module->owned_contracts); + + mjs_set(mjs, event_loop_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module)); + mjs_set(mjs, event_loop_obj, "subscribe", ~0, MJS_MK_FN(js_event_loop_subscribe)); + mjs_set(mjs, event_loop_obj, "run", ~0, MJS_MK_FN(js_event_loop_run)); + mjs_set(mjs, event_loop_obj, "stop", ~0, MJS_MK_FN(js_event_loop_stop)); + mjs_set(mjs, event_loop_obj, "timer", ~0, MJS_MK_FN(js_event_loop_timer)); + mjs_set(mjs, event_loop_obj, "queue", ~0, MJS_MK_FN(js_event_loop_queue)); + + *object = event_loop_obj; + return module; +} + +static void js_event_loop_destroy(void* inst) { + if(inst) { + JsEventLoop* module = inst; + furi_event_loop_stop(module->loop); + + // free subscriptions + SubscriptionArray_it_t sub_iterator; + for(SubscriptionArray_it(sub_iterator, module->subscriptions); + !SubscriptionArray_end_p(sub_iterator); + SubscriptionArray_next(sub_iterator)) { + JsEventLoopSubscription* const* sub = SubscriptionArray_cref(sub_iterator); + free((*sub)->context->arguments); + free((*sub)->context); + free(*sub); + } + SubscriptionArray_clear(module->subscriptions); + + // free owned contracts + ContractArray_it_t iterator; + for(ContractArray_it(iterator, module->owned_contracts); !ContractArray_end_p(iterator); + ContractArray_next(iterator)) { + // unsubscribe object + JsEventLoopContract* contract = *ContractArray_cref(iterator); + if(contract->object_type == JsEventLoopObjectTypeTimer) { + furi_event_loop_timer_stop(contract->object); + } else { + furi_event_loop_unsubscribe(module->loop, contract->object); + } + + // free object + switch(contract->object_type) { + case JsEventLoopObjectTypeTimer: + furi_event_loop_timer_free(contract->object); + break; + case JsEventLoopObjectTypeSemaphore: + furi_semaphore_free(contract->object); + break; + case JsEventLoopObjectTypeQueue: + furi_message_queue_free(contract->object); + break; + default: + furi_crash("unimplemented"); + } + + free(contract); + } + ContractArray_clear(module->owned_contracts); + + furi_event_loop_free(module->loop); + free(module->tick_context); + free(module); + } +} + +extern const ElfApiInterface js_event_loop_hashtable_api_interface; + +static const JsModuleDescriptor js_event_loop_desc = { + "event_loop", + js_event_loop_create, + js_event_loop_destroy, + &js_event_loop_hashtable_api_interface, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_event_loop_desc, +}; + +const FlipperAppPluginDescriptor* js_event_loop_ep(void) { + return &plugin_descriptor; +} + +FuriEventLoop* js_event_loop_get_loop(JsEventLoop* loop) { + // porta: not the proudest function that i ever wrote + furi_check(loop); + return loop->loop; +} diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.h b/applications/system/js_app/modules/js_event_loop/js_event_loop.h new file mode 100644 index 0000000000..7ae608e349 --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.h @@ -0,0 +1,104 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include +#include + +/** + * @file js_event_loop.h + * + * In JS interpreter code, `js_event_loop` always creates and maintains the + * event loop. There are two ways in which other modules can integrate with this + * loop: + * - Via contracts: The user of your module would have to acquire an opaque + * JS value from you and pass it to `js_event_loop`. This is useful for + * events that they user may be interested in. For more info, look at + * `JsEventLoopContract`. Also look at `js_event_loop_get_loop`, which + * you will need to unsubscribe the event loop from your object. + * - Directly: When your module is created, you can acquire an instance of + * `JsEventLoop` which you can use to acquire an instance of + * `FuriEventLoop` that you can manipulate directly, without the JS + * programmer having to pass contracts around. This is useful for + * "behind-the-scenes" events that the user does not need to know about. For + * more info, look at `js_event_loop_get_loop`. + * + * In both cases, your module is responsible for both instantiating, + * unsubscribing and freeing the object that the event loop subscribes to. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct JsEventLoop JsEventLoop; + +typedef enum { + JsEventLoopObjectTypeTimer, + JsEventLoopObjectTypeQueue, + JsEventLoopObjectTypeMutex, + JsEventLoopObjectTypeSemaphore, + JsEventLoopObjectTypeStream, +} JsEventLoopObjectType; + +typedef mjs_val_t ( + *JsEventLoopTransformer)(struct mjs* mjs, FuriEventLoopObject* object, void* context); + +typedef struct { + FuriEventLoopEvent event; + JsEventLoopTransformer transformer; + void* transformer_context; +} JsEventLoopNonTimerContract; + +typedef struct { + FuriEventLoopTimerType type; + uint32_t interval_ticks; +} JsEventLoopTimerContract; + +/** + * @brief Adapter for other JS modules that wish to integrate with the event + * loop JS module + * + * If another module wishes to integrate with `js_event_loop`, it needs to + * implement a function callable from JS that returns an mJS foreign pointer to + * an instance of this structure. This value is then read by `event_loop`'s + * `subscribe` function. + * + * There are two fundamental variants of this structure: + * - `object_type` is `JsEventLoopObjectTypeTimer`: the `timer` field is + * valid, and the `non_timer` field is invalid. + * - `object_type` is something else: the `timer` field is invalid, and the + * `non_timer` field is valid. `non_timer.event` will be passed to + * `furi_event_loop_subscribe`. `non_timer.transformer` will be called to + * transform an object into a JS value (called an item) that's passed to the + * JS callback. This is useful for example to take an item out of a message + * queue and pass it to JS code in a convenient format. If + * `non_timer.transformer` is NULL, the event loop will take semaphores and + * mutexes on its own. + * + * The producer of the contract is responsible for freeing both the contract and + * the object that it points to when the interpreter is torn down. + */ +typedef struct { + JsForeignMagic magic; // +#include + +#include "js_event_loop_api_table_i.h" + +static_assert(!has_hash_collisions(js_event_loop_api_table), "Detected API method hash collision!"); + +extern "C" constexpr HashtableApiInterface js_event_loop_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + js_event_loop_api_table.cbegin(), + js_event_loop_api_table.cend(), +}; diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h b/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h new file mode 100644 index 0000000000..49090caebc --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h @@ -0,0 +1,4 @@ +#include "js_event_loop.h" + +static constexpr auto js_event_loop_api_table = sort( + create_array_t(API_METHOD(js_event_loop_get_loop, FuriEventLoop*, (JsEventLoop*)))); diff --git a/applications/system/js_app/modules/js_flipper.c b/applications/system/js_app/modules/js_flipper.c index 4619a1593c..43c675e107 100644 --- a/applications/system/js_app/modules/js_flipper.c +++ b/applications/system/js_app/modules/js_flipper.c @@ -25,7 +25,8 @@ static void js_flipper_get_battery(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, info.charge)); } -void* js_flipper_create(struct mjs* mjs, mjs_val_t* object) { +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); mjs_val_t flipper_obj = mjs_mk_object(mjs); mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model)); mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name)); diff --git a/applications/system/js_app/modules/js_flipper.h b/applications/system/js_app/modules/js_flipper.h index 3b05389cc7..98979ce582 100644 --- a/applications/system/js_app/modules/js_flipper.h +++ b/applications/system/js_app/modules/js_flipper.h @@ -1,4 +1,4 @@ #pragma once #include "../js_thread_i.h" -void* js_flipper_create(struct mjs* mjs, mjs_val_t* object); +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules); diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index fb42bea2be..70021968fa 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -1,387 +1,337 @@ -#include "../js_modules.h" +#include "../js_modules.h" // IWYU pragma: keep +#include "./js_event_loop/js_event_loop.h" #include #include #include +#include +#include -typedef struct { - FuriHalAdcHandle* handle; -} JsGpioInst; +#define INTERRUPT_QUEUE_LEN 16 +/** + * Per-pin control structure + */ typedef struct { const GpioPin* pin; - const char* name; - const FuriHalAdcChannel channel; -} GpioPinCtx; - -static const GpioPinCtx js_gpio_pins[] = { - {.pin = &gpio_ext_pa7, .name = "PA7", .channel = FuriHalAdcChannel12}, // 2 - {.pin = &gpio_ext_pa6, .name = "PA6", .channel = FuriHalAdcChannel11}, // 3 - {.pin = &gpio_ext_pa4, .name = "PA4", .channel = FuriHalAdcChannel9}, // 4 - {.pin = &gpio_ext_pb3, .name = "PB3", .channel = FuriHalAdcChannelNone}, // 5 - {.pin = &gpio_ext_pb2, .name = "PB2", .channel = FuriHalAdcChannelNone}, // 6 - {.pin = &gpio_ext_pc3, .name = "PC3", .channel = FuriHalAdcChannel4}, // 7 - {.pin = &gpio_swclk, .name = "PA14", .channel = FuriHalAdcChannelNone}, // 10 - {.pin = &gpio_swdio, .name = "PA13", .channel = FuriHalAdcChannelNone}, // 12 - {.pin = &gpio_usart_tx, .name = "PB6", .channel = FuriHalAdcChannelNone}, // 13 - {.pin = &gpio_usart_rx, .name = "PB7", .channel = FuriHalAdcChannelNone}, // 14 - {.pin = &gpio_ext_pc1, .name = "PC1", .channel = FuriHalAdcChannel2}, // 15 - {.pin = &gpio_ext_pc0, .name = "PC0", .channel = FuriHalAdcChannel1}, // 16 - {.pin = &gpio_ibutton, .name = "PB14", .channel = FuriHalAdcChannelNone}, // 17 -}; - -bool js_gpio_get_gpio_pull(const char* pull, GpioPull* value) { - if(strcmp(pull, "no") == 0) { - *value = GpioPullNo; - return true; - } else if(strcmp(pull, "up") == 0) { - *value = GpioPullUp; - return true; - } else if(strcmp(pull, "down") == 0) { - *value = GpioPullDown; - return true; - } else { - *value = GpioPullNo; - return true; - } - return false; -} - -bool js_gpio_get_gpio_mode(const char* mode, GpioMode* value) { - if(strcmp(mode, "input") == 0) { - *value = GpioModeInput; - return true; - } else if(strcmp(mode, "outputPushPull") == 0) { - *value = GpioModeOutputPushPull; - return true; - } else if(strcmp(mode, "outputOpenDrain") == 0) { - *value = GpioModeOutputOpenDrain; - return true; - } else if(strcmp(mode, "altFunctionPushPull") == 0) { - *value = GpioModeAltFunctionPushPull; - return true; - } else if(strcmp(mode, "altFunctionOpenDrain") == 0) { - *value = GpioModeAltFunctionOpenDrain; - return true; - } else if(strcmp(mode, "analog") == 0) { - *value = GpioModeAnalog; - return true; - } else if(strcmp(mode, "interruptRise") == 0) { - *value = GpioModeInterruptRise; - return true; - } else if(strcmp(mode, "interruptFall") == 0) { - *value = GpioModeInterruptFall; - return true; - } else if(strcmp(mode, "interruptRiseFall") == 0) { - *value = GpioModeInterruptRiseFall; - return true; - } else if(strcmp(mode, "eventRise") == 0) { - *value = GpioModeEventRise; - return true; - } else if(strcmp(mode, "eventFall") == 0) { - *value = GpioModeEventFall; - return true; - } else if(strcmp(mode, "eventRiseFall") == 0) { - *value = GpioModeEventRiseFall; - return true; - } else { - return false; - } -} - -const GpioPin* js_gpio_get_gpio_pin(const char* name) { - for(size_t i = 0; i < COUNT_OF(js_gpio_pins); i++) { - if(strcmp(js_gpio_pins[i].name, name) == 0) { - return js_gpio_pins[i].pin; - } - } - return NULL; -} + bool had_interrupt; + FuriSemaphore* interrupt_semaphore; + JsEventLoopContract* interrupt_contract; + FuriHalAdcChannel adc_channel; + FuriHalAdcHandle* adc_handle; +} JsGpioPinInst; + +ARRAY_DEF(ManagedPinsArray, JsGpioPinInst*, M_PTR_OPLIST); //-V575 + +/** + * Per-module instance control structure + */ +typedef struct { + FuriEventLoop* loop; + ManagedPinsArray_t managed_pins; + FuriHalAdcHandle* adc_handle; +} JsGpioInst; -FuriHalAdcChannel js_gpio_get_gpio_channel(const char* name) { - for(size_t i = 0; i < COUNT_OF(js_gpio_pins); i++) { - if(strcmp(js_gpio_pins[i].name, name) == 0) { - return js_gpio_pins[i].channel; - } - } - return FuriHalAdcChannelNone; +/** + * @brief Interrupt callback + */ +static void js_gpio_int_cb(void* arg) { + furi_assert(arg); + FuriSemaphore* semaphore = arg; + furi_semaphore_release(semaphore); } +/** + * @brief Initializes a GPIO pin according to the provided mode object + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * led.init({ direction: "out", outMode: "push_pull" }); + * ``` + */ static void js_gpio_init(struct mjs* mjs) { - mjs_val_t pin_arg = mjs_arg(mjs, 0); - mjs_val_t mode_arg = mjs_arg(mjs, 1); - mjs_val_t pull_arg = mjs_arg(mjs, 2); - - if(!mjs_is_string(pin_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - const char* pin_name = mjs_get_string(mjs, &pin_arg, NULL); - if(!pin_name) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pin name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - if(!mjs_is_string(mode_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - const char* mode_name = mjs_get_string(mjs, &mode_arg, NULL); - if(!mode_name) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get mode name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - if(!mjs_is_string(pull_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - const char* pull_name = mjs_get_string(mjs, &pull_arg, NULL); - if(!pull_name) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pull name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - const GpioPin* gpio_pin = js_gpio_get_gpio_pin(pin_name); - if(gpio_pin == NULL) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pin name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - GpioMode gpio_mode; - if(!js_gpio_get_gpio_mode(mode_name, &gpio_mode)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid mode name"); - mjs_return(mjs, MJS_UNDEFINED); - return; + // deconstruct mode object + mjs_val_t mode_arg; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&mode_arg)); + mjs_val_t direction_arg = mjs_get(mjs, mode_arg, "direction", ~0); + mjs_val_t out_mode_arg = mjs_get(mjs, mode_arg, "outMode", ~0); + mjs_val_t in_mode_arg = mjs_get(mjs, mode_arg, "inMode", ~0); + mjs_val_t edge_arg = mjs_get(mjs, mode_arg, "edge", ~0); + mjs_val_t pull_arg = mjs_get(mjs, mode_arg, "pull", ~0); + + // get strings + const char* direction = mjs_get_string(mjs, &direction_arg, NULL); + const char* out_mode = mjs_get_string(mjs, &out_mode_arg, NULL); + const char* in_mode = mjs_get_string(mjs, &in_mode_arg, NULL); + const char* edge = mjs_get_string(mjs, &edge_arg, NULL); + const char* pull = mjs_get_string(mjs, &pull_arg, NULL); + if(!direction) + JS_ERROR_AND_RETURN( + mjs, MJS_BAD_ARGS_ERROR, "Expected string in \"direction\" field of mode object"); + if(!out_mode) out_mode = "open_drain"; + if(!in_mode) in_mode = "plain_digital"; + if(!edge) edge = "rising"; + + // convert strings to mode + GpioMode mode; + if(strcmp(direction, "out") == 0) { + if(strcmp(out_mode, "push_pull") == 0) + mode = GpioModeOutputPushPull; + else if(strcmp(out_mode, "open_drain") == 0) + mode = GpioModeOutputOpenDrain; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid outMode"); + } else if(strcmp(direction, "in") == 0) { + if(strcmp(in_mode, "analog") == 0) { + mode = GpioModeAnalog; + } else if(strcmp(in_mode, "plain_digital") == 0) { + mode = GpioModeInput; + } else if(strcmp(in_mode, "interrupt") == 0) { + if(strcmp(edge, "rising") == 0) + mode = GpioModeInterruptRise; + else if(strcmp(edge, "falling") == 0) + mode = GpioModeInterruptFall; + else if(strcmp(edge, "both") == 0) + mode = GpioModeInterruptRiseFall; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); + } else if(strcmp(in_mode, "event") == 0) { + if(strcmp(edge, "rising") == 0) + mode = GpioModeEventRise; + else if(strcmp(edge, "falling") == 0) + mode = GpioModeEventFall; + else if(strcmp(edge, "both") == 0) + mode = GpioModeEventRiseFall; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid inMode"); + } + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid direction"); } - GpioPull gpio_pull; - if(!js_gpio_get_gpio_pull(pull_name, &gpio_pull)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pull name"); - mjs_return(mjs, MJS_UNDEFINED); - return; + // convert pull + GpioPull pull_mode; + if(!pull) { + pull_mode = GpioPullNo; + } else if(strcmp(pull, "up") == 0) { + pull_mode = GpioPullUp; + } else if(strcmp(pull, "down") == 0) { + pull_mode = GpioPullDown; + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid pull"); } - expansion_disable(furi_record_open(RECORD_EXPANSION)); - furi_record_close(RECORD_EXPANSION); - - furi_hal_gpio_init(gpio_pin, gpio_mode, gpio_pull, GpioSpeedVeryHigh); - + // init GPIO + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + furi_hal_gpio_init(manager_data->pin, mode, pull_mode, GpioSpeedVeryHigh); mjs_return(mjs, MJS_UNDEFINED); } +/** + * @brief Writes a logic value to a GPIO pin + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * led.init({ direction: "out", outMode: "push_pull" }); + * led.write(true); + * ``` + */ static void js_gpio_write(struct mjs* mjs) { - mjs_val_t pin_arg = mjs_arg(mjs, 0); - mjs_val_t value_arg = mjs_arg(mjs, 1); - - if(!mjs_is_string(pin_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - const char* pin_name = mjs_get_string(mjs, &pin_arg, NULL); - if(!pin_name) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pin name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - if(!mjs_is_boolean(value_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a boolean"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - bool value = mjs_get_bool(mjs, value_arg); - - const GpioPin* gpio_pin = js_gpio_get_gpio_pin(pin_name); - - if(gpio_pin == NULL) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pin name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - furi_hal_gpio_write(gpio_pin, value); - + bool level; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_BOOL(&level)); + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + furi_hal_gpio_write(manager_data->pin, level); mjs_return(mjs, MJS_UNDEFINED); } +/** + * @brief Reads a logic value from a GPIO pin + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let button = gpio.get("pc1"); + * button.init({ direction: "in" }); + * if(button.read()) + * print("hi button!!!!!"); + * ``` + */ static void js_gpio_read(struct mjs* mjs) { - mjs_val_t pin_arg = mjs_arg(mjs, 0); - - if(!mjs_is_string(pin_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - const char* pin_name = mjs_get_string(mjs, &pin_arg, NULL); - if(!pin_name) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pin name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - const GpioPin* gpio_pin = js_gpio_get_gpio_pin(pin_name); - - if(gpio_pin == NULL) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pin name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - bool value = furi_hal_gpio_read(gpio_pin); - + // get level + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + bool value = furi_hal_gpio_read(manager_data->pin); mjs_return(mjs, mjs_mk_boolean(mjs, value)); } -static void js_gpio_read_analog(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsGpioInst* gpio = mjs_get_ptr(mjs, obj_inst); - furi_assert(gpio); - - if(gpio->handle == NULL) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Analog mode not started"); - mjs_return(mjs, MJS_UNDEFINED); - return; +/** + * @brief Returns a event loop contract that can be used to listen to interrupts + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let button = gpio.get("pc1"); + * let event_loop = require("event_loop"); + * button.init({ direction: "in", pull: "up", inMode: "interrupt", edge: "falling" }); + * event_loop.subscribe(button.interrupt(), function (_) { print("Hi!"); }); + * event_loop.run(); + * ``` + */ +static void js_gpio_interrupt(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + + // interrupt handling + if(!manager_data->had_interrupt) { + furi_hal_gpio_add_int_callback( + manager_data->pin, js_gpio_int_cb, manager_data->interrupt_semaphore); + furi_hal_gpio_enable_int_callback(manager_data->pin); + manager_data->had_interrupt = true; } - mjs_val_t pin_arg = mjs_arg(mjs, 0); - - if(!mjs_is_string(pin_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a string"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - const char* pin_name = mjs_get_string(mjs, &pin_arg, NULL); - if(!pin_name) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Failed to get pin name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - FuriHalAdcChannel channel = js_gpio_get_gpio_channel(pin_name); - if(channel == FuriHalAdcChannelNone) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid pin name"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - uint16_t adc_value = furi_hal_adc_read(gpio->handle, channel); - float adc_mv = furi_hal_adc_convert_to_voltage(gpio->handle, adc_value); - - mjs_return(mjs, mjs_mk_number(mjs, adc_mv)); + // make contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = manager_data->interrupt_semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + }, + }; + manager_data->interrupt_contract = contract; + mjs_return(mjs, mjs_mk_foreign(mjs, contract)); } -static void js_gpio_start_analog(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsGpioInst* gpio = mjs_get_ptr(mjs, obj_inst); - furi_assert(gpio); - - FuriHalAdcScale scale = FuriHalAdcScale2048; - if(mjs_nargs(mjs) > 0) { - mjs_val_t scale_arg = mjs_arg(mjs, 0); - - if(!mjs_is_number(scale_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a number"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - int32_t scale_num = mjs_get_int32(mjs, scale_arg); - if(scale_num == 2048 || scale_num == 2000) { // 2 volt reference - scale = FuriHalAdcScale2048; - } else if(scale_num == 2500) { // 2.5 volt reference - scale = FuriHalAdcScale2500; - } else { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Invalid scale"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - } +/** + * @brief Reads a voltage from a GPIO pin in analog mode + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let pot = gpio.get("pc0"); + * pot.init({ direction: "in", inMode: "analog" }); + * print("voltage:" pot.read_analog(), "mV"); + * ``` + */ +static void js_gpio_read_analog(struct mjs* mjs) { + // get mV (ADC is configured for 12 bits and 2048 mV max) + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + uint16_t millivolts = + furi_hal_adc_read(manager_data->adc_handle, manager_data->adc_channel) / 2; + mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts)); +} - if(gpio->handle != NULL) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Analog mode already started"); - mjs_return(mjs, MJS_UNDEFINED); - return; +/** + * @brief Returns an object that manages a specified pin. + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * ``` + */ +static void js_gpio_get(struct mjs* mjs) { + mjs_val_t name_arg; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&name_arg)); + const char* name_string = mjs_get_string(mjs, &name_arg, NULL); + const GpioPinRecord* pin_record = NULL; + + // parse input argument to a pin pointer + if(name_string) { + pin_record = furi_hal_resources_pin_by_name(name_string); + } else if(mjs_is_number(name_arg)) { + int name_int = mjs_get_int(mjs, name_arg); + pin_record = furi_hal_resources_pin_by_number(name_int); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Must be either a string or a number"); } - gpio->handle = furi_hal_adc_acquire(); - furi_hal_adc_configure_ex( - gpio->handle, - scale, - FuriHalAdcClockSync64, - FuriHalAdcOversample64, - FuriHalAdcSamplingtime247_5); + if(!pin_record) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Pin not found on device"); + if(pin_record->debug) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Pin is used for debugging"); + + // return pin manager object + JsGpioInst* module = JS_GET_CONTEXT(mjs); + mjs_val_t manager = mjs_mk_object(mjs); + JsGpioPinInst* manager_data = malloc(sizeof(JsGpioPinInst)); + manager_data->pin = pin_record->pin; + manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0); + manager_data->adc_handle = module->adc_handle; + manager_data->adc_channel = pin_record->channel; + mjs_own(mjs, &manager); + mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data)); + mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init)); + mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write)); + mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read)); + mjs_set(mjs, manager, "read_analog", ~0, MJS_MK_FN(js_gpio_read_analog)); + mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt)); + mjs_return(mjs, manager); + + // remember pin + ManagedPinsArray_push_back(module->managed_pins, manager_data); } -static void js_gpio_stop_analog(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsGpioInst* gpio = mjs_get_ptr(mjs, obj_inst); - furi_assert(gpio); +static void* js_gpio_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + JsEventLoop* js_loop = js_module_get(modules, "event_loop"); + if(M_UNLIKELY(!js_loop)) return NULL; + FuriEventLoop* loop = js_event_loop_get_loop(js_loop); - if(gpio->handle == NULL) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Analog mode not started"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - furi_hal_adc_release(gpio->handle); - gpio->handle = NULL; -} + JsGpioInst* module = malloc(sizeof(JsGpioInst)); + ManagedPinsArray_init(module->managed_pins); + module->adc_handle = furi_hal_adc_acquire(); + module->loop = loop; + furi_hal_adc_configure(module->adc_handle); -static void* js_gpio_create(struct mjs* mjs, mjs_val_t* object) { - JsGpioInst* gpio = malloc(sizeof(JsGpioInst)); - gpio->handle = NULL; mjs_val_t gpio_obj = mjs_mk_object(mjs); - mjs_set(mjs, gpio_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, gpio)); - mjs_set(mjs, gpio_obj, "init", ~0, MJS_MK_FN(js_gpio_init)); - mjs_set(mjs, gpio_obj, "write", ~0, MJS_MK_FN(js_gpio_write)); - mjs_set(mjs, gpio_obj, "read", ~0, MJS_MK_FN(js_gpio_read)); - mjs_set(mjs, gpio_obj, "readAnalog", ~0, MJS_MK_FN(js_gpio_read_analog)); - mjs_set(mjs, gpio_obj, "startAnalog", ~0, MJS_MK_FN(js_gpio_start_analog)); - mjs_set(mjs, gpio_obj, "stopAnalog", ~0, MJS_MK_FN(js_gpio_stop_analog)); + mjs_set(mjs, gpio_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module)); + mjs_set(mjs, gpio_obj, "get", ~0, MJS_MK_FN(js_gpio_get)); *object = gpio_obj; - return (void*)gpio; + return (void*)module; } static void js_gpio_destroy(void* inst) { - if(inst != NULL) { - JsGpioInst* gpio = (JsGpioInst*)inst; - if(gpio->handle != NULL) { - furi_hal_adc_release(gpio->handle); - gpio->handle = NULL; + furi_assert(inst); + JsGpioInst* module = (JsGpioInst*)inst; + + // reset pins + ManagedPinsArray_it_t iterator; + for(ManagedPinsArray_it(iterator, module->managed_pins); !ManagedPinsArray_end_p(iterator); + ManagedPinsArray_next(iterator)) { + JsGpioPinInst* manager_data = *ManagedPinsArray_cref(iterator); + if(manager_data->had_interrupt) { + furi_hal_gpio_disable_int_callback(manager_data->pin); + furi_hal_gpio_remove_int_callback(manager_data->pin); } - free(gpio); - } - - // loop through all pins and reset them to analog mode - for(size_t i = 0; i < COUNT_OF(js_gpio_pins); i++) { - furi_hal_gpio_write(js_gpio_pins[i].pin, false); - furi_hal_gpio_init(js_gpio_pins[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); + furi_semaphore_free(manager_data->interrupt_semaphore); + free(manager_data->interrupt_contract); + free(manager_data); } - expansion_enable(furi_record_open(RECORD_EXPANSION)); - furi_record_close(RECORD_EXPANSION); + // free buffers + furi_hal_adc_release(module->adc_handle); + ManagedPinsArray_clear(module->managed_pins); + free(module); } static const JsModuleDescriptor js_gpio_desc = { "gpio", js_gpio_create, js_gpio_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_gui/dialog.c b/applications/system/js_app/modules/js_gui/dialog.c new file mode 100644 index 0000000000..31eee237f6 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/dialog.c @@ -0,0 +1,129 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define QUEUE_LEN 2 + +typedef struct { + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsDialogCtx; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsDialogCtx* context) { + UNUSED(context); + DialogExResult result; + furi_check(furi_message_queue_get(queue, &result, 0) == FuriStatusOk); + const char* string; + if(result == DialogExResultLeft) { + string = "left"; + } else if(result == DialogExResultCenter) { + string = "center"; + } else if(result == DialogExResultRight) { + string = "right"; + } else { + furi_crash(); + } + return mjs_mk_string(mjs, string, ~0, false); +} + +static void input_callback(DialogExResult result, JsDialogCtx* context) { + furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk); +} + +static bool + header_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_header(dialog, value.string, 64, 0, AlignCenter, AlignTop); + return true; +} + +static bool + text_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_text(dialog, value.string, 64, 32, AlignCenter, AlignCenter); + return true; +} + +static bool + left_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_left_button_text(dialog, value.string); + return true; +} +static bool + center_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_center_button_text(dialog, value.string); + return true; +} +static bool + right_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_right_button_text(dialog, value.string); + return true; +} + +static JsDialogCtx* ctx_make(struct mjs* mjs, DialogEx* dialog, mjs_val_t view_obj) { + JsDialogCtx* context = malloc(sizeof(JsDialogCtx)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(DialogExResult)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + }, + }; + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + dialog_ex_set_result_callback(dialog, (DialogExResultCallback)input_callback); + dialog_ex_set_context(dialog, context); + return context; +} + +static void ctx_destroy(DialogEx* input, JsDialogCtx* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)dialog_ex_alloc, + .free = (JsViewFree)dialog_ex_free, + .get_view = (JsViewGetView)dialog_ex_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 5, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "text", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)text_assign}, + (JsViewPropDescriptor){ + .name = "left", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)left_assign}, + (JsViewPropDescriptor){ + .name = "center", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)center_assign}, + (JsViewPropDescriptor){ + .name = "right", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)right_assign}, + }}; + +JS_GUI_VIEW_DEF(dialog, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/empty_screen.c b/applications/system/js_app/modules/js_gui/empty_screen.c new file mode 100644 index 0000000000..9684eabdc1 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/empty_screen.c @@ -0,0 +1,12 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)empty_screen_alloc, + .free = (JsViewFree)empty_screen_free, + .get_view = (JsViewGetView)empty_screen_get_view, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(empty_screen, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c new file mode 100644 index 0000000000..8ac3055d5d --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -0,0 +1,348 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "./js_gui.h" +#include +#include +#include +#include "../js_event_loop/js_event_loop.h" +#include + +#define EVENT_QUEUE_SIZE 16 + +typedef struct { + uint32_t next_view_id; + FuriEventLoop* loop; + Gui* gui; + ViewDispatcher* dispatcher; + // event stuff + JsEventLoopContract custom_contract; + FuriMessageQueue* custom; + JsEventLoopContract navigation_contract; + FuriSemaphore* + navigation; // FIXME: (-nofl) convert into callback once FuriEventLoop starts supporting this +} JsGui; + +// Useful for factories +static JsGui* js_gui; + +typedef struct { + uint32_t id; + const JsViewDescriptor* descriptor; + void* specific_view; + void* custom_data; +} JsGuiViewData; + +/** + * @brief Transformer for custom events + */ +static mjs_val_t + js_gui_vd_custom_transformer(struct mjs* mjs, FuriEventLoopObject* object, void* context) { + UNUSED(context); + furi_check(object); + FuriMessageQueue* queue = object; + uint32_t event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + return mjs_mk_number(mjs, (double)event); +} + +/** + * @brief ViewDispatcher custom event callback + */ +static bool js_gui_vd_custom_callback(void* context, uint32_t event) { + furi_check(context); + JsGui* module = context; + furi_check(furi_message_queue_put(module->custom, &event, 0) == FuriStatusOk); + return true; +} + +/** + * @brief ViewDispatcher navigation event callback + */ +static bool js_gui_vd_nav_callback(void* context) { + furi_check(context); + JsGui* module = context; + furi_semaphore_release(module->navigation); + return true; +} + +/** + * @brief `viewDispatcher.sendCustom` + */ +static void js_gui_vd_send_custom(struct mjs* mjs) { + int32_t event; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&event)); + + JsGui* module = JS_GET_CONTEXT(mjs); + view_dispatcher_send_custom_event(module->dispatcher, (uint32_t)event); +} + +/** + * @brief `viewDispatcher.sendTo` + */ +static void js_gui_vd_send_to(struct mjs* mjs) { + enum { + SendDirToFront, + SendDirToBack, + } send_direction; + JS_ENUM_MAP(send_direction, {"front", SendDirToFront}, {"back", SendDirToBack}); + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ENUM(send_direction, "SendDirection")); + + JsGui* module = JS_GET_CONTEXT(mjs); + if(send_direction == SendDirToBack) { + view_dispatcher_send_to_back(module->dispatcher); + } else { + view_dispatcher_send_to_front(module->dispatcher); + } +} + +/** + * @brief `viewDispatcher.switchTo` + */ +static void js_gui_vd_switch_to(struct mjs* mjs) { + mjs_val_t view; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view)); + JsGuiViewData* view_data = JS_GET_INST(mjs, view); + JsGui* module = JS_GET_CONTEXT(mjs); + view_dispatcher_switch_to_view(module->dispatcher, (uint32_t)view_data->id); +} + +static void* js_gui_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + // get event loop + JsEventLoop* js_loop = js_module_get(modules, "event_loop"); + if(M_UNLIKELY(!js_loop)) return NULL; + FuriEventLoop* loop = js_event_loop_get_loop(js_loop); + + // create C object + JsGui* module = malloc(sizeof(JsGui)); + module->loop = loop; + module->gui = furi_record_open(RECORD_GUI); + module->dispatcher = view_dispatcher_alloc_ex(loop); + module->custom = furi_message_queue_alloc(EVENT_QUEUE_SIZE, sizeof(uint32_t)); + module->navigation = furi_semaphore_alloc(EVENT_QUEUE_SIZE, 0); + view_dispatcher_attach_to_gui(module->dispatcher, module->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_send_to_front(module->dispatcher); + + // subscribe to events and create contracts + view_dispatcher_set_event_callback_context(module->dispatcher, module); + view_dispatcher_set_custom_event_callback(module->dispatcher, js_gui_vd_custom_callback); + view_dispatcher_set_navigation_event_callback(module->dispatcher, js_gui_vd_nav_callback); + module->custom_contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object = module->custom, + .object_type = JsEventLoopObjectTypeQueue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = js_gui_vd_custom_transformer, + }, + }; + module->navigation_contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object = module->navigation, + .object_type = JsEventLoopObjectTypeSemaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + }, + }; + + // create viewDispatcher object + mjs_val_t view_dispatcher = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, view_dispatcher) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, module)); + JS_FIELD("sendCustom", MJS_MK_FN(js_gui_vd_send_custom)); + JS_FIELD("sendTo", MJS_MK_FN(js_gui_vd_send_to)); + JS_FIELD("switchTo", MJS_MK_FN(js_gui_vd_switch_to)); + JS_FIELD("custom", mjs_mk_foreign(mjs, &module->custom_contract)); + JS_FIELD("navigation", mjs_mk_foreign(mjs, &module->navigation_contract)); + } + + // create API object + mjs_val_t api = mjs_mk_object(mjs); + mjs_set(mjs, api, "viewDispatcher", ~0, view_dispatcher); + + *object = api; + js_gui = module; + return module; +} + +static void js_gui_destroy(void* inst) { + furi_assert(inst); + JsGui* module = inst; + + view_dispatcher_free(module->dispatcher); + furi_event_loop_maybe_unsubscribe(module->loop, module->custom); + furi_event_loop_maybe_unsubscribe(module->loop, module->navigation); + furi_message_queue_free(module->custom); + furi_semaphore_free(module->navigation); + + furi_record_close(RECORD_GUI); + free(module); + js_gui = NULL; +} + +/** + * @brief Assigns a `View` property. Not available from JS. + */ +static bool + js_gui_view_assign(struct mjs* mjs, const char* name, mjs_val_t value, JsGuiViewData* data) { + const JsViewDescriptor* descriptor = data->descriptor; + for(size_t i = 0; i < descriptor->prop_cnt; i++) { + JsViewPropDescriptor prop = descriptor->props[i]; + if(strcmp(prop.name, name) != 0) continue; + + // convert JS value to C + JsViewPropValue c_value; + const char* expected_type = NULL; + switch(prop.type) { + case JsViewPropTypeNumber: { + if(!mjs_is_number(value)) { + expected_type = "number"; + break; + } + c_value = (JsViewPropValue){.number = mjs_get_int32(mjs, value)}; + } break; + case JsViewPropTypeString: { + if(!mjs_is_string(value)) { + expected_type = "string"; + break; + } + c_value = (JsViewPropValue){.string = mjs_get_string(mjs, &value, NULL)}; + } break; + case JsViewPropTypeArr: { + if(!mjs_is_array(value)) { + expected_type = "array"; + break; + } + c_value = (JsViewPropValue){.array = value}; + } break; + } + + if(expected_type) { + mjs_prepend_errorf( + mjs, MJS_BAD_ARGS_ERROR, "view prop \"%s\" requires %s value", name, expected_type); + return false; + } else { + return prop.assign(mjs, data->specific_view, c_value, data->custom_data); + } + } + + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "view has no prop named \"%s\"", name); + return false; +} + +/** + * @brief `View.set` + */ +static void js_gui_view_set(struct mjs* mjs) { + const char* name; + mjs_val_t value; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name), JS_ARG_ANY(&value)); + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + bool success = js_gui_view_assign(mjs, name, value, data); + UNUSED(success); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief `View` destructor + */ +static void js_gui_view_destructor(struct mjs* mjs, mjs_val_t obj) { + JsGuiViewData* data = JS_GET_INST(mjs, obj); + view_dispatcher_remove_view(js_gui->dispatcher, data->id); + if(data->descriptor->custom_destroy) + data->descriptor->custom_destroy(data->specific_view, data->custom_data, js_gui->loop); + data->descriptor->free(data->specific_view); + free(data); +} + +/** + * @brief Creates a `View` object from a descriptor. Not available from JS. + */ +static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descriptor) { + void* specific_view = descriptor->alloc(); + View* view = descriptor->get_view(specific_view); + uint32_t view_id = js_gui->next_view_id++; + view_dispatcher_add_view(js_gui->dispatcher, view_id, view); + + // generic view API + mjs_val_t view_obj = mjs_mk_object(mjs); + mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set)); + + // object data + JsGuiViewData* data = malloc(sizeof(JsGuiViewData)); + *data = (JsGuiViewData){ + .descriptor = descriptor, + .id = view_id, + .specific_view = specific_view, + .custom_data = + descriptor->custom_make ? descriptor->custom_make(mjs, specific_view, view_obj) : NULL, + }; + mjs_set(mjs, view_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, data)); + mjs_set(mjs, view_obj, MJS_DESTRUCTOR_PROP_NAME, ~0, MJS_MK_FN(js_gui_view_destructor)); + + return view_obj; +} + +/** + * @brief `ViewFactory.make` + */ +static void js_gui_vf_make(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); + mjs_return(mjs, js_gui_make_view(mjs, descriptor)); +} + +/** + * @brief `ViewFactory.makeWith` + */ +static void js_gui_vf_make_with(struct mjs* mjs) { + mjs_val_t props; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&props)); + const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); + + // make the object like normal + mjs_val_t view_obj = js_gui_make_view(mjs, descriptor); + JsGuiViewData* data = JS_GET_INST(mjs, view_obj); + + // assign properties one by one + mjs_val_t key, iter = MJS_UNDEFINED; + while((key = mjs_next(mjs, props, &iter)) != MJS_UNDEFINED) { + furi_check(mjs_is_string(key)); + const char* name = mjs_get_string(mjs, &key, NULL); + mjs_val_t value = mjs_get(mjs, props, name, ~0); + + if(!js_gui_view_assign(mjs, name, value, data)) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + } + + mjs_return(mjs, view_obj); +} + +mjs_val_t js_gui_make_view_factory(struct mjs* mjs, const JsViewDescriptor* view_descriptor) { + mjs_val_t factory = mjs_mk_object(mjs); + mjs_set(mjs, factory, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, (void*)view_descriptor)); + mjs_set(mjs, factory, "make", ~0, MJS_MK_FN(js_gui_vf_make)); + mjs_set(mjs, factory, "makeWith", ~0, MJS_MK_FN(js_gui_vf_make_with)); + return factory; +} + +extern const ElfApiInterface js_gui_hashtable_api_interface; + +static const JsModuleDescriptor js_gui_desc = { + "gui", + js_gui_create, + js_gui_destroy, + &js_gui_hashtable_api_interface, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_gui_desc, +}; + +const FlipperAppPluginDescriptor* js_gui_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h new file mode 100644 index 0000000000..02198ca4f3 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -0,0 +1,116 @@ +#include "../../js_modules.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + JsViewPropTypeString, + JsViewPropTypeNumber, + JsViewPropTypeArr, +} JsViewPropType; + +typedef union { + const char* string; + int32_t number; + mjs_val_t array; +} JsViewPropValue; + +/** + * @brief Assigns a value to a view property + * + * The name and the type are implicit and defined in the property descriptor + */ +typedef bool ( + *JsViewPropAssign)(struct mjs* mjs, void* specific_view, JsViewPropValue value, void* context); + +/** @brief Property descriptor */ +typedef struct { + const char* name; // get_view -> [custom_make (if set)] -> props[i].assign -> [custom_destroy (if_set)] -> free +// \_______________ creation ________________/ \___ usage ___/ \_________ destruction _________/ + +/** + * @brief Creates a JS `ViewFactory` object + * + * This function is intended to be used by individual view adapter modules that + * wish to create a unified JS API interface in a declarative way. Usually this + * is done via the `JS_GUI_VIEW_DEF` macro which hides all the boilerplate. + * + * The `ViewFactory` object exposes two methods, `make` and `makeWith`, each + * returning a `View` object. These objects fully comply with the expectations + * of the `ViewDispatcher`, TS type definitions and the proposed Flipper JS + * coding style. + */ +mjs_val_t js_gui_make_view_factory(struct mjs* mjs, const JsViewDescriptor* view_descriptor); + +/** + * @brief Defines a module implementing `View` glue code + */ +#define JS_GUI_VIEW_DEF(name, descriptor) \ + static void* view_mod_ctor(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { \ + UNUSED(modules); \ + *object = js_gui_make_view_factory(mjs, descriptor); \ + return NULL; \ + } \ + static const JsModuleDescriptor js_mod_desc = { \ + "gui__" #name, \ + view_mod_ctor, \ + NULL, \ + NULL, \ + }; \ + static const FlipperAppPluginDescriptor plugin_descriptor = { \ + .appid = PLUGIN_APP_ID, \ + .ep_api_version = PLUGIN_API_VERSION, \ + .entry_point = &js_mod_desc, \ + }; \ + const FlipperAppPluginDescriptor* js_view_##name##_ep(void) { \ + return &plugin_descriptor; \ + } + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp b/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp new file mode 100644 index 0000000000..2be9cb3b21 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp @@ -0,0 +1,16 @@ +#include +#include + +#include "js_gui_api_table_i.h" + +static_assert(!has_hash_collisions(js_gui_api_table), "Detected API method hash collision!"); + +extern "C" constexpr HashtableApiInterface js_gui_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + js_gui_api_table.cbegin(), + js_gui_api_table.cend(), +}; diff --git a/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h new file mode 100644 index 0000000000..852b3d1071 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h @@ -0,0 +1,4 @@ +#include "js_gui.h" + +static constexpr auto js_gui_api_table = sort(create_array_t( + API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)))); diff --git a/applications/system/js_app/modules/js_gui/loading.c b/applications/system/js_app/modules/js_gui/loading.c new file mode 100644 index 0000000000..e291824a07 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/loading.c @@ -0,0 +1,12 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)loading_alloc, + .free = (JsViewFree)loading_free, + .get_view = (JsViewGetView)loading_get_view, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(loading, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/submenu.c b/applications/system/js_app/modules/js_gui/submenu.c new file mode 100644 index 0000000000..aecd413be4 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/submenu.c @@ -0,0 +1,87 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define QUEUE_LEN 2 + +typedef struct { + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsSubmenuCtx; + +static mjs_val_t choose_transformer(struct mjs* mjs, FuriMessageQueue* queue, void* context) { + UNUSED(context); + uint32_t index; + furi_check(furi_message_queue_get(queue, &index, 0) == FuriStatusOk); + return mjs_mk_number(mjs, (double)index); +} + +void choose_callback(void* context, uint32_t index) { + JsSubmenuCtx* ctx = context; + furi_check(furi_message_queue_put(ctx->queue, &index, 0) == FuriStatusOk); +} + +static bool + header_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { + UNUSED(mjs); + UNUSED(context); + submenu_set_header(submenu, value.string); + return true; +} + +static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { + UNUSED(mjs); + submenu_reset(submenu); + size_t len = mjs_array_length(mjs, value.array); + for(size_t i = 0; i < len; i++) { + mjs_val_t item = mjs_array_get(mjs, value.array, i); + if(!mjs_is_string(item)) return false; + submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context); + } + return true; +} + +static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) { + UNUSED(input); + JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(uint32_t)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)choose_transformer, + }, + }; + mjs_set(mjs, view_obj, "chosen", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(Submenu* input, JsSubmenuCtx* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)submenu_alloc, + .free = (JsViewFree)submenu_free, + .get_view = (JsViewGetView)submenu_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 2, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "items", + .type = JsViewPropTypeArr, + .assign = (JsViewPropAssign)items_assign}, + }}; +JS_GUI_VIEW_DEF(submenu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/text_box.c b/applications/system/js_app/modules/js_gui/text_box.c new file mode 100644 index 0000000000..4e6c8247ca --- /dev/null +++ b/applications/system/js_app/modules/js_gui/text_box.c @@ -0,0 +1,78 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static bool + text_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, FuriString* context) { + UNUSED(mjs); + furi_string_set(context, value.string); + text_box_set_text(text_box, furi_string_get_cstr(context)); + return true; +} + +static bool font_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, void* context) { + UNUSED(context); + TextBoxFont font; + if(strcasecmp(value.string, "hex") == 0) { + font = TextBoxFontHex; + } else if(strcasecmp(value.string, "text") == 0) { + font = TextBoxFontText; + } else { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "must be one of: \"text\", \"hex\""); + return false; + } + text_box_set_font(text_box, font); + return true; +} + +static bool + focus_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, void* context) { + UNUSED(context); + TextBoxFocus focus; + if(strcasecmp(value.string, "start") == 0) { + focus = TextBoxFocusStart; + } else if(strcasecmp(value.string, "end") == 0) { + focus = TextBoxFocusEnd; + } else { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "must be one of: \"start\", \"end\""); + return false; + } + text_box_set_focus(text_box, focus); + return true; +} + +FuriString* ctx_make(struct mjs* mjs, TextBox* specific_view, mjs_val_t view_obj) { + UNUSED(mjs); + UNUSED(specific_view); + UNUSED(view_obj); + return furi_string_alloc(); +} + +void ctx_destroy(TextBox* specific_view, FuriString* context, FuriEventLoop* loop) { + UNUSED(specific_view); + UNUSED(loop); + furi_string_free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)text_box_alloc, + .free = (JsViewFree)text_box_free, + .get_view = (JsViewGetView)text_box_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "text", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)text_assign}, + (JsViewPropDescriptor){ + .name = "font", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)font_assign}, + (JsViewPropDescriptor){ + .name = "focus", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)focus_assign}, + }}; +JS_GUI_VIEW_DEF(text_box, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/text_input.c b/applications/system/js_app/modules/js_gui/text_input.c new file mode 100644 index 0000000000..575029f8e6 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/text_input.c @@ -0,0 +1,120 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define DEFAULT_BUF_SZ 33 + +typedef struct { + char* buffer; + size_t buffer_size; + FuriString* header; + FuriSemaphore* input_semaphore; + JsEventLoopContract contract; +} JsKbdContext; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriSemaphore* semaphore, JsKbdContext* context) { + furi_check(furi_semaphore_acquire(semaphore, 0) == FuriStatusOk); + return mjs_mk_string(mjs, context->buffer, ~0, true); +} + +static void input_callback(JsKbdContext* context) { + furi_semaphore_release(context->input_semaphore); +} + +static bool + header_assign(struct mjs* mjs, TextInput* input, JsViewPropValue value, JsKbdContext* context) { + UNUSED(mjs); + furi_string_set(context->header, value.string); + text_input_set_header_text(input, furi_string_get_cstr(context->header)); + return true; +} + +static bool min_len_assign( + struct mjs* mjs, + TextInput* input, + JsViewPropValue value, + JsKbdContext* context) { + UNUSED(mjs); + UNUSED(context); + text_input_set_minimum_length(input, (size_t)value.number); + return true; +} + +static bool max_len_assign( + struct mjs* mjs, + TextInput* input, + JsViewPropValue value, + JsKbdContext* context) { + UNUSED(mjs); + context->buffer_size = (size_t)(value.number + 1); + context->buffer = realloc(context->buffer, context->buffer_size); //-V701 + text_input_set_result_callback( + input, + (TextInputCallback)input_callback, + context, + context->buffer, + context->buffer_size, + true); + return true; +} + +static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_obj) { + UNUSED(input); + JsKbdContext* context = malloc(sizeof(JsKbdContext)); + *context = (JsKbdContext){ + .buffer_size = DEFAULT_BUF_SZ, + .buffer = malloc(DEFAULT_BUF_SZ), + .header = furi_string_alloc(), + .input_semaphore = furi_semaphore_alloc(1, 0), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = context->input_semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + UNUSED(mjs); + UNUSED(view_obj); + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(TextInput* input, JsKbdContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_semaphore); + furi_semaphore_free(context->input_semaphore); + furi_string_free(context->header); + free(context->buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)text_input_alloc, + .free = (JsViewFree)text_input_free, + .get_view = (JsViewGetView)text_input_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "minLength", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)min_len_assign}, + (JsViewPropDescriptor){ + .name = "maxLength", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)max_len_assign}, + }}; + +JS_GUI_VIEW_DEF(text_input, &view_descriptor); diff --git a/applications/system/js_app/modules/js_keyboard.c b/applications/system/js_app/modules/js_keyboard.c deleted file mode 100644 index 53f48d01de..0000000000 --- a/applications/system/js_app/modules/js_keyboard.c +++ /dev/null @@ -1,206 +0,0 @@ -#include "../js_modules.h" -#include -#include -#include -#include - -#define membersof(x) (sizeof(x) / sizeof(x[0])) - -typedef struct { - TextInput* text_input; - ByteInput* byte_input; - ViewHolder* view_holder; - FuriApiLock lock; - char* header; - bool accepted; -} JsKeyboardInst; - -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); -} - -static JsKeyboardInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsKeyboardInst* keyboard = mjs_get_ptr(mjs, obj_inst); - furi_assert(keyboard); - return keyboard; -} - -static void keyboard_callback(void* context) { - JsKeyboardInst* keyboard = (JsKeyboardInst*)context; - keyboard->accepted = true; - api_lock_unlock(keyboard->lock); -} - -static void keyboard_exit(void* context) { - JsKeyboardInst* keyboard = (JsKeyboardInst*)context; - keyboard->accepted = false; - api_lock_unlock(keyboard->lock); -} - -static void js_keyboard_set_header(struct mjs* mjs) { - JsKeyboardInst* keyboard = get_this_ctx(mjs); - - mjs_val_t header_arg = mjs_arg(mjs, 0); - const char* header = mjs_get_string(mjs, &header_arg, NULL); - if(!header) { - ret_bad_args(mjs, "Header must be a string"); - return; - } - - if(keyboard->header) { - free(keyboard->header); - } - keyboard->header = strdup(header); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_keyboard_text(struct mjs* mjs) { - JsKeyboardInst* keyboard = get_this_ctx(mjs); - - mjs_val_t input_length_arg = mjs_arg(mjs, 0); - if(!mjs_is_number(input_length_arg)) { - ret_bad_args(mjs, "Input length must be a number"); - return; - } - int32_t input_length = mjs_get_int32(mjs, input_length_arg); - char* buffer = malloc(input_length); - - mjs_val_t default_text_arg = mjs_arg(mjs, 1); - const char* default_text = mjs_get_string(mjs, &default_text_arg, NULL); - bool clear_default = false; - if(default_text) { - strlcpy(buffer, default_text, input_length); - mjs_val_t bool_obj = mjs_arg(mjs, 2); - clear_default = mjs_get_bool(mjs, bool_obj); - } - - if(keyboard->header) { - text_input_set_header_text(keyboard->text_input, keyboard->header); - } - text_input_set_result_callback( - keyboard->text_input, keyboard_callback, keyboard, buffer, input_length, clear_default); - - text_input_set_minimum_length(keyboard->text_input, 0); - - keyboard->lock = api_lock_alloc_locked(); - Gui* gui = furi_record_open(RECORD_GUI); - keyboard->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(keyboard->view_holder, gui); - view_holder_set_back_callback(keyboard->view_holder, keyboard_exit, keyboard); - - view_holder_set_view(keyboard->view_holder, text_input_get_view(keyboard->text_input)); - api_lock_wait_unlock(keyboard->lock); - - view_holder_set_view(keyboard->view_holder, NULL); - view_holder_free(keyboard->view_holder); - - furi_record_close(RECORD_GUI); - api_lock_free(keyboard->lock); - - text_input_reset(keyboard->text_input); - if(keyboard->header) { - free(keyboard->header); - keyboard->header = NULL; - } - if(keyboard->accepted) { - mjs_return(mjs, mjs_mk_string(mjs, buffer, ~0, true)); - } else { - mjs_return(mjs, MJS_UNDEFINED); - } - free(buffer); -} - -static void js_keyboard_byte(struct mjs* mjs) { - JsKeyboardInst* keyboard = get_this_ctx(mjs); - - mjs_val_t input_length_arg = mjs_arg(mjs, 0); - if(!mjs_is_number(input_length_arg)) { - ret_bad_args(mjs, "Input length must be a number"); - return; - } - int32_t input_length = mjs_get_int32(mjs, input_length_arg); - uint8_t* buffer = malloc(input_length); - - mjs_val_t default_data_arg = mjs_arg(mjs, 1); - if(mjs_is_typed_array(default_data_arg)) { - if(mjs_is_data_view(default_data_arg)) { - default_data_arg = mjs_dataview_get_buf(mjs, default_data_arg); - } - size_t default_data_len = 0; - char* default_data = mjs_array_buf_get_ptr(mjs, default_data_arg, &default_data_len); - memcpy(buffer, (uint8_t*)default_data, MIN((size_t)input_length, default_data_len)); - } - - if(keyboard->header) { - byte_input_set_header_text(keyboard->byte_input, keyboard->header); - } - byte_input_set_result_callback( - keyboard->byte_input, keyboard_callback, NULL, keyboard, buffer, input_length); - - keyboard->lock = api_lock_alloc_locked(); - Gui* gui = furi_record_open(RECORD_GUI); - keyboard->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(keyboard->view_holder, gui); - view_holder_set_back_callback(keyboard->view_holder, keyboard_exit, keyboard); - - view_holder_set_view(keyboard->view_holder, byte_input_get_view(keyboard->byte_input)); - api_lock_wait_unlock(keyboard->lock); - - view_holder_set_view(keyboard->view_holder, NULL); - view_holder_free(keyboard->view_holder); - - furi_record_close(RECORD_GUI); - api_lock_free(keyboard->lock); - - if(keyboard->header) { - free(keyboard->header); - keyboard->header = NULL; - } - byte_input_set_result_callback(keyboard->byte_input, NULL, NULL, NULL, NULL, 0); - byte_input_set_header_text(keyboard->byte_input, ""); - if(keyboard->accepted) { - mjs_return(mjs, mjs_mk_array_buf(mjs, (char*)buffer, input_length)); - } else { - mjs_return(mjs, MJS_UNDEFINED); - } - free(buffer); -} - -static void* js_keyboard_create(struct mjs* mjs, mjs_val_t* object) { - JsKeyboardInst* keyboard = malloc(sizeof(JsKeyboardInst)); - mjs_val_t keyboard_obj = mjs_mk_object(mjs); - mjs_set(mjs, keyboard_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, keyboard)); - mjs_set(mjs, keyboard_obj, "setHeader", ~0, MJS_MK_FN(js_keyboard_set_header)); - mjs_set(mjs, keyboard_obj, "text", ~0, MJS_MK_FN(js_keyboard_text)); - mjs_set(mjs, keyboard_obj, "byte", ~0, MJS_MK_FN(js_keyboard_byte)); - keyboard->byte_input = byte_input_alloc(); - keyboard->text_input = text_input_alloc(); - *object = keyboard_obj; - return keyboard; -} - -static void js_keyboard_destroy(void* inst) { - JsKeyboardInst* keyboard = inst; - byte_input_free(keyboard->byte_input); - text_input_free(keyboard->text_input); - free(keyboard); -} - -static const JsModuleDescriptor js_keyboard_desc = { - "keyboard", - js_keyboard_create, - js_keyboard_destroy, -}; - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_keyboard_desc, -}; - -const FlipperAppPluginDescriptor* js_keyboard_ep(void) { - return &plugin_descriptor; -} diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index d8812e61bb..7d54cf9b9f 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -305,7 +305,8 @@ void js_math_trunc(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); } -static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_math_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); mjs_val_t math_obj = mjs_mk_object(mjs); mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); @@ -342,6 +343,7 @@ static const JsModuleDescriptor js_math_desc = { "math", js_math_create, NULL, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_notification.c b/applications/system/js_app/modules/js_notification.c index 2f57c45d1b..994283a098 100644 --- a/applications/system/js_app/modules/js_notification.c +++ b/applications/system/js_app/modules/js_notification.c @@ -75,7 +75,8 @@ static void js_notify_blink(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_notification_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); mjs_val_t notify_obj = mjs_mk_object(mjs); mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification)); @@ -96,6 +97,7 @@ static const JsModuleDescriptor js_notification_desc = { "notification", js_notification_create, js_notification_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index 293798a12b..b1e578fbc7 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -658,7 +658,8 @@ static void js_serial_expect(struct mjs* mjs) { } } -static void* js_serial_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); js_serial->mjs = mjs; mjs_val_t serial_obj = mjs_mk_object(mjs); @@ -686,6 +687,7 @@ static const JsModuleDescriptor js_serial_desc = { "serial", js_serial_create, js_serial_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index d6ff9cf4e2..1d4053a5f7 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -1,262 +1,375 @@ -#include "../js_modules.h" -#include - -typedef struct { - Storage* api; -} JsStorageInst; - -static JsStorageInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsStorageInst* storage = mjs_get_ptr(mjs, obj_inst); - furi_assert(storage); - return storage; -} +#include "../js_modules.h" // IWYU pragma: keep +#include + +// ---=== file ops ===--- -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); +static void js_storage_file_close(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_close(file))); } -static void ret_int_err(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); +static void js_storage_file_is_open(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_is_open(file))); } -static bool check_arg_count(struct mjs* mjs, size_t count) { - size_t num_args = mjs_nargs(mjs); - if(num_args != count) { - ret_bad_args(mjs, "Wrong argument count"); - return false; +static void js_storage_file_read(struct mjs* mjs) { + enum { + ReadModeAscii, + ReadModeBinary, + } read_mode; + JS_ENUM_MAP(read_mode, {"ascii", ReadModeAscii}, {"binary", ReadModeBinary}); + int32_t length; + JS_FETCH_ARGS_OR_RETURN( + mjs, JS_EXACTLY, JS_ARG_ENUM(read_mode, "ReadMode"), JS_ARG_INT32(&length)); + File* file = JS_GET_CONTEXT(mjs); + char buffer[length]; + size_t actually_read = storage_file_read(file, buffer, length); + if(read_mode == ReadModeAscii) { + mjs_return(mjs, mjs_mk_string(mjs, buffer, actually_read, true)); + } else if(read_mode == ReadModeBinary) { + mjs_return(mjs, mjs_mk_array_buf(mjs, buffer, actually_read)); } - return true; } -static bool get_path_arg(struct mjs* mjs, const char** path, size_t index) { - mjs_val_t path_obj = mjs_arg(mjs, index); - if(!mjs_is_string(path_obj)) { - ret_bad_args(mjs, "Path must be a string"); - return false; - } - size_t path_len = 0; - *path = mjs_get_string(mjs, &path_obj, &path_len); - if((path_len == 0) || (*path == NULL)) { - ret_bad_args(mjs, "Bad path argument"); - return false; +static void js_storage_file_write(struct mjs* mjs) { + mjs_val_t data; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&data)); + const void* buf; + size_t len; + if(mjs_is_string(data)) { + buf = mjs_get_string(mjs, &data, &len); + } else if(mjs_is_array_buf(data)) { + buf = mjs_array_buf_get_ptr(mjs, data, &len); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: expected string or ArrayBuffer"); } - return true; + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_write(file, buf, len))); } -static void js_storage_read(struct mjs* mjs) { - JsStorageInst* storage = get_this_ctx(mjs); +static void js_storage_file_seek_relative(struct mjs* mjs) { + int32_t offset; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, false))); +} - const char* path; - if(!get_path_arg(mjs, &path, 0)) return; +static void js_storage_file_seek_absolute(struct mjs* mjs) { + int32_t offset; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, true))); +} - File* file = storage_file_alloc(storage->api); - do { - if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { - ret_int_err(mjs, storage_file_get_error_desc(file)); - break; - } +static void js_storage_file_tell(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_tell(file))); +} - uint64_t size = storage_file_size(file); - mjs_val_t size_arg = mjs_arg(mjs, 1); - if(mjs_is_number(size_arg)) { - size = mjs_get_int32(mjs, size_arg); - } +static void js_storage_file_truncate(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_truncate(file))); +} - mjs_val_t seek_arg = mjs_arg(mjs, 2); - if(mjs_is_number(seek_arg)) { - storage_file_seek(file, mjs_get_int32(mjs, seek_arg), true); - size = MIN(size, storage_file_size(file) - storage_file_tell(file)); - } +static void js_storage_file_size(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_size(file))); +} - if(size > memmgr_heap_get_max_free_block()) { - ret_int_err(mjs, "Read size too large"); - break; - } +static void js_storage_file_eof(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_eof(file))); +} - uint8_t* data = malloc(size); - size_t read = storage_file_read(file, data, size); - if(read == size) { - mjs_return(mjs, mjs_mk_array_buf(mjs, (char*)data, size)); - } else { - ret_int_err(mjs, "File read failed"); - } - free(data); - } while(0); - storage_file_free(file); +static void js_storage_file_copy_to(struct mjs* mjs) { + File* source = JS_GET_CONTEXT(mjs); + mjs_val_t dest_obj; + int32_t bytes; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&dest_obj), JS_ARG_INT32(&bytes)); + File* destination = JS_GET_INST(mjs, dest_obj); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_copy_to_file(source, destination, bytes))); } -static void js_storage_write(struct mjs* mjs) { - JsStorageInst* storage = get_this_ctx(mjs); +// ---=== top-level file ops ===--- - const char* path; - if(!get_path_arg(mjs, &path, 0)) return; +// common destructor for file and dir objects +static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { + File* file = JS_GET_INST(mjs, obj); + storage_file_free(file); +} - mjs_val_t data_arg = mjs_arg(mjs, 1); - if(!mjs_is_typed_array(data_arg) && !mjs_is_string(data_arg)) { - ret_bad_args(mjs, "Data must be string, arraybuf or dataview"); +static void js_storage_open_file(struct mjs* mjs) { + const char* path; + FS_AccessMode access_mode; + FS_OpenMode open_mode; + JS_ENUM_MAP(access_mode, {"r", FSAM_READ}, {"w", FSAM_WRITE}, {"rw", FSAM_READ_WRITE}); + JS_ENUM_MAP( + open_mode, + {"open_existing", FSOM_OPEN_EXISTING}, + {"open_always", FSOM_OPEN_ALWAYS}, + {"open_append", FSOM_OPEN_APPEND}, + {"create_new", FSOM_CREATE_NEW}, + {"create_always", FSOM_CREATE_ALWAYS}); + JS_FETCH_ARGS_OR_RETURN( + mjs, + JS_EXACTLY, + JS_ARG_STR(&path), + JS_ARG_ENUM(access_mode, "AccessMode"), + JS_ARG_ENUM(open_mode, "OpenMode")); + + Storage* storage = JS_GET_CONTEXT(mjs); + File* file = storage_file_alloc(storage); + if(!storage_file_open(file, path, access_mode, open_mode)) { + mjs_return(mjs, MJS_UNDEFINED); return; } - if(mjs_is_data_view(data_arg)) { - data_arg = mjs_dataview_get_buf(mjs, data_arg); - } - size_t data_len = 0; - const char* data = NULL; - if(mjs_is_string(data_arg)) { - data = mjs_get_string(mjs, &data_arg, &data_len); - } else if(mjs_is_typed_array(data_arg)) { - data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len); - } - - mjs_val_t seek_arg = mjs_arg(mjs, 2); - - File* file = storage_file_alloc(storage->api); - if(!storage_file_open( - file, - path, - FSAM_WRITE, - mjs_is_number(seek_arg) ? FSOM_OPEN_ALWAYS : FSOM_CREATE_ALWAYS)) { - ret_int_err(mjs, storage_file_get_error_desc(file)); - - } else { - if(mjs_is_number(seek_arg)) { - storage_file_seek(file, mjs_get_int32(mjs, seek_arg), true); - } - size_t write = storage_file_write(file, data, data_len); - mjs_return(mjs, mjs_mk_boolean(mjs, write == data_len)); + mjs_val_t file_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, file_obj) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, file)); + JS_FIELD(MJS_DESTRUCTOR_PROP_NAME, MJS_MK_FN(js_storage_file_destructor)); + JS_FIELD("close", MJS_MK_FN(js_storage_file_close)); + JS_FIELD("isOpen", MJS_MK_FN(js_storage_file_is_open)); + JS_FIELD("read", MJS_MK_FN(js_storage_file_read)); + JS_FIELD("write", MJS_MK_FN(js_storage_file_write)); + JS_FIELD("seekRelative", MJS_MK_FN(js_storage_file_seek_relative)); + JS_FIELD("seekAbsolute", MJS_MK_FN(js_storage_file_seek_absolute)); + JS_FIELD("tell", MJS_MK_FN(js_storage_file_tell)); + JS_FIELD("truncate", MJS_MK_FN(js_storage_file_truncate)); + JS_FIELD("size", MJS_MK_FN(js_storage_file_size)); + JS_FIELD("eof", MJS_MK_FN(js_storage_file_eof)); + JS_FIELD("copyTo", MJS_MK_FN(js_storage_file_copy_to)); } - storage_file_free(file); + mjs_return(mjs, file_obj); } -static void js_storage_append(struct mjs* mjs) { - JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; +static void js_storage_file_exists(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_exists(storage, path))); +} +// ---=== dir ops ===--- + +static void js_storage_read_directory(struct mjs* mjs) { const char* path; - if(!get_path_arg(mjs, &path, 0)) return; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); - mjs_val_t data_arg = mjs_arg(mjs, 1); - if(!mjs_is_typed_array(data_arg) && !mjs_is_string(data_arg)) { - ret_bad_args(mjs, "Data must be string, arraybuf or dataview"); + Storage* storage = JS_GET_CONTEXT(mjs); + File* dir = storage_file_alloc(storage); + if(!storage_dir_open(dir, path)) { + mjs_return(mjs, MJS_UNDEFINED); return; } - if(mjs_is_data_view(data_arg)) { - data_arg = mjs_dataview_get_buf(mjs, data_arg); - } - size_t data_len = 0; - const char* data = NULL; - if(mjs_is_string(data_arg)) { - data = mjs_get_string(mjs, &data_arg, &data_len); - } else if(mjs_is_typed_array(data_arg)) { - data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len); - } - File* file = storage_file_alloc(storage->api); - if(!storage_file_open(file, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { - ret_int_err(mjs, storage_file_get_error_desc(file)); - } else { - size_t write = storage_file_write(file, data, data_len); - mjs_return(mjs, mjs_mk_boolean(mjs, write == data_len)); + FileInfo file_info; + char name[128]; + FuriString* file_path = furi_string_alloc_set_str(path); + size_t path_size = furi_string_size(file_path); + uint32_t timestamp; + + mjs_val_t ret = mjs_mk_array(mjs); + while(storage_dir_read(dir, &file_info, name, sizeof(name))) { + furi_string_left(file_path, path_size); + path_append(file_path, name); + furi_check( + storage_common_timestamp(storage, furi_string_get_cstr(file_path), ×tamp) == + FSE_OK); + mjs_val_t obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, obj) { + JS_FIELD("path", mjs_mk_string(mjs, name, ~0, true)); + JS_FIELD("isDirectory", mjs_mk_boolean(mjs, file_info_is_dir(&file_info))); + JS_FIELD("size", mjs_mk_number(mjs, file_info.size)); + JS_FIELD("timestamp", mjs_mk_number(mjs, timestamp)); + } + mjs_array_push(mjs, ret, obj); } - storage_file_free(file); -} -static void js_storage_exists(struct mjs* mjs) { - JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; + storage_file_free(dir); + furi_string_free(file_path); + mjs_return(mjs, ret); +} +static void js_storage_directory_exists(struct mjs* mjs) { const char* path; - if(!get_path_arg(mjs, &path, 0)) return; - - mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage->api, path))); + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_dir_exists(storage, path))); } -static void js_storage_remove(struct mjs* mjs) { - JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; - +static void js_storage_make_directory(struct mjs* mjs) { const char* path; - if(!get_path_arg(mjs, &path, 0)) return; - - mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage->api, path))); + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage, path))); } -static void js_storage_copy(struct mjs* mjs) { - JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; - - const char* old_path; - if(!get_path_arg(mjs, &old_path, 0)) return; +// ---=== common ops ===--- - const char* new_path; - if(!get_path_arg(mjs, &new_path, 1)) return; +static void js_storage_file_or_dir_exists(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage, path))); +} - FS_Error error = storage_common_copy(storage->api, old_path, new_path); - if(error == FSE_OK) { +static void js_storage_stat(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + FileInfo file_info; + uint32_t timestamp; + if((storage_common_stat(storage, path, &file_info) | + storage_common_timestamp(storage, path, ×tamp)) != FSE_OK) { mjs_return(mjs, MJS_UNDEFINED); - } else { - ret_int_err(mjs, storage_error_get_desc(error)); + return; } + mjs_val_t ret = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, ret) { + JS_FIELD("path", mjs_mk_string(mjs, path, ~0, 1)); + JS_FIELD("isDirectory", mjs_mk_boolean(mjs, file_info_is_dir(&file_info))); + JS_FIELD("size", mjs_mk_number(mjs, file_info.size)); + JS_FIELD("accessTime", mjs_mk_number(mjs, timestamp)); + } + mjs_return(mjs, ret); } -static void js_storage_move(struct mjs* mjs) { - JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; +static void js_storage_remove(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage, path))); +} - const char* old_path; - if(!get_path_arg(mjs, &old_path, 0)) return; +static void js_storage_rmrf(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove_recursive(storage, path))); +} - const char* new_path; - if(!get_path_arg(mjs, &new_path, 1)) return; +static void js_storage_rename(struct mjs* mjs) { + const char *old, *new; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&old), JS_ARG_STR(&new)); + Storage* storage = JS_GET_CONTEXT(mjs); + FS_Error status = storage_common_rename(storage, old, new); + mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK)); +} - FS_Error error = storage_common_rename(storage->api, old_path, new_path); - if(error == FSE_OK) { +static void js_storage_copy(struct mjs* mjs) { + const char *source, *dest; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&source), JS_ARG_STR(&dest)); + Storage* storage = JS_GET_CONTEXT(mjs); + FS_Error status = storage_common_copy(storage, source, dest); + mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK || status == FSE_EXIST)); +} + +static void js_storage_fs_info(struct mjs* mjs) { + const char* fs; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fs)); + Storage* storage = JS_GET_CONTEXT(mjs); + uint64_t total_space, free_space; + if(storage_common_fs_info(storage, fs, &total_space, &free_space) != FSE_OK) { mjs_return(mjs, MJS_UNDEFINED); - } else { - ret_int_err(mjs, storage_error_get_desc(error)); + return; + } + mjs_val_t ret = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, ret) { + JS_FIELD("totalSpace", mjs_mk_number(mjs, total_space)); + JS_FIELD("freeSpace", mjs_mk_number(mjs, free_space)); } + mjs_return(mjs, ret); } -static void js_storage_mkdir(struct mjs* mjs) { - JsStorageInst* storage = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; +static void js_storage_next_available_filename(struct mjs* mjs) { + const char *dir_path, *file_name, *file_ext; + int32_t max_len; + JS_FETCH_ARGS_OR_RETURN( + mjs, + JS_EXACTLY, + JS_ARG_STR(&dir_path), + JS_ARG_STR(&file_name), + JS_ARG_STR(&file_ext), + JS_ARG_INT32(&max_len)); + Storage* storage = JS_GET_CONTEXT(mjs); + FuriString* next_name = furi_string_alloc(); + storage_get_next_filename(storage, dir_path, file_name, file_ext, next_name, max_len); + mjs_return(mjs, mjs_mk_string(mjs, furi_string_get_cstr(next_name), ~0, true)); + furi_string_free(next_name); +} - const char* path; - if(!get_path_arg(mjs, &path, 0)) return; +// ---=== path ops ===--- - mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage->api, path))); +static void js_storage_are_paths_equal(struct mjs* mjs) { + const char *path1, *path2; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path1), JS_ARG_STR(&path2)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_equivalent_path(storage, path1, path2))); } -static void* js_storage_create(struct mjs* mjs, mjs_val_t* object) { - JsStorageInst* storage = malloc(sizeof(JsStorageInst)); - mjs_val_t storage_obj = mjs_mk_object(mjs); - mjs_set(mjs, storage_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, storage)); - mjs_set(mjs, storage_obj, "read", ~0, MJS_MK_FN(js_storage_read)); - mjs_set(mjs, storage_obj, "write", ~0, MJS_MK_FN(js_storage_write)); - mjs_set(mjs, storage_obj, "append", ~0, MJS_MK_FN(js_storage_append)); - mjs_set(mjs, storage_obj, "exists", ~0, MJS_MK_FN(js_storage_exists)); - mjs_set(mjs, storage_obj, "remove", ~0, MJS_MK_FN(js_storage_remove)); - mjs_set(mjs, storage_obj, "copy", ~0, MJS_MK_FN(js_storage_copy)); - mjs_set(mjs, storage_obj, "move", ~0, MJS_MK_FN(js_storage_move)); - mjs_set(mjs, storage_obj, "mkdir", ~0, MJS_MK_FN(js_storage_mkdir)); - storage->api = furi_record_open(RECORD_STORAGE); - *object = storage_obj; - return storage; +static void js_storage_is_subpath_of(struct mjs* mjs) { + const char *parent, *child; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&parent), JS_ARG_STR(&child)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_is_subdir(storage, parent, child))); } -static void js_storage_destroy(void* inst) { - JsStorageInst* storage = inst; +// ---=== module ctor & dtor ===--- + +static void* js_storage_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + Storage* storage = furi_record_open(RECORD_STORAGE); + UNUSED(storage); + *object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, *object) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, storage)); + + // top-level file ops + JS_FIELD("openFile", MJS_MK_FN(js_storage_open_file)); + JS_FIELD("fileExists", MJS_MK_FN(js_storage_file_exists)); + + // dir ops + JS_FIELD("readDirectory", MJS_MK_FN(js_storage_read_directory)); + JS_FIELD("directoryExists", MJS_MK_FN(js_storage_directory_exists)); + JS_FIELD("makeDirectory", MJS_MK_FN(js_storage_make_directory)); + + // common ops + JS_FIELD("fileOrDirExists", MJS_MK_FN(js_storage_file_or_dir_exists)); + JS_FIELD("stat", MJS_MK_FN(js_storage_stat)); + JS_FIELD("remove", MJS_MK_FN(js_storage_remove)); + JS_FIELD("rmrf", MJS_MK_FN(js_storage_rmrf)); + JS_FIELD("rename", MJS_MK_FN(js_storage_rename)); + JS_FIELD("copy", MJS_MK_FN(js_storage_copy)); + JS_FIELD("fsInfo", MJS_MK_FN(js_storage_fs_info)); + JS_FIELD("nextAvailableFilename", MJS_MK_FN(js_storage_next_available_filename)); + + // path ops + JS_FIELD("arePathsEqual", MJS_MK_FN(js_storage_are_paths_equal)); + JS_FIELD("isSubpathOf", MJS_MK_FN(js_storage_is_subpath_of)); + } + return NULL; +} + +static void js_storage_destroy(void* data) { + UNUSED(data); furi_record_close(RECORD_STORAGE); - free(storage); } +// ---=== boilerplate ===--- + static const JsModuleDescriptor js_storage_desc = { "storage", js_storage_create, js_storage_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c deleted file mode 100644 index 5ab9bef77c..0000000000 --- a/applications/system/js_app/modules/js_submenu.c +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include -#include -#include -#include "../js_modules.h" - -typedef struct { - Submenu* submenu; - ViewHolder* view_holder; - FuriApiLock lock; - uint32_t result; - bool accepted; -} JsSubmenuInst; - -static JsSubmenuInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSubmenuInst* submenu = mjs_get_ptr(mjs, obj_inst); - furi_assert(submenu); - return submenu; -} - -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); -} - -static bool check_arg_count(struct mjs* mjs, size_t count) { - size_t num_args = mjs_nargs(mjs); - if(num_args != count) { - ret_bad_args(mjs, "Wrong argument count"); - return false; - } - return true; -} - -static void submenu_callback(void* context, uint32_t id) { - JsSubmenuInst* submenu = context; - submenu->result = id; - submenu->accepted = true; - api_lock_unlock(submenu->lock); -} - -static void submenu_exit(void* context) { - JsSubmenuInst* submenu = context; - submenu->result = 0; - submenu->accepted = false; - api_lock_unlock(submenu->lock); -} - -static void js_submenu_add_item(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; - - mjs_val_t label_arg = mjs_arg(mjs, 0); - const char* label = mjs_get_string(mjs, &label_arg, NULL); - if(!label) { - ret_bad_args(mjs, "Label must be a string"); - return; - } - - mjs_val_t id_arg = mjs_arg(mjs, 1); - if(!mjs_is_number(id_arg)) { - ret_bad_args(mjs, "Id must be a number"); - return; - } - int32_t id = mjs_get_int32(mjs, id_arg); - - submenu_add_item(submenu->submenu, label, id, submenu_callback, submenu); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_submenu_set_header(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; - - mjs_val_t header_arg = mjs_arg(mjs, 0); - const char* header = mjs_get_string(mjs, &header_arg, NULL); - if(!header) { - ret_bad_args(mjs, "Header must be a string"); - return; - } - - submenu_set_header(submenu->submenu, header); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_submenu_show(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - submenu->lock = api_lock_alloc_locked(); - Gui* gui = furi_record_open(RECORD_GUI); - submenu->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(submenu->view_holder, gui); - view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu); - - view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu)); - api_lock_wait_unlock(submenu->lock); - - view_holder_set_view(submenu->view_holder, NULL); - view_holder_free(submenu->view_holder); - furi_record_close(RECORD_GUI); - api_lock_free(submenu->lock); - - submenu_reset(submenu->submenu); - if(submenu->accepted) { - mjs_return(mjs, mjs_mk_number(mjs, submenu->result)); - } else { - mjs_return(mjs, MJS_UNDEFINED); - } -} - -static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) { - JsSubmenuInst* submenu = malloc(sizeof(JsSubmenuInst)); - mjs_val_t submenu_obj = mjs_mk_object(mjs); - mjs_set(mjs, submenu_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, submenu)); - mjs_set(mjs, submenu_obj, "addItem", ~0, MJS_MK_FN(js_submenu_add_item)); - mjs_set(mjs, submenu_obj, "setHeader", ~0, MJS_MK_FN(js_submenu_set_header)); - mjs_set(mjs, submenu_obj, "show", ~0, MJS_MK_FN(js_submenu_show)); - submenu->submenu = submenu_alloc(); - *object = submenu_obj; - return submenu; -} - -static void js_submenu_destroy(void* inst) { - JsSubmenuInst* submenu = inst; - submenu_free(submenu->submenu); - free(submenu); -} - -static const JsModuleDescriptor js_submenu_desc = { - "submenu", - js_submenu_create, - js_submenu_destroy, -}; - -static const FlipperAppPluginDescriptor submenu_plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_submenu_desc, -}; - -const FlipperAppPluginDescriptor* js_submenu_ep(void) { - return &submenu_plugin_descriptor; -} diff --git a/applications/system/js_app/modules/js_tests.c b/applications/system/js_app/modules/js_tests.c new file mode 100644 index 0000000000..f275640000 --- /dev/null +++ b/applications/system/js_app/modules/js_tests.c @@ -0,0 +1,104 @@ +#include "../js_modules.h" // IWYU pragma: keep +#include +#include +#include + +#define TAG "JsTests" + +static void js_tests_fail(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 1); + mjs_val_t message_arg = mjs_arg(mjs, 0); + const char* message = mjs_get_string(mjs, &message_arg, NULL); + furi_check(message); + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "%s", message); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_tests_assert_eq(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 2); + + mjs_val_t expected_arg = mjs_arg(mjs, 0); + mjs_val_t result_arg = mjs_arg(mjs, 1); + + if(mjs_is_number(expected_arg) && mjs_is_number(result_arg)) { + int32_t expected = mjs_get_int32(mjs, expected_arg); + int32_t result = mjs_get_int32(mjs, result_arg); + if(expected == result) { + FURI_LOG_T(TAG, "eq passed (exp=%ld res=%ld)", expected, result); + } else { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "expected %d, found %d", expected, result); + } + } else if(mjs_is_string(expected_arg) && mjs_is_string(result_arg)) { + const char* expected = mjs_get_string(mjs, &expected_arg, NULL); + const char* result = mjs_get_string(mjs, &result_arg, NULL); + if(strcmp(expected, result) == 0) { + FURI_LOG_T(TAG, "eq passed (exp=\"%s\" res=\"%s\")", expected, result); + } else { + mjs_prepend_errorf( + mjs, MJS_INTERNAL_ERROR, "expected \"%s\", found \"%s\"", expected, result); + } + } else if(mjs_is_boolean(expected_arg) && mjs_is_boolean(result_arg)) { + bool expected = mjs_get_bool(mjs, expected_arg); + bool result = mjs_get_bool(mjs, result_arg); + if(expected == result) { + FURI_LOG_T( + TAG, + "eq passed (exp=%s res=%s)", + expected ? "true" : "false", + result ? "true" : "false"); + } else { + mjs_prepend_errorf( + mjs, + MJS_INTERNAL_ERROR, + "expected %s, found %s", + expected ? "true" : "false", + result ? "true" : "false"); + } + } else { + JS_ERROR_AND_RETURN( + mjs, + MJS_INTERNAL_ERROR, + "type mismatch (expected %s, result %s)", + mjs_typeof(expected_arg), + mjs_typeof(result_arg)); + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_tests_assert_float_close(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 3); + + mjs_val_t expected_arg = mjs_arg(mjs, 0); + mjs_val_t result_arg = mjs_arg(mjs, 1); + mjs_val_t epsilon_arg = mjs_arg(mjs, 2); + furi_check(mjs_is_number(expected_arg)); + furi_check(mjs_is_number(result_arg)); + furi_check(mjs_is_number(epsilon_arg)); + double expected = mjs_get_double(mjs, expected_arg); + double result = mjs_get_double(mjs, result_arg); + double epsilon = mjs_get_double(mjs, epsilon_arg); + + if(ABS(expected - result) > epsilon) { + mjs_prepend_errorf( + mjs, + MJS_INTERNAL_ERROR, + "expected %f found %f (tolerance=%f)", + expected, + result, + epsilon); + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +void* js_tests_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + mjs_val_t tests_obj = mjs_mk_object(mjs); + mjs_set(mjs, tests_obj, "fail", ~0, MJS_MK_FN(js_tests_fail)); + mjs_set(mjs, tests_obj, "assert_eq", ~0, MJS_MK_FN(js_tests_assert_eq)); + mjs_set(mjs, tests_obj, "assert_float_close", ~0, MJS_MK_FN(js_tests_assert_float_close)); + *object = tests_obj; + + return (void*)1; +} diff --git a/applications/system/js_app/modules/js_tests.h b/applications/system/js_app/modules/js_tests.h new file mode 100644 index 0000000000..49f752c2b3 --- /dev/null +++ b/applications/system/js_app/modules/js_tests.h @@ -0,0 +1,5 @@ +#pragma once +#include "../js_thread_i.h" +#include "../js_modules.h" + +void* js_tests_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules); diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c deleted file mode 100644 index b90dbc153a..0000000000 --- a/applications/system/js_app/modules/js_textbox.c +++ /dev/null @@ -1,219 +0,0 @@ -#include -#include -#include "../js_modules.h" - -typedef struct { - TextBox* text_box; - ViewHolder* view_holder; - FuriString* text; - bool is_shown; -} JsTextboxInst; - -static JsTextboxInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsTextboxInst* textbox = mjs_get_ptr(mjs, obj_inst); - furi_assert(textbox); - return textbox; -} - -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); -} - -static bool check_arg_count(struct mjs* mjs, size_t count) { - size_t num_args = mjs_nargs(mjs); - if(num_args != count) { - ret_bad_args(mjs, "Wrong argument count"); - return false; - } - return true; -} - -static void js_textbox_set_config(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; - - TextBoxFocus set_focus = TextBoxFocusStart; - mjs_val_t focus_arg = mjs_arg(mjs, 0); - const char* focus = mjs_get_string(mjs, &focus_arg, NULL); - if(!focus) { - ret_bad_args(mjs, "Focus must be a string"); - return; - } else { - if(!strncmp(focus, "start", strlen("start"))) { - set_focus = TextBoxFocusStart; - } else if(!strncmp(focus, "end", strlen("end"))) { - set_focus = TextBoxFocusEnd; - } else { - ret_bad_args(mjs, "Bad focus value"); - return; - } - } - - TextBoxFont set_font = TextBoxFontText; - mjs_val_t font_arg = mjs_arg(mjs, 1); - const char* font = mjs_get_string(mjs, &font_arg, NULL); - if(!font) { - ret_bad_args(mjs, "Font must be a string"); - return; - } else { - if(!strncmp(font, "text", strlen("text"))) { - set_font = TextBoxFontText; - } else if(!strncmp(font, "hex", strlen("hex"))) { - set_font = TextBoxFontHex; - } else { - ret_bad_args(mjs, "Bad font value"); - return; - } - } - - text_box_set_focus(textbox->text_box, set_focus); - text_box_set_font(textbox->text_box, set_font); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_add_text(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; - - mjs_val_t text_arg = mjs_arg(mjs, 0); - size_t text_len = 0; - const char* text = mjs_get_string(mjs, &text_arg, &text_len); - if(!text) { - ret_bad_args(mjs, "Text must be a string"); - return; - } - - // Avoid condition race between GUI and JS thread - text_box_set_text(textbox->text_box, ""); - - size_t new_len = furi_string_size(textbox->text) + text_len; - if(new_len >= 4096) { - furi_string_right(textbox->text, new_len / 2); - } - - furi_string_cat(textbox->text, text); - - text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_clear_text(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - // Avoid condition race between GUI and JS thread - text_box_set_text(textbox->text_box, ""); - - furi_string_reset(textbox->text); - - text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_is_open(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); -} - -static void textbox_callback(void* context, uint32_t arg) { - UNUSED(arg); - JsTextboxInst* textbox = context; - view_holder_set_view(textbox->view_holder, NULL); - textbox->is_shown = false; -} - -static void textbox_exit(void* context) { - JsTextboxInst* textbox = context; - // Using timer to schedule view_holder stop, will not work under high CPU load - furi_timer_pending_callback(textbox_callback, textbox, 0); -} - -static void js_textbox_show(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - if(textbox->is_shown) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); - textbox->is_shown = true; - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_close(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - view_holder_set_view(textbox->view_holder, NULL); - textbox->is_shown = false; - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { - JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); - - mjs_val_t textbox_obj = mjs_mk_object(mjs); - mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); - mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); - mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); - mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); - mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); - mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); - mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); - - textbox->text = furi_string_alloc(); - textbox->text_box = text_box_alloc(); - - Gui* gui = furi_record_open(RECORD_GUI); - textbox->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(textbox->view_holder, gui); - view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); - - *object = textbox_obj; - return textbox; -} - -static void js_textbox_destroy(void* inst) { - JsTextboxInst* textbox = inst; - - view_holder_set_view(textbox->view_holder, NULL); - view_holder_free(textbox->view_holder); - textbox->view_holder = NULL; - - furi_record_close(RECORD_GUI); - - text_box_reset(textbox->text_box); - furi_string_reset(textbox->text); - - text_box_free(textbox->text_box); - furi_string_free(textbox->text); - free(textbox); -} - -static const JsModuleDescriptor js_textbox_desc = { - "textbox", - js_textbox_create, - js_textbox_destroy, -}; - -static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_textbox_desc, -}; - -const FlipperAppPluginDescriptor* js_textbox_ep(void) { - return &textbox_plugin_descriptor; -} diff --git a/applications/system/js_app/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h index b48221343f..b2debbde87 100644 --- a/applications/system/js_app/plugin_api/app_api_table_i.h +++ b/applications/system/js_app/plugin_api/app_api_table_i.h @@ -7,4 +7,5 @@ static constexpr auto app_api_table = sort(create_array_t( API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), - API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)))); + API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), + API_METHOD(js_module_get, void*, (JsModules*, const char*)))); diff --git a/applications/system/js_app/plugin_api/js_plugin_api.h b/applications/system/js_app/plugin_api/js_plugin_api.h index a817d34a90..421b685762 100644 --- a/applications/system/js_app/plugin_api/js_plugin_api.h +++ b/applications/system/js_app/plugin_api/js_plugin_api.h @@ -7,12 +7,16 @@ extern "C" { #endif +typedef void JsModules; + bool js_delay_with_flags(struct mjs* mjs, uint32_t time); void js_flags_set(struct mjs* mjs, uint32_t flags); uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); +void* js_module_get(JsModules* modules, const char* name); + #ifdef __cplusplus } #endif diff --git a/applications/system/js_app/types/badusb/index.d.ts b/applications/system/js_app/types/badusb/index.d.ts new file mode 100644 index 0000000000..2107909673 --- /dev/null +++ b/applications/system/js_app/types/badusb/index.d.ts @@ -0,0 +1,81 @@ +/** + * @brief Special key codes that this module recognizes + */ +export type ModifierKey = "CTRL" | "SHIFT" | "ALT" | "GUI"; + +export type MainKey = + "DOWN" | "LEFT" | "RIGHT" | "UP" | + + "ENTER" | "PAUSE" | "CAPSLOCK" | "DELETE" | "BACKSPACE" | "END" | "ESC" | + "HOME" | "INSERT" | "NUMLOCK" | "PAGEUP" | "PAGEDOWN" | "PRINTSCREEN" | + "SCROLLLOCK" | "SPACE" | "TAB" | "MENU" | + + "F1" | "F2" | "F3" | "F4" | "F5" | "F6" | "F7" | "F8" | "F9" | "F10" | + "F11" | "F12" | "F13" | "F14" | "F15" | "F16" | "F17" | "F18" | "F19" | + "F20" | "F21" | "F22" | "F23" | "F24" | + + "\n" | " " | "!" | "\"" | "#" | "$" | "%" | "&" | "'" | "(" | ")" | "*" | + "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | ">" | "=" | "?" | "@" | "[" | + "]" | "\\" | "^" | "_" | "`" | "{" | "}" | "|" | "~" | + + "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | + + "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | + "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | + "Y" | "Z" | + + "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | + "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | + "y" | "z"; + +export type KeyCode = MainKey | ModifierKey | number; + +/** + * @brief Initializes the module + * @param settings USB device settings. Omit to select default parameters + */ +export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string }): void; + +/** + * @brief Tells whether the virtual USB HID device has successfully connected + */ +export declare function isConnected(): boolean; + +/** + * @brief Presses one or multiple keys at once, then releases them + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function press(...keys: KeyCode[]): void; + +/** + * @brief Presses one or multiple keys at once without releasing them + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function hold(...keys: KeyCode[]): void; + +/** + * @brief Releases one or multiple keys at once + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function release(...keys: KeyCode[]): void; + +/** + * @brief Prints a string by repeatedly pressing and releasing keys + * @param string The string to print + * @param delay How many milliseconds to wait between key presses + */ +export declare function print(string: string, delay?: number): void; + +/** + * @brief Prints a string by repeatedly pressing and releasing keys. Presses + * "Enter" after printing the string + * @param string The string to print + * @param delay How many milliseconds to wait between key presses + */ +export declare function println(): void; diff --git a/applications/system/js_app/types/event_loop/index.d.ts b/applications/system/js_app/types/event_loop/index.d.ts new file mode 100644 index 0000000000..49237782c5 --- /dev/null +++ b/applications/system/js_app/types/event_loop/index.d.ts @@ -0,0 +1,70 @@ +type Lit = undefined | null | {}; + +/** + * Subscription control interface + */ +export interface Subscription { + /** + * Cancels the subscription, preventing any future events managed by the + * subscription from firing + */ + cancel(): void; +} + +/** + * Opaque event source identifier + */ +export type Contract = symbol; + +/** + * A callback can be assigned to an event loop to listen to an event. It may + * return an array with values that will be passed to it as arguments the next + * time that it is called. The first argument is always the subscription + * manager, and the second argument is always the item that trigged the event. + * The type of the item is defined by the event source. + */ +export type Callback = (subscription: Subscription, item: Item, ...args: Args) => Args | undefined | void; + +/** + * Subscribes a callback to an event + * @param contract Event identifier + * @param callback Function to call when the event is triggered + * @param args Initial arguments passed to the callback + */ +export function subscribe(contract: Contract, callback: Callback, ...args: Args): Subscription; +/** + * Runs the event loop until it is stopped (potentially never) + */ +export function run(): void | never; +/** + * Stops the event loop + */ +export function stop(): void; + +/** + * Creates a timer event that can be subscribed to just like any other event + * @param mode Either `"oneshot"` or `"periodic"` + * @param interval Timer interval in milliseconds + */ +export function timer(mode: "oneshot" | "periodic", interval: number): Contract; + +/** + * Message queue + */ +export interface Queue { + /** + * Message event + */ + input: Contract; + /** + * Sends a message to the queue + * @param message message to send + */ + send(message: T): void; +} + +/** + * Creates a message queue + * @param length maximum queue capacity + */ +export function queue(length: number): Queue; diff --git a/applications/system/js_app/types/flipper/index.d.ts b/applications/system/js_app/types/flipper/index.d.ts new file mode 100644 index 0000000000..b1b1d474bc --- /dev/null +++ b/applications/system/js_app/types/flipper/index.d.ts @@ -0,0 +1,14 @@ +/** + * @brief Returns the device model + */ +export declare function getModel(): string; + +/** + * @brief Returns the name of the virtual dolphin + */ +export declare function getName(): string; + +/** + * @brief Returns the battery charge percentage + */ +export declare function getBatteryCharge(): number; diff --git a/applications/system/js_app/types/global.d.ts b/applications/system/js_app/types/global.d.ts new file mode 100644 index 0000000000..ab1660cf66 --- /dev/null +++ b/applications/system/js_app/types/global.d.ts @@ -0,0 +1,178 @@ +/** + * @brief Pauses JavaScript execution for a while + * @param ms How many milliseconds to pause the execution for + */ +declare function delay(ms: number): void; + +/** + * @brief Prints to the GUI console view + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the console view + */ +declare function print(...args: any[]): void; + +/** + * @brief Converts a number to a string + * @param value The number to convert to a string + * @param base Integer base (`2`...`16`), default: 16 + */ +declare function toString(value: number, base?: number): string; + +/** + * @brief Reads a JS value from a file + * + * Reads a file at the specified path, interprets it as a JS value and returns + * said value. + * + * @param path The path to the file + */ +declare function load(path: string): any; + +/** + * @brief mJS Foreign Pointer type + * + * JavaScript code cannot do anything with values of `RawPointer` type except + * acquire them from native code and pass them right back to other parts of + * native code. These values cannot be turned into something meaningful, nor can + * be they modified. + */ +declare type RawPointer = symbol & { "__tag__": "raw_ptr" }; +// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist. + +/** + * @brief Holds raw bytes + */ +declare class ArrayBuffer { + /** + * @brief The pointer to the byte buffer + * @note Like other `RawPointer` values, this value is essentially useless + * to JS code. + */ + getPtr: RawPointer; + /** + * @brief The length of the buffer in bytes + */ + byteLength: number; + /** + * @brief Creates an `ArrayBuffer` that contains a sub-part of the buffer + * @param start The index of the byte in the source buffer to be used as the + * start for the new buffer + * @param end The index of the byte in the source buffer that follows the + * byte to be used as the last byte for the new buffer + */ + slice(start: number, end?: number): ArrayBuffer; +} + +declare function ArrayBuffer(): ArrayBuffer; + +declare type ElementType = "u8" | "i8" | "u16" | "i16" | "u32" | "i32"; + +declare class TypedArray { + /** + * @brief The length of the buffer in bytes + */ + byteLength: number; + /** + * @brief The length of the buffer in typed elements + */ + length: number; + /** + * @brief The underlying `ArrayBuffer` + */ + buffer: ArrayBuffer; +} + +declare class Uint8Array extends TypedArray<"u8"> { } +declare class Int8Array extends TypedArray<"i8"> { } +declare class Uint16Array extends TypedArray<"u16"> { } +declare class Int16Array extends TypedArray<"i16"> { } +declare class Uint32Array extends TypedArray<"u32"> { } +declare class Int32Array extends TypedArray<"i32"> { } + +declare function Uint8Array(data: ArrayBuffer | number | number[]): Uint8Array; +declare function Int8Array(data: ArrayBuffer | number | number[]): Int8Array; +declare function Uint16Array(data: ArrayBuffer | number | number[]): Uint16Array; +declare function Int16Array(data: ArrayBuffer | number | number[]): Int16Array; +declare function Uint32Array(data: ArrayBuffer | number | number[]): Uint32Array; +declare function Int32Array(data: ArrayBuffer | number | number[]): Int32Array; + +declare const console: { + /** + * @brief Prints to the UART logs at the `[I]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + log(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[D]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + debug(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[W]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + warn(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[E]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + error(...args: any[]): void; +}; + +declare class Array { + /** + * @brief Takes items out of the array + * + * Removes elements from the array and returns them in a new array + * + * @param start The index to start taking elements from + * @param deleteCount How many elements to take + * @returns The elements that were taken out of the original array as a new + * array + */ + splice(start: number, deleteCount: number): T[]; + /** + * @brief Adds a value to the end of the array + * @param value The value to add + * @returns New length of the array + */ + push(value: T): number; + /** + * @brief How many elements there are in the array + */ + length: number; +} + +declare class String { + /** + * @brief How many characters there are in the string + */ + length: number; + /** + * @brief Returns the character code at an index in the string + * @param index The index to consult + */ + charCodeAt(index: number): number; + /** + * See `charCodeAt` + */ + at(index: number): number; +} + +declare class Boolean { } + +declare class Function { } + +declare class Number { } + +declare class Object { } + +declare class RegExp { } + +declare interface IArguments { } + +declare type Partial = { [K in keyof O]?: O[K] }; diff --git a/applications/system/js_app/types/gpio/index.d.ts b/applications/system/js_app/types/gpio/index.d.ts new file mode 100644 index 0000000000..18705f8982 --- /dev/null +++ b/applications/system/js_app/types/gpio/index.d.ts @@ -0,0 +1,45 @@ +import type { Contract } from "../event_loop"; + +export interface Mode { + direction: "in" | "out"; + outMode?: "push_pull" | "open_drain"; + inMode?: "analog" | "plain_digital" | "interrupt" | "event"; + edge?: "rising" | "falling" | "both"; + pull?: "up" | "down"; +} + +export interface Pin { + /** + * Configures a pin. This may be done several times. + * @param mode Pin configuration object + */ + init(mode: Mode): void; + /** + * Sets the output value of a pin if it's been configured with + * `direction: "out"`. + * @param value Logic value to output + */ + write(value: boolean): void; + /** + * Gets the input value of a pin if it's been configured with + * `direction: "in"`, but not `inMode: "analog"`. + */ + read(): boolean; + /** + * Gets the input voltage of a pin in millivolts if it's been configured + * with `direction: "in"` and `inMode: "analog"` + */ + read_analog(): number; + /** + * Returns an `event_loop` event that can be used to listen to interrupts, + * as configured by `init` + */ + interrupt(): Contract; +} + +/** + * Returns an object that can be used to manage a GPIO pin. For the list of + * available pins, see https://docs.flipper.net/gpio-and-modules#miFsS + * @param pin Pin name (e.g. `"PC3"`) or number (e.g. `7`) + */ +export function get(pin: string | number): Pin; diff --git a/applications/system/js_app/types/gui/dialog.d.ts b/applications/system/js_app/types/gui/dialog.d.ts new file mode 100644 index 0000000000..6d9c8d43b2 --- /dev/null +++ b/applications/system/js_app/types/gui/dialog.d.ts @@ -0,0 +1,16 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + text: string, + left: string, + center: string, + right: string, +} +declare class Dialog extends View { + input: Contract<"left" | "center" | "right">; +} +declare class DialogFactory extends ViewFactory { } +declare const factory: DialogFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/empty_screen.d.ts b/applications/system/js_app/types/gui/empty_screen.d.ts new file mode 100644 index 0000000000..c71e93b327 --- /dev/null +++ b/applications/system/js_app/types/gui/empty_screen.d.ts @@ -0,0 +1,7 @@ +import type { View, ViewFactory } from "."; + +type Props = {}; +declare class EmptyScreen extends View { } +declare class EmptyScreenFactory extends ViewFactory { } +declare const factory: EmptyScreenFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/index.d.ts b/applications/system/js_app/types/gui/index.d.ts new file mode 100644 index 0000000000..3f95ab7806 --- /dev/null +++ b/applications/system/js_app/types/gui/index.d.ts @@ -0,0 +1,41 @@ +import type { Contract } from "../event_loop"; + +type Properties = { [K: string]: any }; + +export declare class View { + set

(property: P, value: Props[P]): void; +} + +export declare class ViewFactory> { + make(): V; + makeWith(initial: Partial): V; +} + +declare class ViewDispatcher { + /** + * Event source for `sendCustom` events + */ + custom: Contract; + /** + * Event source for navigation events (back key presses) + */ + navigation: Contract; + /** + * Sends a number to the custom event handler + * @param event number to send + */ + sendCustom(event: number): void; + /** + * Switches to a view + * @param assoc View-ViewDispatcher association as returned by `add` + */ + switchTo(assoc: View): void; + /** + * Sends this ViewDispatcher to the front or back, above or below all other + * GUI viewports + * @param direction Either `"front"` or `"back"` + */ + sendTo(direction: "front" | "back"): void; +} + +export const viewDispatcher: ViewDispatcher; diff --git a/applications/system/js_app/types/gui/loading.d.ts b/applications/system/js_app/types/gui/loading.d.ts new file mode 100644 index 0000000000..73a9633494 --- /dev/null +++ b/applications/system/js_app/types/gui/loading.d.ts @@ -0,0 +1,7 @@ +import type { View, ViewFactory } from "."; + +type Props = {}; +declare class Loading extends View { } +declare class LoadingFactory extends ViewFactory { } +declare const factory: LoadingFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/submenu.d.ts b/applications/system/js_app/types/gui/submenu.d.ts new file mode 100644 index 0000000000..59d5358643 --- /dev/null +++ b/applications/system/js_app/types/gui/submenu.d.ts @@ -0,0 +1,13 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + items: string[], +}; +declare class Submenu extends View { + chosen: Contract; +} +declare class SubmenuFactory extends ViewFactory { } +declare const factory: SubmenuFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/text_box.d.ts b/applications/system/js_app/types/gui/text_box.d.ts new file mode 100644 index 0000000000..3dbbac5710 --- /dev/null +++ b/applications/system/js_app/types/gui/text_box.d.ts @@ -0,0 +1,14 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + text: string, + font: "text" | "hex", + focus: "start" | "end", +} +declare class TextBox extends View { + chosen: Contract; +} +declare class TextBoxFactory extends ViewFactory { } +declare const factory: TextBoxFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/text_input.d.ts b/applications/system/js_app/types/gui/text_input.d.ts new file mode 100644 index 0000000000..96652b1d45 --- /dev/null +++ b/applications/system/js_app/types/gui/text_input.d.ts @@ -0,0 +1,14 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + minLength: number, + maxLength: number, +} +declare class TextInput extends View { + input: Contract; +} +declare class TextInputFactory extends ViewFactory { } +declare const factory: TextInputFactory; +export = factory; diff --git a/applications/system/js_app/types/math/index.d.ts b/applications/system/js_app/types/math/index.d.ts new file mode 100644 index 0000000000..25abca4af1 --- /dev/null +++ b/applications/system/js_app/types/math/index.d.ts @@ -0,0 +1,24 @@ +export function abs(n: number): number; +export function acos(n: number): number; +export function acosh(n: number): number; +export function asin(n: number): number; +export function asinh(n: number): number; +export function atan(n: number): number; +export function atan2(a: number, b: number): number; +export function atanh(n: number): number; +export function cbrt(n: number): number; +export function ceil(n: number): number; +export function clz32(n: number): number; +export function cos(n: number): number; +export function exp(n: number): number; +export function floor(n: number): number; +export function max(n: number, m: number): number; +export function min(n: number, m: number): number; +export function pow(n: number, m: number): number; +export function random(): number; +export function sign(n: number): number; +export function sin(n: number): number; +export function sqrt(n: number): number; +export function trunc(n: number): number; +declare const PI: number; +declare const EPSILON: number; diff --git a/applications/system/js_app/types/notification/index.d.ts b/applications/system/js_app/types/notification/index.d.ts new file mode 100644 index 0000000000..947daba218 --- /dev/null +++ b/applications/system/js_app/types/notification/index.d.ts @@ -0,0 +1,20 @@ +/** + * @brief Signals success to the user via the color LED, speaker and vibration + * motor + */ +export declare function success(): void; + +/** + * @brief Signals failure to the user via the color LED, speaker and vibration + * motor + */ +export declare function error(): void; + +export type Color = "red" | "green" | "blue" | "yellow" | "cyan" | "magenta"; + +/** + * @brief Displays a basic color on the color LED + * @param color The color to display, see `Color` + * @param duration The duration, either `"short"` (10ms) or `"long"` (100ms) + */ +export declare function blink(color: Color, duration: "short" | "long"): void; diff --git a/applications/system/js_app/types/serial/index.d.ts b/applications/system/js_app/types/serial/index.d.ts new file mode 100644 index 0000000000..1a7ed6397e --- /dev/null +++ b/applications/system/js_app/types/serial/index.d.ts @@ -0,0 +1,77 @@ +/** + * @brief Initializes the serial port + * @param port The port to initialize (`"lpuart"` or `"start"`) + * @param baudRate + */ +export declare function setup(port: "lpuart" | "usart", baudRate: number): void; + +/** + * @brief Writes data to the serial port + * @param value The data to write: + * - Strings will get sent as ASCII. + * - Numbers will get sent as a single byte. + * - Arrays of numbers will get sent as a sequence of bytes. + * - `ArrayBuffer`s and `TypedArray`s will be sent as a sequence + * of bytes. + */ +export declare function write(value: string | number | number[] | ArrayBuffer | TypedArray): void; + +/** + * @brief Reads data from the serial port + * @param length The number of bytes to read + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. + * @returns The received data interpreted as ASCII, or `undefined` if 0 bytes + * were read. + */ +export declare function read(length: number, timeout?: number): string | undefined; + +/** + * @brief Reads data from the serial port + * + * Data is read one character after another until either a `\r` or `\n` + * character is received, neither of which is included in the result. + * + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. The timeout only + * applies to characters, not entire strings. + * @returns The received data interpreted as ASCII, or `undefined` if 0 bytes + * were read. + */ +export declare function readln(timeout?: number): string; + +/** + * @brief Reads data from the serial port + * @param length The number of bytes to read + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. + * @returns The received data as an ArrayBuffer, or `undefined` if 0 bytes were + * read. + */ +export declare function readBytes(length: number, timeout?: number): ArrayBuffer; + +/** + * @brief Reads data from the serial port, trying to match it to a pattern + * @param patterns A single pattern or an array of patterns: + * - If the argument is a single `string`, this function will + * match against the given string. + * - If the argument is an array of `number`s, this function + * will match against the given sequence of bytes, + * - If the argument is an array of `string`s, this function + * will match against any string out of the ones that were + * provided. + * - If the argument is an array of arrays of `number`s, this + * function will match against any sequence of bytes out of + * the ones that were provided. + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. The timeout only + * applies to characters, not entire strings. + * @returns The index of the matched pattern if multiple were provided, or 0 if + * only one was provided and it matched, or `undefined` if none of the + * patterns matched. + */ +export declare function expect(patterns: string | number[] | string[] | number[][], timeout?: number): number | undefined; diff --git a/applications/system/js_app/types/storage/index.d.ts b/applications/system/js_app/types/storage/index.d.ts new file mode 100644 index 0000000000..0dd29e121c --- /dev/null +++ b/applications/system/js_app/types/storage/index.d.ts @@ -0,0 +1,237 @@ +/** + * File readability mode: + * - `"r"`: read-only + * - `"w"`: write-only + * - `"rw"`: read-write + */ +export type AccessMode = "r" | "w" | "rw"; + +/** + * File creation mode: + * - `"open_existing"`: open file or fail if it doesn't exist + * - `"open_always"`: open file or create a new empty one if it doesn't exist + * - `"open_append"`: open file and set r/w pointer to EOF, or create a new one if it doesn't exist + * - `"create_new"`: create new file or fail if it exists + * - `"create_always"`: truncate and open file, or create a new empty one if it doesn't exist + */ +export type OpenMode = "open_existing" | "open_always" | "open_append" | "create_new" | "create_always"; + +/** Standard UNIX timestamp */ +export type Timestamp = number; + +/** File information structure */ +export declare class FileInfo { + /** + * Full path (e.g. "/ext/test", returned by `stat`) or file name + * (e.g. "test", returned by `readDirectory`) + */ + path: string; + /** + * Is the file a directory? + */ + isDirectory: boolean; + /** + * File size in bytes, or 0 in the case of directories + */ + size: number; + /** + * Time of last access as a UNIX timestamp + */ + accessTime: Timestamp; +} + +/** Filesystem information structure */ +export declare class FsInfo { + /** Total size of the filesystem, in bytes */ + totalSpace: number; + /** Free space in the filesystem, in bytes */ + freeSpace: number; +} + +// file operations + +/** File class */ +export declare class File { + /** + * Closes the file. After this method is called, all other operations + * related to this file become unavailable. + * @returns `true` on success, `false` on failure + */ + close(): boolean; + /** + * Is the file currently open? + */ + isOpen(): boolean; + /** + * Reads bytes from a file opened in read-only or read-write mode + * @param mode The data type to interpret the bytes as: a `string` decoded + * from ASCII data (`"ascii"`), or an `ArrayBuf` (`"binary"`) + * @param bytes How many bytes to read from the file + * @returns an `ArrayBuf` if the mode is `"binary"`, a `string` if the mode + * is `ascii`. The number of bytes that was actually read may be + * fewer than requested. + */ + read(mode: T extends ArrayBuffer ? "binary" : "ascii", bytes: number): T; + /** + * Writes bytes to a file opened in write-only or read-write mode + * @param data The data to write: a string that will be ASCII-encoded, or an + * ArrayBuf + * @returns the amount of bytes that was actually written + */ + write(data: ArrayBuffer | string): number; + /** + * Moves the R/W pointer forward + * @param bytes How many bytes to move the pointer forward by + * @returns `true` on success, `false` on failure + */ + seekRelative(bytes: number): boolean; + /** + * Moves the R/W pointer to an absolute position inside the file + * @param bytes The position inside the file + * @returns `true` on success, `false` on failure + */ + seekAbsolute(bytes: number): boolean; + /** + * Gets the absolute position of the R/W pointer in bytes + */ + tell(): number; + /** + * Discards the data after the current position of the R/W pointer in a file + * opened in either write-only or read-write mode. + * @returns `true` on success, `false` on failure + */ + truncate(): boolean; + /** + * Reads the total size of the file in bytes + */ + size(): number; + /** + * Detects whether the R/W pointer has reached the end of the file + */ + eof(): boolean; + /** + * Copies bytes from the R/W pointer in the current file to the R/W pointer + * in another file + * @param dest The file to copy the bytes into + * @param bytes The number of bytes to copy + * @returns `true` on success, `false` on failure + */ + copyTo(dest: File, bytes: number): boolean; +} + +/** + * Opens a file + * @param path The path to the file + * @param accessMode `"r"`, `"w"` or `"rw"`; see `AccessMode` + * @param openMode `"open_existing"`, `"open_always"`, `"open_append"`, + * `"create_new"` or `"create_always"`; see `OpenMode` + * @returns a `File` on success, or `undefined` on failure + */ +export declare function openFile(path: string, accessMode: AccessMode, openMode: OpenMode): File | undefined; +/** + * Detects whether a file exists + * @param path The path to the file + * @returns `true` on success, `false` on failure + */ +export declare function fileExists(path: string): boolean; + +// directory operations + +/** + * Reads the list of files in a directory + * @param path The path to the directory + * @returns Array of `FileInfo` structures with directory entries, + * or `undefined` on failure + */ +export declare function readDirectory(path: string): FileInfo[] | undefined; +/** + * Detects whether a directory exists + * @param path The path to the directory + */ +export declare function directoryExists(path: string): boolean; +/** + * Creates an empty directory + * @param path The path to the new directory + * @returns `true` on success, `false` on failure + */ +export declare function makeDirectory(path: string): boolean; + +// common (file/dir) operations + +/** + * Detects whether a file or a directory exists + * @param path The path to the file or directory + */ +export declare function fileOrDirExists(path: string): boolean; +/** + * Acquires metadata about a file or directory + * @param path The path to the file or directory + * @returns A `FileInfo` structure or `undefined` on failure + */ +export declare function stat(path: string): FileInfo | undefined; +/** + * Removes a file or an empty directory + * @param path The path to the file or directory + * @returns `true` on success, `false` on failure + */ +export declare function remove(path: string): boolean; +/** + * Removes a file or recursively removes a possibly non-empty directory + * @param path The path to the file or directory + * @returns `true` on success, `false` on failure + */ +export declare function rmrf(path: string): boolean; +/** + * Renames or moves a file or directory + * @param oldPath The old path to the file or directory + * @param newPath The new path that the file or directory will become accessible + * under + * @returns `true` on success, `false` on failure + */ +export declare function rename(oldPath: string, newPath: string): boolean; +/** + * Copies a file or recursively copies a possibly non-empty directory + * @param oldPath The original path to the file or directory + * @param newPath The new path that the copy of the file or directory will be + * accessible under + */ +export declare function copy(oldPath: string, newPath: string): boolean; +/** + * Fetches generic information about a filesystem + * @param filesystem The path to the filesystem (e.g. `"/ext"` or `"/int"`) + */ +export declare function fsInfo(filesystem: string): FsInfo | undefined; +/** + * Chooses the next available filename with a numeric suffix in a directory + * + * ``` + * "/ext/example_dir/example_file123.txt" + * \______________/ \__________/\_/\__/ + * dirPath fileName | | + * | +---- fileExt + * +------- selected by this function + * ``` + * + * @param dirPath The directory to look in + * @param fileName The base of the filename (the part before the numeric suffix) + * @param fileExt The extension of the filename (the part after the numeric suffix) + * @param maxLen The maximum length of the filename with the numeric suffix + * @returns The base of the filename with the next available numeric suffix, + * without the extension or the base directory. + */ +export declare function nextAvailableFilename(dirPath: string, fileName: string, fileExt: string, maxLen: number): string; + +// path operations that do not access the filesystem + +/** + * Determines whether the two paths are equivalent. Respects filesystem-defined + * path equivalence rules. + */ +export declare function arePathsEqual(path1: string, path2: string): boolean; +/** + * Determines whether a path is a subpath of another path. Respects + * filesystem-defined path equivalence rules. + * @param parentPath The parent path + * @param childPath The child path + */ +export declare function isSubpathOf(parentPath: string, childPath: string): boolean; diff --git a/applications/system/js_app/types/tests/index.d.ts b/applications/system/js_app/types/tests/index.d.ts new file mode 100644 index 0000000000..8aaeec5e52 --- /dev/null +++ b/applications/system/js_app/types/tests/index.d.ts @@ -0,0 +1,8 @@ +/** + * Unit test module. Only available if the firmware has been configured with + * `FIRMWARE_APP_SET=unit_tests`. + */ + +export function fail(message: string): never; +export function assert_eq(expected: T, result: T): void | never; +export function assert_float_close(expected: number, result: number, epsilon: number): void | never; From 8233534d6aa464bf624ead2b027c2914e933dcfc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 15 Oct 2024 03:27:04 +0300 Subject: [PATCH 149/236] merge p3 --- applications/system/js_app/application.fam | 40 +++++++++++++++++++ .../system/js_app/modules/js_blebeacon.c | 4 +- .../js_app/modules/js_subghz/js_subghz.c | 4 +- .../js_app/modules/js_usbdisk/js_usbdisk.c | 4 +- .../system/js_app/modules/js_vgm/js_vgm.c | 4 +- .../system/js_app/modules/js_widget.c | 4 +- 6 files changed, 55 insertions(+), 5 deletions(-) diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 36fd7b16c4..276690b932 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -129,3 +129,43 @@ App( requires=["js_app"], sources=["modules/js_storage.c"], ) + +App( + appid="js_widget", + apptype=FlipperAppType.PLUGIN, + entry_point="js_widget_ep", + requires=["js_app"], + sources=["modules/js_widget.c"], +) + +App( + appid="js_vgm", + apptype=FlipperAppType.PLUGIN, + entry_point="js_vgm_ep", + requires=["js_app"], + sources=["modules/js_vgm/*.c", "modules/js_vgm/ICM42688P/*.c"], +) + +App( + appid="js_subghz", + apptype=FlipperAppType.PLUGIN, + entry_point="js_subghz_ep", + requires=["js_app"], + sources=["modules/js_subghz/*.c"], +) + +App( + appid="js_blebeacon", + apptype=FlipperAppType.PLUGIN, + entry_point="js_blebeacon_ep", + requires=["js_app"], + sources=["modules/js_blebeacon.c"], +) + +App( + appid="js_usbdisk", + apptype=FlipperAppType.PLUGIN, + entry_point="js_usbdisk_ep", + requires=["js_app"], + sources=["modules/js_usbdisk/*.c"], +) diff --git a/applications/system/js_app/modules/js_blebeacon.c b/applications/system/js_app/modules/js_blebeacon.c index 1d79457c87..379c6a18b5 100644 --- a/applications/system/js_app/modules/js_blebeacon.c +++ b/applications/system/js_app/modules/js_blebeacon.c @@ -193,7 +193,8 @@ static void js_blebeacon_keep_alive(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void* js_blebeacon_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_blebeacon_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsBlebeaconInst* blebeacon = malloc(sizeof(JsBlebeaconInst)); mjs_val_t blebeacon_obj = mjs_mk_object(mjs); mjs_set(mjs, blebeacon_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, blebeacon)); @@ -231,6 +232,7 @@ static const JsModuleDescriptor js_blebeacon_desc = { "blebeacon", js_blebeacon_create, js_blebeacon_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_subghz/js_subghz.c b/applications/system/js_app/modules/js_subghz/js_subghz.c index f0e21c1ff9..daa46e5e5d 100644 --- a/applications/system/js_app/modules/js_subghz/js_subghz.c +++ b/applications/system/js_app/modules/js_subghz/js_subghz.c @@ -479,7 +479,8 @@ static void js_subghz_end(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void* js_subghz_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_subghz_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsSubghzInst* js_subghz = malloc(sizeof(JsSubghzInst)); mjs_val_t subghz_obj = mjs_mk_object(mjs); @@ -519,6 +520,7 @@ static const JsModuleDescriptor js_subghz_desc = { "subghz", js_subghz_create, js_subghz_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_usbdisk/js_usbdisk.c b/applications/system/js_app/modules/js_usbdisk/js_usbdisk.c index 2fe0887ef5..c79aba176e 100644 --- a/applications/system/js_app/modules/js_usbdisk/js_usbdisk.c +++ b/applications/system/js_app/modules/js_usbdisk/js_usbdisk.c @@ -167,7 +167,8 @@ static void js_usbdisk_stop(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void* js_usbdisk_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_usbdisk_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsUsbdiskInst* usbdisk = malloc(sizeof(JsUsbdiskInst)); mjs_val_t usbdisk_obj = mjs_mk_object(mjs); mjs_set(mjs, usbdisk_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, usbdisk)); @@ -188,6 +189,7 @@ static const JsModuleDescriptor js_usbdisk_desc = { "usbdisk", js_usbdisk_create, js_usbdisk_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_vgm/js_vgm.c b/applications/system/js_app/modules/js_vgm/js_vgm.c index f7c0d6fcdb..2d7a56b6e9 100644 --- a/applications/system/js_app/modules/js_vgm/js_vgm.c +++ b/applications/system/js_app/modules/js_vgm/js_vgm.c @@ -119,7 +119,8 @@ static void js_vgm_delta_yaw(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, 0)); } -static void* js_vgm_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_vgm_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsVgmInst* vgm = malloc(sizeof(JsVgmInst)); vgm->imu = imu_alloc(); vgm->present = imu_present(vgm->imu); @@ -146,6 +147,7 @@ static const JsModuleDescriptor js_vgm_desc = { name: "vgm", create: js_vgm_create, destroy: js_vgm_destroy, + api_interface: NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_widget.c b/applications/system/js_app/modules/js_widget.c index 6c8e79b2ec..64390abe77 100644 --- a/applications/system/js_app/modules/js_widget.c +++ b/applications/system/js_app/modules/js_widget.c @@ -810,7 +810,8 @@ static void widget_draw_callback(Canvas* canvas, void* model) { } } -static void* js_widget_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_widget_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsWidgetInst* widget = malloc(sizeof(JsWidgetInst)); mjs_val_t widget_obj = mjs_mk_object(mjs); @@ -908,6 +909,7 @@ static const JsModuleDescriptor js_widget_desc = { "widget", js_widget_create, js_widget_destroy, + NULL, }; static const FlipperAppPluginDescriptor widget_plugin_descriptor = { From a6cf08523ca6bad2c96bdad275c8574aa6526769 Mon Sep 17 00:00:00 2001 From: porta Date: Tue, 15 Oct 2024 20:03:15 +0300 Subject: [PATCH 150/236] Small JS fixes (#3950) --- applications/system/js_app/examples/apps/Scripts/uart_echo.js | 4 ++-- applications/system/js_app/types/badusb/index.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/uart_echo.js index 2a0159b469..06d6119fdc 100644 --- a/applications/system/js_app/examples/apps/Scripts/uart_echo.js +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo.js @@ -2,10 +2,10 @@ let serial = require("serial"); serial.setup("usart", 230400); while (1) { - let rx_data = serial.readBytes(1, 0); + let rx_data = serial.readBytes(1, 1000); if (rx_data !== undefined) { serial.write(rx_data); let data_view = Uint8Array(rx_data); print("0x" + toString(data_view[0], 16)); } -} \ No newline at end of file +} diff --git a/applications/system/js_app/types/badusb/index.d.ts b/applications/system/js_app/types/badusb/index.d.ts index 2107909673..647382dc0b 100644 --- a/applications/system/js_app/types/badusb/index.d.ts +++ b/applications/system/js_app/types/badusb/index.d.ts @@ -78,4 +78,4 @@ export declare function print(string: string, delay?: number): void; * @param string The string to print * @param delay How many milliseconds to wait between key presses */ -export declare function println(): void; +export declare function println(string: string, delay?: number): void; From a34e09094b571de9953b2e380f1ccfec15d94007 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 16 Oct 2024 02:11:41 +0900 Subject: [PATCH 151/236] [FL-3914] BackUSB (#3951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "[FL-3896] Split BadUSB into BadUSB and BadBLE (#3931)" This reverts commit 0eaad8bf64f01a6f932647a9cda5475dd9ea1524. * Better USB-BLE switch UX * Format sources * Format images Co-authored-by: あく --- applications/main/bad_usb/application.fam | 2 +- applications/main/bad_usb/bad_usb_app.c | 25 +- applications/main/bad_usb/bad_usb_app_i.h | 8 +- .../main/bad_usb/helpers/bad_usb_hid.c | 156 +++- .../main/bad_usb/helpers/bad_usb_hid.h | 7 +- .../main/bad_usb/helpers/ducky_script.c | 4 +- .../main/bad_usb/helpers/ducky_script.h | 2 +- .../bad_usb/scenes/bad_usb_scene_config.c | 59 ++ .../bad_usb/scenes/bad_usb_scene_config.h | 3 + .../scenes/bad_usb_scene_confirm_unpair.c} | 32 +- .../main/bad_usb/scenes/bad_usb_scene_error.c | 2 +- .../scenes/bad_usb_scene_unpair_done.c | 39 + .../main/bad_usb/scenes/bad_usb_scene_work.c | 21 +- .../main/bad_usb/views/bad_usb_view.c | 19 +- .../main/bad_usb/views/bad_usb_view.h | 2 + applications/system/bad_ble/application.fam | 12 - applications/system/bad_ble/bad_ble_app.c | 196 ----- applications/system/bad_ble/bad_ble_app.h | 11 - applications/system/bad_ble/bad_ble_app_i.h | 53 -- .../system/bad_ble/helpers/bad_ble_hid.c | 157 ---- .../system/bad_ble/helpers/bad_ble_hid.h | 34 - .../system/bad_ble/helpers/ducky_script.c | 716 ------------------ .../system/bad_ble/helpers/ducky_script.h | 55 -- .../bad_ble/helpers/ducky_script_commands.c | 241 ------ .../system/bad_ble/helpers/ducky_script_i.h | 76 -- .../bad_ble/helpers/ducky_script_keycodes.c | 133 ---- applications/system/bad_ble/icon.png | Bin 96 -> 0 bytes .../system/bad_ble/scenes/bad_ble_scene.c | 30 - .../system/bad_ble/scenes/bad_ble_scene.h | 29 - .../bad_ble/scenes/bad_ble_scene_config.c | 59 -- .../bad_ble/scenes/bad_ble_scene_config.h | 7 - .../scenes/bad_ble_scene_config_layout.c | 49 -- .../bad_ble/scenes/bad_ble_scene_error.c | 65 -- .../scenes/bad_ble_scene_file_select.c | 46 -- .../scenes/bad_ble_scene_unpair_done.c | 37 - .../bad_ble/scenes/bad_ble_scene_work.c | 65 -- .../system/bad_ble/views/bad_ble_view.c | 284 ------- .../system/bad_ble/views/bad_ble_view.h | 26 - .../icons/BadUsb}/Bad_BLE_48x22.png | Bin furi/core/timer.c | 2 - 40 files changed, 349 insertions(+), 2415 deletions(-) create mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_config.c rename applications/{system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c => main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c} (51%) create mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c delete mode 100644 applications/system/bad_ble/application.fam delete mode 100644 applications/system/bad_ble/bad_ble_app.c delete mode 100644 applications/system/bad_ble/bad_ble_app.h delete mode 100644 applications/system/bad_ble/bad_ble_app_i.h delete mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.c delete mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.h delete mode 100644 applications/system/bad_ble/helpers/ducky_script.c delete mode 100644 applications/system/bad_ble/helpers/ducky_script.h delete mode 100644 applications/system/bad_ble/helpers/ducky_script_commands.c delete mode 100644 applications/system/bad_ble/helpers/ducky_script_i.h delete mode 100644 applications/system/bad_ble/helpers/ducky_script_keycodes.c delete mode 100644 applications/system/bad_ble/icon.png delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.h delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.h delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_error.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_file_select.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_work.c delete mode 100644 applications/system/bad_ble/views/bad_ble_view.c delete mode 100644 applications/system/bad_ble/views/bad_ble_view.h rename {applications/system/bad_ble/assets => assets/icons/BadUsb}/Bad_BLE_48x22.png (100%) diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 9844e248df..8d3909fccc 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -7,7 +7,7 @@ App( icon="A_BadUsb_14", order=70, resources="resources", - fap_libs=["assets"], + fap_libs=["assets", "ble_profile"], fap_icon="icon.png", fap_category="USB", ) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 1ee92bdf3f..eda702cf4d 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -35,6 +35,7 @@ static void bad_usb_load_settings(BadUsbApp* app) { FuriString* temp_str = furi_string_alloc(); uint32_t version = 0; + uint32_t interface = 0; if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) { do { @@ -44,6 +45,8 @@ static void bad_usb_load_settings(BadUsbApp* app) { break; if(!flipper_format_read_string(fff, "layout", temp_str)) break; + if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break; + if(interface > BadUsbHidInterfaceBle) break; state = true; } while(0); @@ -53,6 +56,7 @@ static void bad_usb_load_settings(BadUsbApp* app) { if(state) { furi_string_set(app->keyboard_layout, temp_str); + app->interface = interface; Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo layout_file_info; @@ -64,6 +68,7 @@ static void bad_usb_load_settings(BadUsbApp* app) { } } else { furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); + app->interface = BadUsbHidInterfaceUsb; } furi_string_free(temp_str); @@ -79,6 +84,9 @@ static void bad_usb_save_settings(BadUsbApp* app) { fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION)) break; if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; + uint32_t interface_id = app->interface; + if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1)) + break; } while(0); } @@ -86,6 +94,11 @@ static void bad_usb_save_settings(BadUsbApp* app) { furi_record_close(RECORD_STORAGE); } +void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface) { + app->interface = interface; + bad_usb_view_set_interface(app->bad_usb_view, interface); +} + BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* app = malloc(sizeof(BadUsbApp)); @@ -117,7 +130,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { // Custom Widget app->widget = widget_alloc(); view_dispatcher_add_view( - app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget)); + app->view_dispatcher, BadUsbAppViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, BadUsbAppViewPopup, popup_get_view(app->popup)); app->var_item_list = variable_item_list_alloc(); view_dispatcher_add_view( @@ -163,9 +180,13 @@ void bad_usb_app_free(BadUsbApp* app) { bad_usb_view_free(app->bad_usb_view); // Custom Widget - view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError); + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWidget); widget_free(app->widget); + // Popup + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewPopup); + popup_free(app->popup); + // Config menu view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig); variable_item_list_free(app->var_item_list); diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index e63d0044c0..b34bd5de69 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "views/bad_usb_view.h" #include @@ -33,6 +34,7 @@ struct BadUsbApp { NotificationApp* notifications; DialogsApp* dialogs; Widget* widget; + Popup* popup; VariableItemList* var_item_list; BadUsbAppError error; @@ -41,11 +43,15 @@ struct BadUsbApp { BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; + BadUsbHidInterface interface; FuriHalUsbInterface* usb_if_prev; }; typedef enum { - BadUsbAppViewError, + BadUsbAppViewWidget, + BadUsbAppViewPopup, BadUsbAppViewWork, BadUsbAppViewConfig, } BadUsbAppView; + +void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface); diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index dcba7b5e93..5d7076314a 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -1,9 +1,12 @@ #include "bad_usb_hid.h" #include +#include #include #define TAG "BadUSB HID" +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" + void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) { furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); return NULL; @@ -69,6 +72,155 @@ static const BadUsbHidApi hid_api_usb = { .release_all = hid_usb_release_all, .get_led_state = hid_usb_get_led_state, }; -const BadUsbHidApi* bad_usb_hid_get_interface() { - return &hid_api_usb; + +typedef struct { + Bt* bt; + FuriHalBleProfileBase* profile; + HidStateCallback state_callback; + void* callback_context; + bool is_connected; +} BleHidInstance; + +static const BleProfileHidParams ble_hid_params = { + .device_name_prefix = "BadUSB", + .mac_xor = 0x0002, +}; + +static void hid_ble_connection_status_callback(BtStatus status, void* context) { + furi_assert(context); + BleHidInstance* ble_hid = context; + ble_hid->is_connected = (status == BtStatusConnected); + if(ble_hid->state_callback) { + ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); + } +} + +void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { + UNUSED(hid_cfg); + BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); + ble_hid->bt = furi_record_open(RECORD_BT); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); + furi_check(ble_hid->profile); + + furi_hal_bt_start_advertising(); + + bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); + + return ble_hid; +} + +void hid_ble_deinit(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + + bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(ble_hid->bt); + + furi_check(bt_profile_restore_default(ble_hid->bt)); + furi_record_close(RECORD_BT); + free(ble_hid); +} + +void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + ble_hid->state_callback = cb; + ble_hid->callback_context = context; +} + +bool hid_ble_is_connected(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_hid->is_connected; +} + +bool hid_ble_kb_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_press(ble_hid->profile, button); +} + +bool hid_ble_kb_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_release(ble_hid->profile, button); +} + +bool hid_ble_consumer_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_press(ble_hid->profile, button); +} + +bool hid_ble_consumer_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_release(ble_hid->profile, button); +} + +bool hid_ble_release_all(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + bool state = ble_profile_hid_kb_release_all(ble_hid->profile); + state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); + return state; +} + +uint8_t hid_ble_get_led_state(void* inst) { + UNUSED(inst); + FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); + return 0; +} + +static const BadUsbHidApi hid_api_ble = { + .init = hid_ble_init, + .deinit = hid_ble_deinit, + .set_state_callback = hid_ble_set_state_callback, + .is_connected = hid_ble_is_connected, + + .kb_press = hid_ble_kb_press, + .kb_release = hid_ble_kb_release, + .consumer_press = hid_ble_consumer_press, + .consumer_release = hid_ble_consumer_release, + .release_all = hid_ble_release_all, + .get_led_state = hid_ble_get_led_state, +}; + +const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) { + if(interface == BadUsbHidInterfaceUsb) { + return &hid_api_usb; + } else { + return &hid_api_ble; + } +} + +void bad_usb_hid_ble_remove_pairing(void) { + Bt* bt = furi_record_open(RECORD_BT); + bt_disconnect(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + bt_forget_bonded_devices(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(bt); + + furi_check(bt_profile_restore_default(bt)); + furi_record_close(RECORD_BT); } diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index feaaacd541..71d3a58e79 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -7,6 +7,11 @@ extern "C" { #include #include +typedef enum { + BadUsbHidInterfaceUsb, + BadUsbHidInterfaceBle, +} BadUsbHidInterface; + typedef struct { void* (*init)(FuriHalUsbHidConfig* hid_cfg); void (*deinit)(void* inst); @@ -21,7 +26,7 @@ typedef struct { uint8_t (*get_led_state)(void* inst); } BadUsbHidApi; -const BadUsbHidApi* bad_usb_hid_get_interface(); +const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface); void bad_usb_hid_ble_remove_pairing(void); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index d730fdba4d..ccc3caa811 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -650,7 +650,7 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) { memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout))); } -BadUsbScript* bad_usb_script_open(FuriString* file_path) { +BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) { furi_assert(file_path); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); @@ -660,7 +660,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) { bad_usb->st.state = BadUsbStateInit; bad_usb->st.error[0] = '\0'; - bad_usb->hid = bad_usb_hid_get_interface(); + bad_usb->hid = bad_usb_hid_get_interface(interface); bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); furi_thread_start(bad_usb->thread); diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 43969d7b67..9519623f60 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -34,7 +34,7 @@ typedef struct { typedef struct BadUsbScript BadUsbScript; -BadUsbScript* bad_usb_script_open(FuriString* file_path); +BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface); void bad_usb_script_close(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c new file mode 100644 index 0000000000..ae440cade9 --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.c @@ -0,0 +1,59 @@ +#include "../bad_usb_app_i.h" + +enum SubmenuIndex { + ConfigIndexKeyboardLayout, + ConfigIndexBleUnpair, +}; + +void bad_usb_scene_config_select_callback(void* context, uint32_t index) { + BadUsbApp* bad_usb = context; + + view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index); +} + +static void draw_menu(BadUsbApp* bad_usb) { + VariableItemList* var_item_list = bad_usb->var_item_list; + + variable_item_list_reset(var_item_list); + + variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL); +} + +void bad_usb_scene_config_on_enter(void* context) { + BadUsbApp* bad_usb = context; + VariableItemList* var_item_list = bad_usb->var_item_list; + + variable_item_list_set_enter_callback( + var_item_list, bad_usb_scene_config_select_callback, bad_usb); + draw_menu(bad_usb); + variable_item_list_set_selected_item(var_item_list, 0); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig); +} + +bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == ConfigIndexKeyboardLayout) { + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout); + } else if(event.event == ConfigIndexBleUnpair) { + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfirmUnpair); + } else { + furi_crash("Unknown key type"); + } + } + + return consumed; +} + +void bad_usb_scene_config_on_exit(void* context) { + BadUsbApp* bad_usb = context; + VariableItemList* var_item_list = bad_usb->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h index e640bb556b..3d1b8b1a7a 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.h +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.h @@ -1,4 +1,7 @@ ADD_SCENE(bad_usb, file_select, FileSelect) ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, error, Error) +ADD_SCENE(bad_usb, config, Config) ADD_SCENE(bad_usb, config_layout, ConfigLayout) +ADD_SCENE(bad_usb, confirm_unpair, ConfirmUnpair) +ADD_SCENE(bad_usb, unpair_done, UnpairDone) diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c b/applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c similarity index 51% rename from applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c rename to applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c index 63f1e92cf2..b8fd993e2e 100644 --- a/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c @@ -1,42 +1,42 @@ -#include "../bad_ble_app_i.h" +#include "../bad_usb_app_i.h" -void bad_ble_scene_confirm_unpair_widget_callback( +void bad_usb_scene_confirm_unpair_widget_callback( GuiButtonType type, InputType input_type, void* context) { UNUSED(input_type); SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type}; - bad_ble_scene_confirm_unpair_on_event(context, event); + bad_usb_scene_confirm_unpair_on_event(context, event); } -void bad_ble_scene_confirm_unpair_on_enter(void* context) { - BadBleApp* bad_ble = context; - Widget* widget = bad_ble->widget; +void bad_usb_scene_confirm_unpair_on_enter(void* context) { + BadUsbApp* bad_usb = context; + Widget* widget = bad_usb->widget; widget_add_button_element( - widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context); + widget, GuiButtonTypeLeft, "Cancel", bad_usb_scene_confirm_unpair_widget_callback, context); widget_add_button_element( widget, GuiButtonTypeRight, "Unpair", - bad_ble_scene_confirm_unpair_widget_callback, + bad_usb_scene_confirm_unpair_widget_callback, context); widget_add_text_box_element( widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false); - view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget); + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewWidget); } -bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) { - BadBleApp* bad_ble = context; - SceneManager* scene_manager = bad_ble->scene_manager; +bool bad_usb_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + SceneManager* scene_manager = bad_usb->scene_manager; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone); + scene_manager_next_scene(scene_manager, BadUsbSceneUnpairDone); } else if(event.event == GuiButtonTypeLeft) { scene_manager_previous_scene(scene_manager); } @@ -45,9 +45,9 @@ bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent even return consumed; } -void bad_ble_scene_confirm_unpair_on_exit(void* context) { - BadBleApp* bad_ble = context; - Widget* widget = bad_ble->widget; +void bad_usb_scene_confirm_unpair_on_exit(void* context) { + BadUsbApp* bad_usb = context; + Widget* widget = bad_usb->widget; widget_reset(widget); } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_error.c b/applications/main/bad_usb/scenes/bad_usb_scene_error.c index 9d3a44f12f..ea7f797671 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_error.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_error.c @@ -43,7 +43,7 @@ void bad_usb_scene_error_on_enter(void* context) { "Disconnect from\nPC or phone to\nuse this function."); } - view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewError); + view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWidget); } bool bad_usb_scene_error_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c b/applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c new file mode 100644 index 0000000000..9583f9bfba --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c @@ -0,0 +1,39 @@ +#include "../bad_usb_app_i.h" + +static void bad_usb_scene_unpair_done_popup_callback(void* context) { + BadUsbApp* bad_usb = context; + scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneConfig); +} + +void bad_usb_scene_unpair_done_on_enter(void* context) { + BadUsbApp* bad_usb = context; + Popup* popup = bad_usb->popup; + + bad_usb_hid_ble_remove_pairing(); + + popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58); + popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom); + popup_set_callback(popup, bad_usb_scene_unpair_done_popup_callback); + popup_set_context(popup, bad_usb); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewPopup); +} + +bool bad_usb_scene_unpair_done_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + UNUSED(bad_usb); + UNUSED(event); + bool consumed = false; + + return consumed; +} + +void bad_usb_scene_unpair_done_on_exit(void* context) { + BadUsbApp* bad_usb = context; + Popup* popup = bad_usb->popup; + UNUSED(popup); + + popup_reset(popup); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 0afc056b62..0382b0f8ea 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -20,14 +20,27 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { bad_usb_script_close(app->bad_usb_script); app->bad_usb_script = NULL; - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); + if(app->interface == BadUsbHidInterfaceBle) { + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + } else { + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); + } } consumed = true; } else if(event.event == InputKeyOk) { bad_usb_script_start_stop(app->bad_usb_script); consumed = true; } else if(event.event == InputKeyRight) { - bad_usb_script_pause_resume(app->bad_usb_script); + if(bad_usb_view_is_idle_state(app->bad_usb_view)) { + bad_usb_set_interface( + app, + app->interface == BadUsbHidInterfaceBle ? BadUsbHidInterfaceUsb : + BadUsbHidInterfaceBle); + bad_usb_script_close(app->bad_usb_script); + app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); + } else { + bad_usb_script_pause_resume(app->bad_usb_script); + } consumed = true; } } else if(event.type == SceneManagerEventTypeTick) { @@ -39,7 +52,9 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { void bad_usb_scene_work_on_enter(void* context) { BadUsbApp* app = context; - app->bad_usb_script = bad_usb_script_open(app->file_path); + bad_usb_view_set_interface(app->bad_usb_view, app->interface); + + app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); FuriString* file_name; diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 7fb0b1434e..1a6f77958d 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -18,6 +18,7 @@ typedef struct { BadUsbState state; bool pause_wait; uint8_t anim_frame; + BadUsbHidInterface interface; } BadUsbModel; static void bad_usb_draw_callback(Canvas* canvas, void* _model) { @@ -40,14 +41,24 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { furi_string_reset(disp_str); - canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); + if(model->interface == BadUsbHidInterfaceBle) { + canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22); + } else { + canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); + } BadUsbWorkerState state = model->state.state; if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || (state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); - elements_button_left(canvas, "Layout"); + if(model->interface == BadUsbHidInterfaceBle) { + elements_button_right(canvas, "USB"); + elements_button_left(canvas, "Config"); + } else { + elements_button_right(canvas, "BLE"); + elements_button_left(canvas, "Layout"); + } } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); if(!model->pause_wait) { @@ -266,6 +277,10 @@ void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st) { true); } +void bad_usb_view_set_interface(BadUsb* bad_usb, BadUsbHidInterface interface) { + with_view_model(bad_usb->view, BadUsbModel * model, { model->interface = interface; }, true); +} + bool bad_usb_view_is_idle_state(BadUsb* bad_usb) { bool is_idle = false; with_view_model( diff --git a/applications/main/bad_usb/views/bad_usb_view.h b/applications/main/bad_usb/views/bad_usb_view.h index 45f13d650b..bca2f4bc08 100644 --- a/applications/main/bad_usb/views/bad_usb_view.h +++ b/applications/main/bad_usb/views/bad_usb_view.h @@ -23,4 +23,6 @@ void bad_usb_view_set_layout(BadUsb* bad_usb, const char* layout); void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st); +void bad_usb_view_set_interface(BadUsb* bad_usb, BadUsbHidInterface interface); + bool bad_usb_view_is_idle_state(BadUsb* bad_usb); diff --git a/applications/system/bad_ble/application.fam b/applications/system/bad_ble/application.fam deleted file mode 100644 index e00e6eefcc..0000000000 --- a/applications/system/bad_ble/application.fam +++ /dev/null @@ -1,12 +0,0 @@ -App( - appid="bad_ble", - name="Bad BLE", - apptype=FlipperAppType.EXTERNAL, - entry_point="bad_ble_app", - stack_size=2 * 1024, - icon="A_BadUsb_14", - fap_libs=["assets", "ble_profile"], - fap_icon="icon.png", - fap_icon_assets="assets", - fap_category="Bluetooth", -) diff --git a/applications/system/bad_ble/bad_ble_app.c b/applications/system/bad_ble/bad_ble_app.c deleted file mode 100644 index f243371986..0000000000 --- a/applications/system/bad_ble/bad_ble_app.c +++ /dev/null @@ -1,196 +0,0 @@ -#include "bad_ble_app_i.h" -#include -#include -#include -#include -#include - -#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/.badble.settings" -#define BAD_BLE_SETTINGS_FILE_TYPE "Flipper BadBLE Settings File" -#define BAD_BLE_SETTINGS_VERSION 1 -#define BAD_BLE_SETTINGS_DEFAULT_LAYOUT BAD_BLE_APP_PATH_LAYOUT_FOLDER "/en-US.kl" - -static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - BadBleApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool bad_ble_app_back_event_callback(void* context) { - furi_assert(context); - BadBleApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void bad_ble_app_tick_event_callback(void* context) { - furi_assert(context); - BadBleApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -static void bad_ble_load_settings(BadBleApp* app) { - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff = flipper_format_file_alloc(storage); - bool state = false; - - FuriString* temp_str = furi_string_alloc(); - uint32_t version = 0; - - if(flipper_format_file_open_existing(fff, BAD_BLE_SETTINGS_PATH)) { - do { - if(!flipper_format_read_header(fff, temp_str, &version)) break; - if((strcmp(furi_string_get_cstr(temp_str), BAD_BLE_SETTINGS_FILE_TYPE) != 0) || - (version != BAD_BLE_SETTINGS_VERSION)) - break; - - if(!flipper_format_read_string(fff, "layout", temp_str)) break; - - state = true; - } while(0); - } - flipper_format_free(fff); - furi_record_close(RECORD_STORAGE); - - if(state) { - furi_string_set(app->keyboard_layout, temp_str); - - Storage* fs_api = furi_record_open(RECORD_STORAGE); - FileInfo layout_file_info; - FS_Error file_check_err = storage_common_stat( - fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); - furi_record_close(RECORD_STORAGE); - if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) { - furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); - } - } else { - furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); - } - - furi_string_free(temp_str); -} - -static void bad_ble_save_settings(BadBleApp* app) { - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff = flipper_format_file_alloc(storage); - - if(flipper_format_file_open_always(fff, BAD_BLE_SETTINGS_PATH)) { - do { - if(!flipper_format_write_header_cstr( - fff, BAD_BLE_SETTINGS_FILE_TYPE, BAD_BLE_SETTINGS_VERSION)) - break; - if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; - } while(0); - } - - flipper_format_free(fff); - furi_record_close(RECORD_STORAGE); -} - -BadBleApp* bad_ble_app_alloc(char* arg) { - BadBleApp* app = malloc(sizeof(BadBleApp)); - - app->bad_ble_script = NULL; - - app->file_path = furi_string_alloc(); - app->keyboard_layout = furi_string_alloc(); - if(arg && strlen(arg)) { - furi_string_set(app->file_path, arg); - } - - bad_ble_load_settings(app); - - app->gui = furi_record_open(RECORD_GUI); - app->notifications = furi_record_open(RECORD_NOTIFICATION); - app->dialogs = furi_record_open(RECORD_DIALOGS); - - app->view_dispatcher = view_dispatcher_alloc(); - app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app); - - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, bad_ble_app_tick_event_callback, 500); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, bad_ble_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, bad_ble_app_back_event_callback); - - // Custom Widget - app->widget = widget_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, BadBleAppViewWidget, widget_get_view(app->widget)); - - // Popup - app->popup = popup_alloc(); - view_dispatcher_add_view(app->view_dispatcher, BadBleAppViewPopup, popup_get_view(app->popup)); - - app->var_item_list = variable_item_list_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - BadBleAppViewConfig, - variable_item_list_get_view(app->var_item_list)); - - app->bad_ble_view = bad_ble_view_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, BadBleAppViewWork, bad_ble_view_get_view(app->bad_ble_view)); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - if(!furi_string_empty(app->file_path)) { - scene_manager_next_scene(app->scene_manager, BadBleSceneWork); - } else { - furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER); - scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect); - } - - return app; -} - -void bad_ble_app_free(BadBleApp* app) { - furi_assert(app); - - if(app->bad_ble_script) { - bad_ble_script_close(app->bad_ble_script); - app->bad_ble_script = NULL; - } - - // Views - view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork); - bad_ble_view_free(app->bad_ble_view); - - // Custom Widget - view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWidget); - widget_free(app->widget); - - // Popup - view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewPopup); - popup_free(app->popup); - - // Config menu - view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig); - variable_item_list_free(app->var_item_list); - - // View dispatcher - view_dispatcher_free(app->view_dispatcher); - scene_manager_free(app->scene_manager); - - // Close records - furi_record_close(RECORD_GUI); - furi_record_close(RECORD_NOTIFICATION); - furi_record_close(RECORD_DIALOGS); - - bad_ble_save_settings(app); - - furi_string_free(app->file_path); - furi_string_free(app->keyboard_layout); - - free(app); -} - -int32_t bad_ble_app(void* p) { - BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p); - - view_dispatcher_run(bad_ble_app->view_dispatcher); - - bad_ble_app_free(bad_ble_app); - return 0; -} diff --git a/applications/system/bad_ble/bad_ble_app.h b/applications/system/bad_ble/bad_ble_app.h deleted file mode 100644 index 11954836e5..0000000000 --- a/applications/system/bad_ble/bad_ble_app.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct BadBleApp BadBleApp; - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/bad_ble/bad_ble_app_i.h b/applications/system/bad_ble/bad_ble_app_i.h deleted file mode 100644 index d1f739bebb..0000000000 --- a/applications/system/bad_ble/bad_ble_app_i.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "bad_ble_app.h" -#include "scenes/bad_ble_scene.h" -#include "helpers/ducky_script.h" -#include "helpers/bad_ble_hid.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "views/bad_ble_view.h" - -#define BAD_BLE_APP_BASE_FOLDER EXT_PATH("badusb") -#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/assets/layouts" -#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt" -#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl" - -typedef enum { - BadBleAppErrorNoFiles, - BadBleAppErrorCloseRpc, -} BadBleAppError; - -struct BadBleApp { - Gui* gui; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - NotificationApp* notifications; - DialogsApp* dialogs; - Widget* widget; - Popup* popup; - VariableItemList* var_item_list; - - BadBleAppError error; - FuriString* file_path; - FuriString* keyboard_layout; - BadBle* bad_ble_view; - BadBleScript* bad_ble_script; - - BadBleHidInterface interface; -}; - -typedef enum { - BadBleAppViewWidget, - BadBleAppViewPopup, - BadBleAppViewWork, - BadBleAppViewConfig, -} BadBleAppView; diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.c b/applications/system/bad_ble/helpers/bad_ble_hid.c deleted file mode 100644 index c34b3c6461..0000000000 --- a/applications/system/bad_ble/helpers/bad_ble_hid.c +++ /dev/null @@ -1,157 +0,0 @@ -#include "bad_ble_hid.h" -#include -#include -#include - -#define TAG "BadBLE HID" - -#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" - -typedef struct { - Bt* bt; - FuriHalBleProfileBase* profile; - HidStateCallback state_callback; - void* callback_context; - bool is_connected; -} BleHidInstance; - -static const BleProfileHidParams ble_hid_params = { - .device_name_prefix = "BadBLE", - .mac_xor = 0x0002, -}; - -static void hid_ble_connection_status_callback(BtStatus status, void* context) { - furi_assert(context); - BleHidInstance* ble_hid = context; - ble_hid->is_connected = (status == BtStatusConnected); - if(ble_hid->state_callback) { - ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); - } -} - -void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { - UNUSED(hid_cfg); - BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); - ble_hid->bt = furi_record_open(RECORD_BT); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - - ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); - furi_check(ble_hid->profile); - - furi_hal_bt_start_advertising(); - - bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); - - return ble_hid; -} - -void hid_ble_deinit(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - - bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(ble_hid->bt); - - furi_check(bt_profile_restore_default(ble_hid->bt)); - furi_record_close(RECORD_BT); - free(ble_hid); -} - -void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - ble_hid->state_callback = cb; - ble_hid->callback_context = context; -} - -bool hid_ble_is_connected(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_hid->is_connected; -} - -bool hid_ble_kb_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_press(ble_hid->profile, button); -} - -bool hid_ble_kb_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_release(ble_hid->profile, button); -} - -bool hid_ble_consumer_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_press(ble_hid->profile, button); -} - -bool hid_ble_consumer_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_release(ble_hid->profile, button); -} - -bool hid_ble_release_all(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - bool state = ble_profile_hid_kb_release_all(ble_hid->profile); - state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); - return state; -} - -uint8_t hid_ble_get_led_state(void* inst) { - UNUSED(inst); - FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); - return 0; -} - -static const BadBleHidApi hid_api_ble = { - .init = hid_ble_init, - .deinit = hid_ble_deinit, - .set_state_callback = hid_ble_set_state_callback, - .is_connected = hid_ble_is_connected, - - .kb_press = hid_ble_kb_press, - .kb_release = hid_ble_kb_release, - .consumer_press = hid_ble_consumer_press, - .consumer_release = hid_ble_consumer_release, - .release_all = hid_ble_release_all, - .get_led_state = hid_ble_get_led_state, -}; - -const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface) { - UNUSED(interface); - return &hid_api_ble; -} - -void bad_ble_hid_ble_remove_pairing(void) { - Bt* bt = furi_record_open(RECORD_BT); - bt_disconnect(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - furi_hal_bt_stop_advertising(); - - bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - bt_forget_bonded_devices(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(bt); - - furi_check(bt_profile_restore_default(bt)); - furi_record_close(RECORD_BT); -} diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.h b/applications/system/bad_ble/helpers/bad_ble_hid.h deleted file mode 100644 index b06385d6dd..0000000000 --- a/applications/system/bad_ble/helpers/bad_ble_hid.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -typedef enum { - BadBleHidInterfaceBle, -} BadBleHidInterface; - -typedef struct { - void* (*init)(FuriHalUsbHidConfig* hid_cfg); - void (*deinit)(void* inst); - void (*set_state_callback)(void* inst, HidStateCallback cb, void* context); - bool (*is_connected)(void* inst); - - bool (*kb_press)(void* inst, uint16_t button); - bool (*kb_release)(void* inst, uint16_t button); - bool (*consumer_press)(void* inst, uint16_t button); - bool (*consumer_release)(void* inst, uint16_t button); - bool (*release_all)(void* inst); - uint8_t (*get_led_state)(void* inst); -} BadBleHidApi; - -const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface); - -void bad_ble_hid_ble_remove_pairing(void); - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/bad_ble/helpers/ducky_script.c b/applications/system/bad_ble/helpers/ducky_script.c deleted file mode 100644 index a903fbdc4d..0000000000 --- a/applications/system/bad_ble/helpers/ducky_script.c +++ /dev/null @@ -1,716 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "ducky_script.h" -#include "ducky_script_i.h" -#include - -#define TAG "BadBle" - -#define WORKER_TAG TAG "Worker" - -#define BADUSB_ASCII_TO_KEY(script, x) \ - (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) - -typedef enum { - WorkerEvtStartStop = (1 << 0), - WorkerEvtPauseResume = (1 << 1), - WorkerEvtEnd = (1 << 2), - WorkerEvtConnect = (1 << 3), - WorkerEvtDisconnect = (1 << 4), -} WorkerEvtFlags; - -static const char ducky_cmd_id[] = {"ID"}; - -static const uint8_t numpad_keys[10] = { - HID_KEYPAD_0, - HID_KEYPAD_1, - HID_KEYPAD_2, - HID_KEYPAD_3, - HID_KEYPAD_4, - HID_KEYPAD_5, - HID_KEYPAD_6, - HID_KEYPAD_7, - HID_KEYPAD_8, - HID_KEYPAD_9, -}; - -uint32_t ducky_get_command_len(const char* line) { - uint32_t len = strlen(line); - for(uint32_t i = 0; i < len; i++) { - if(line[i] == ' ') return i; - } - return 0; -} - -bool ducky_is_line_end(const char chr) { - return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'); -} - -uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) { - uint16_t keycode = ducky_get_keycode_by_name(param); - if(keycode != HID_KEYBOARD_NONE) { - return keycode; - } - - if((accept_chars) && (strlen(param) > 0)) { - return BADUSB_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF; - } - return 0; -} - -bool ducky_get_number(const char* param, uint32_t* val) { - uint32_t value = 0; - if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) { - *val = value; - return true; - } - return false; -} - -void ducky_numlock_on(BadBleScript* bad_ble) { - if((bad_ble->hid->get_led_state(bad_ble->hid_inst) & HID_KB_LED_NUM) == 0) { - bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); - bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); - } -} - -bool ducky_numpad_press(BadBleScript* bad_ble, const char num) { - if((num < '0') || (num > '9')) return false; - - uint16_t key = numpad_keys[num - '0']; - bad_ble->hid->kb_press(bad_ble->hid_inst, key); - bad_ble->hid->kb_release(bad_ble->hid_inst, key); - - return true; -} - -bool ducky_altchar(BadBleScript* bad_ble, const char* charcode) { - uint8_t i = 0; - bool state = false; - - bad_ble->hid->kb_press(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); - - while(!ducky_is_line_end(charcode[i])) { - state = ducky_numpad_press(bad_ble, charcode[i]); - if(state == false) break; - i++; - } - - bad_ble->hid->kb_release(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); - return state; -} - -bool ducky_altstring(BadBleScript* bad_ble, const char* param) { - uint32_t i = 0; - bool state = false; - - while(param[i] != '\0') { - if((param[i] < ' ') || (param[i] > '~')) { - i++; - continue; // Skip non-printable chars - } - - char temp_str[4]; - snprintf(temp_str, 4, "%u", param[i]); - - state = ducky_altchar(bad_ble, temp_str); - if(state == false) break; - i++; - } - return state; -} - -int32_t ducky_error(BadBleScript* bad_ble, const char* text, ...) { - va_list args; - va_start(args, text); - - vsnprintf(bad_ble->st.error, sizeof(bad_ble->st.error), text, args); - - va_end(args); - return SCRIPT_STATE_ERROR; -} - -bool ducky_string(BadBleScript* bad_ble, const char* param) { - uint32_t i = 0; - - while(param[i] != '\0') { - if(param[i] != '\n') { - uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, param[i]); - if(keycode != HID_KEYBOARD_NONE) { - bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); - bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); - } - } else { - bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); - bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); - } - i++; - } - bad_ble->stringdelay = 0; - return true; -} - -static bool ducky_string_next(BadBleScript* bad_ble) { - if(bad_ble->string_print_pos >= furi_string_size(bad_ble->string_print)) { - return true; - } - - char print_char = furi_string_get_char(bad_ble->string_print, bad_ble->string_print_pos); - - if(print_char != '\n') { - uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, print_char); - if(keycode != HID_KEYBOARD_NONE) { - bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); - bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); - } - } else { - bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); - bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); - } - - bad_ble->string_print_pos++; - - return false; -} - -static int32_t ducky_parse_line(BadBleScript* bad_ble, FuriString* line) { - uint32_t line_len = furi_string_size(line); - const char* line_tmp = furi_string_get_cstr(line); - - if(line_len == 0) { - return SCRIPT_STATE_NEXT_LINE; // Skip empty lines - } - FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); - - // Ducky Lang Functions - int32_t cmd_result = ducky_execute_cmd(bad_ble, line_tmp); - if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { - return cmd_result; - } - - // Special keys + modifiers - uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_ble, "No keycode defined for %s", line_tmp); - } - if((key & 0xFF00) != 0) { - // It's a modifier key - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - key |= ducky_get_keycode(bad_ble, line_tmp, true); - } - bad_ble->hid->kb_press(bad_ble->hid_inst, key); - bad_ble->hid->kb_release(bad_ble->hid_inst, key); - return 0; -} - -static bool ducky_set_usb_id(BadBleScript* bad_ble, const char* line) { - if(sscanf(line, "%lX:%lX", &bad_ble->hid_cfg.vid, &bad_ble->hid_cfg.pid) == 2) { - bad_ble->hid_cfg.manuf[0] = '\0'; - bad_ble->hid_cfg.product[0] = '\0'; - - uint8_t id_len = ducky_get_command_len(line); - if(!ducky_is_line_end(line[id_len + 1])) { - sscanf( - &line[id_len + 1], - "%31[^\r\n:]:%31[^\r\n]", - bad_ble->hid_cfg.manuf, - bad_ble->hid_cfg.product); - } - FURI_LOG_D( - WORKER_TAG, - "set id: %04lX:%04lX mfr:%s product:%s", - bad_ble->hid_cfg.vid, - bad_ble->hid_cfg.pid, - bad_ble->hid_cfg.manuf, - bad_ble->hid_cfg.product); - return true; - } - return false; -} - -static void bad_ble_hid_state_callback(bool state, void* context) { - furi_assert(context); - BadBleScript* bad_ble = context; - - if(state == true) { - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect); - } else { - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect); - } -} - -static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) { - uint8_t ret = 0; - uint32_t line_len = 0; - - furi_string_reset(bad_ble->line); - - do { - ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); - for(uint16_t i = 0; i < ret; i++) { - if(bad_ble->file_buf[i] == '\n' && line_len > 0) { - bad_ble->st.line_nb++; - line_len = 0; - } else { - if(bad_ble->st.line_nb == 0) { // Save first line - furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); - } - line_len++; - } - } - if(storage_file_eof(script_file)) { - if(line_len > 0) { - bad_ble->st.line_nb++; - break; - } - } - } while(ret > 0); - - const char* line_tmp = furi_string_get_cstr(bad_ble->line); - bool id_set = false; // Looking for ID command at first line - if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { - id_set = ducky_set_usb_id(bad_ble, &line_tmp[strlen(ducky_cmd_id) + 1]); - } - - if(id_set) { - bad_ble->hid_inst = bad_ble->hid->init(&bad_ble->hid_cfg); - } else { - bad_ble->hid_inst = bad_ble->hid->init(NULL); - } - bad_ble->hid->set_state_callback(bad_ble->hid_inst, bad_ble_hid_state_callback, bad_ble); - - storage_file_seek(script_file, 0, true); - furi_string_reset(bad_ble->line); - - return true; -} - -static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) { - int32_t delay_val = 0; - - if(bad_ble->repeat_cnt > 0) { - bad_ble->repeat_cnt--; - delay_val = ducky_parse_line(bad_ble, bad_ble->line_prev); - if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line - return 0; - } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays - return delay_val; - } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button - return delay_val; - } else if(delay_val < 0) { // Script error - bad_ble->st.error_line = bad_ble->st.line_cur - 1; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur - 1U); - return SCRIPT_STATE_ERROR; - } else { - return delay_val + bad_ble->defdelay; - } - } - - furi_string_set(bad_ble->line_prev, bad_ble->line); - furi_string_reset(bad_ble->line); - - while(1) { - if(bad_ble->buf_len == 0) { - bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); - if(storage_file_eof(script_file)) { - if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) { - bad_ble->file_buf[bad_ble->buf_len] = '\n'; - bad_ble->buf_len++; - bad_ble->file_end = true; - } - } - - bad_ble->buf_start = 0; - if(bad_ble->buf_len == 0) return SCRIPT_STATE_END; - } - for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) { - if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) { - bad_ble->st.line_cur++; - bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1); - bad_ble->buf_start = i + 1; - furi_string_trim(bad_ble->line); - delay_val = ducky_parse_line(bad_ble, bad_ble->line); - if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line - return 0; - } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays - return delay_val; - } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button - return delay_val; - } else if(delay_val < 0) { - bad_ble->st.error_line = bad_ble->st.line_cur; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur); - return SCRIPT_STATE_ERROR; - } else { - return delay_val + bad_ble->defdelay; - } - } else { - furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); - } - } - bad_ble->buf_len = 0; - if(bad_ble->file_end) return SCRIPT_STATE_END; - } - - return 0; -} - -static uint32_t bad_ble_flags_get(uint32_t flags_mask, uint32_t timeout) { - uint32_t flags = furi_thread_flags_get(); - furi_check((flags & FuriFlagError) == 0); - if(flags == 0) { - flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); - furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout)); - } else { - uint32_t state = furi_thread_flags_clear(flags); - furi_check((state & FuriFlagError) == 0); - } - return flags; -} - -static int32_t bad_ble_worker(void* context) { - BadBleScript* bad_ble = context; - - BadBleWorkerState worker_state = BadBleStateInit; - BadBleWorkerState pause_state = BadBleStateRunning; - int32_t delay_val = 0; - - FURI_LOG_I(WORKER_TAG, "Init"); - File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - bad_ble->line = furi_string_alloc(); - bad_ble->line_prev = furi_string_alloc(); - bad_ble->string_print = furi_string_alloc(); - - while(1) { - if(worker_state == BadBleStateInit) { // State: initialization - if(storage_file_open( - script_file, - furi_string_get_cstr(bad_ble->file_path), - FSAM_READ, - FSOM_OPEN_EXISTING)) { - if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) { - if(bad_ble->hid->is_connected(bad_ble->hid_inst)) { - worker_state = BadBleStateIdle; // Ready to run - } else { - worker_state = BadBleStateNotConnected; // USB not connected - } - } else { - worker_state = BadBleStateScriptError; // Script preload error - } - } else { - FURI_LOG_E(WORKER_TAG, "File open error"); - worker_state = BadBleStateFileError; // File open error - } - bad_ble->st.state = worker_state; - - } else if(worker_state == BadBleStateNotConnected) { // State: USB not connected - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, - FuriWaitForever); - - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtConnect) { - worker_state = BadBleStateIdle; // Ready to run - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateWillRun; // Will run when USB is connected - } - bad_ble->st.state = worker_state; - - } else if(worker_state == BadBleStateIdle) { // State: ready to start - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); - - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { // Start executing script - dolphin_deed(DolphinDeedBadUsbPlayScript); - delay_val = 0; - bad_ble->buf_len = 0; - bad_ble->st.line_cur = 0; - bad_ble->defdelay = 0; - bad_ble->stringdelay = 0; - bad_ble->defstringdelay = 0; - bad_ble->repeat_cnt = 0; - bad_ble->key_hold_nb = 0; - bad_ble->file_end = false; - storage_file_seek(script_file, 0, true); - worker_state = BadBleStateRunning; - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - } - bad_ble->st.state = worker_state; - - } else if(worker_state == BadBleStateWillRun) { // State: start on connection - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); - - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtConnect) { // Start executing script - dolphin_deed(DolphinDeedBadUsbPlayScript); - delay_val = 0; - bad_ble->buf_len = 0; - bad_ble->st.line_cur = 0; - bad_ble->defdelay = 0; - bad_ble->stringdelay = 0; - bad_ble->defstringdelay = 0; - bad_ble->repeat_cnt = 0; - bad_ble->file_end = false; - storage_file_seek(script_file, 0, true); - // extra time for PC to recognize Flipper as keyboard - flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, - FuriFlagWaitAny | FuriFlagNoClear, - 1500); - if(flags == (unsigned)FuriFlagErrorTimeout) { - // If nothing happened - start script execution - worker_state = BadBleStateRunning; - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateIdle; - furi_thread_flags_clear(WorkerEvtStartStop); - } - } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution - worker_state = BadBleStateNotConnected; - } - bad_ble->st.state = worker_state; - - } else if(worker_state == BadBleStateRunning) { // State: running - uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); - uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - FuriFlagWaitAny, - delay_cur); - - delay_val -= delay_cur; - if(!(flags & FuriFlagError)) { - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateIdle; // Stop executing script - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtPauseResume) { - pause_state = BadBleStateRunning; - worker_state = BadBleStatePaused; // Pause - } - bad_ble->st.state = worker_state; - continue; - } else if( - (flags == (unsigned)FuriFlagErrorTimeout) || - (flags == (unsigned)FuriFlagErrorResource)) { - if(delay_val > 0) { - bad_ble->st.delay_remain--; - continue; - } - bad_ble->st.state = BadBleStateRunning; - delay_val = ducky_script_execute_next(bad_ble, script_file); - if(delay_val == SCRIPT_STATE_ERROR) { // Script error - delay_val = 0; - worker_state = BadBleStateScriptError; - bad_ble->st.state = worker_state; - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(delay_val == SCRIPT_STATE_END) { // End of script - delay_val = 0; - worker_state = BadBleStateIdle; - bad_ble->st.state = BadBleStateDone; - bad_ble->hid->release_all(bad_ble->hid_inst); - continue; - } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays - delay_val = bad_ble->defdelay; - bad_ble->string_print_pos = 0; - worker_state = BadBleStateStringDelay; - } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input - worker_state = BadBleStateWaitForBtn; - bad_ble->st.state = BadBleStateWaitForBtn; // Show long delays - } else if(delay_val > 1000) { - bad_ble->st.state = BadBleStateDelay; // Show long delays - bad_ble->st.delay_remain = delay_val / 1000; - } - } else { - furi_check((flags & FuriFlagError) == 0); - } - } else if(worker_state == BadBleStateWaitForBtn) { // State: Wait for button Press - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - FuriWaitForever); - if(!(flags & FuriFlagError)) { - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { - delay_val = 0; - worker_state = BadBleStateRunning; - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - bad_ble->hid->release_all(bad_ble->hid_inst); - } - bad_ble->st.state = worker_state; - continue; - } - } else if(worker_state == BadBleStatePaused) { // State: Paused - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - FuriWaitForever); - if(!(flags & FuriFlagError)) { - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateIdle; // Stop executing script - bad_ble->st.state = worker_state; - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - bad_ble->st.state = worker_state; - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtPauseResume) { - if(pause_state == BadBleStateRunning) { - if(delay_val > 0) { - bad_ble->st.state = BadBleStateDelay; - bad_ble->st.delay_remain = delay_val / 1000; - } else { - bad_ble->st.state = BadBleStateRunning; - delay_val = 0; - } - worker_state = BadBleStateRunning; // Resume - } else if(pause_state == BadBleStateStringDelay) { - bad_ble->st.state = BadBleStateRunning; - worker_state = BadBleStateStringDelay; // Resume - } - } - continue; - } - } else if(worker_state == BadBleStateStringDelay) { // State: print string with delays - uint32_t delay = (bad_ble->stringdelay == 0) ? bad_ble->defstringdelay : - bad_ble->stringdelay; - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - delay); - - if(!(flags & FuriFlagError)) { - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateIdle; // Stop executing script - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtPauseResume) { - pause_state = BadBleStateStringDelay; - worker_state = BadBleStatePaused; // Pause - } - bad_ble->st.state = worker_state; - continue; - } else if( - (flags == (unsigned)FuriFlagErrorTimeout) || - (flags == (unsigned)FuriFlagErrorResource)) { - bool string_end = ducky_string_next(bad_ble); - if(string_end) { - bad_ble->stringdelay = 0; - worker_state = BadBleStateRunning; - } - } else { - furi_check((flags & FuriFlagError) == 0); - } - } else if( - (worker_state == BadBleStateFileError) || - (worker_state == BadBleStateScriptError)) { // State: error - uint32_t flags = - bad_ble_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command - - if(flags & WorkerEvtEnd) { - break; - } - } - } - - bad_ble->hid->set_state_callback(bad_ble->hid_inst, NULL, NULL); - bad_ble->hid->deinit(bad_ble->hid_inst); - - storage_file_close(script_file); - storage_file_free(script_file); - furi_string_free(bad_ble->line); - furi_string_free(bad_ble->line_prev); - furi_string_free(bad_ble->string_print); - - FURI_LOG_I(WORKER_TAG, "End"); - - return 0; -} - -static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) { - furi_assert(bad_ble); - memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout)); - memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout))); -} - -BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface) { - furi_assert(file_path); - - BadBleScript* bad_ble = malloc(sizeof(BadBleScript)); - bad_ble->file_path = furi_string_alloc(); - furi_string_set(bad_ble->file_path, file_path); - bad_ble_script_set_default_keyboard_layout(bad_ble); - - bad_ble->st.state = BadBleStateInit; - bad_ble->st.error[0] = '\0'; - bad_ble->hid = bad_ble_hid_get_interface(interface); - - bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble); - furi_thread_start(bad_ble->thread); - return bad_ble; -} //-V773 - -void bad_ble_script_close(BadBleScript* bad_ble) { - furi_assert(bad_ble); - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd); - furi_thread_join(bad_ble->thread); - furi_thread_free(bad_ble->thread); - furi_string_free(bad_ble->file_path); - free(bad_ble); -} - -void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) { - furi_assert(bad_ble); - - if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) { - // do not update keyboard layout while a script is running - return; - } - - File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - if(!furi_string_empty(layout_path)) { //-V1051 - if(storage_file_open( - layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { - uint16_t layout[128]; - if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { - memcpy(bad_ble->layout, layout, sizeof(layout)); - } - } - storage_file_close(layout_file); - } else { - bad_ble_script_set_default_keyboard_layout(bad_ble); - } - storage_file_free(layout_file); -} - -void bad_ble_script_start_stop(BadBleScript* bad_ble) { - furi_assert(bad_ble); - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtStartStop); -} - -void bad_ble_script_pause_resume(BadBleScript* bad_ble) { - furi_assert(bad_ble); - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtPauseResume); -} - -BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) { - furi_assert(bad_ble); - return &(bad_ble->st); -} diff --git a/applications/system/bad_ble/helpers/ducky_script.h b/applications/system/bad_ble/helpers/ducky_script.h deleted file mode 100644 index 044cae8256..0000000000 --- a/applications/system/bad_ble/helpers/ducky_script.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include "bad_ble_hid.h" - -typedef enum { - BadBleStateInit, - BadBleStateNotConnected, - BadBleStateIdle, - BadBleStateWillRun, - BadBleStateRunning, - BadBleStateDelay, - BadBleStateStringDelay, - BadBleStateWaitForBtn, - BadBleStatePaused, - BadBleStateDone, - BadBleStateScriptError, - BadBleStateFileError, -} BadBleWorkerState; - -typedef struct { - BadBleWorkerState state; - size_t line_cur; - size_t line_nb; - uint32_t delay_remain; - size_t error_line; - char error[64]; -} BadBleState; - -typedef struct BadBleScript BadBleScript; - -BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface); - -void bad_ble_script_close(BadBleScript* bad_ble); - -void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path); - -void bad_ble_script_start(BadBleScript* bad_ble); - -void bad_ble_script_stop(BadBleScript* bad_ble); - -void bad_ble_script_start_stop(BadBleScript* bad_ble); - -void bad_ble_script_pause_resume(BadBleScript* bad_ble); - -BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble); - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_commands.c b/applications/system/bad_ble/helpers/ducky_script_commands.c deleted file mode 100644 index f70c5eba40..0000000000 --- a/applications/system/bad_ble/helpers/ducky_script_commands.c +++ /dev/null @@ -1,241 +0,0 @@ -#include -#include "ducky_script.h" -#include "ducky_script_i.h" - -typedef int32_t (*DuckyCmdCallback)(BadBleScript* bad_usb, const char* line, int32_t param); - -typedef struct { - char* name; - DuckyCmdCallback callback; - int32_t param; -} DuckyCmd; - -static int32_t ducky_fnc_delay(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint32_t delay_val = 0; - bool state = ducky_get_number(line, &delay_val); - if((state) && (delay_val > 0)) { - return (int32_t)delay_val; - } - - return ducky_error(bad_usb, "Invalid number %s", line); -} - -static int32_t ducky_fnc_defdelay(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - bool state = ducky_get_number(line, &bad_usb->defdelay); - if(!state) { - return ducky_error(bad_usb, "Invalid number %s", line); - } - return 0; -} - -static int32_t ducky_fnc_strdelay(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - bool state = ducky_get_number(line, &bad_usb->stringdelay); - if(!state) { - return ducky_error(bad_usb, "Invalid number %s", line); - } - return 0; -} - -static int32_t ducky_fnc_defstrdelay(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - bool state = ducky_get_number(line, &bad_usb->defstringdelay); - if(!state) { - return ducky_error(bad_usb, "Invalid number %s", line); - } - return 0; -} - -static int32_t ducky_fnc_string(BadBleScript* bad_usb, const char* line, int32_t param) { - line = &line[ducky_get_command_len(line) + 1]; - furi_string_set_str(bad_usb->string_print, line); - if(param == 1) { - furi_string_cat(bad_usb->string_print, "\n"); - } - - if(bad_usb->stringdelay == 0 && - bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately - bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); - if(!state) { - return ducky_error(bad_usb, "Invalid string %s", line); - } - } else { // stringdelay is set - run command in thread to keep handling external events - return SCRIPT_STATE_STRING_START; - } - - return 0; -} - -static int32_t ducky_fnc_repeat(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - bool state = ducky_get_number(line, &bad_usb->repeat_cnt); - if((!state) || (bad_usb->repeat_cnt == 0)) { - return ducky_error(bad_usb, "Invalid number %s", line); - } - return 0; -} - -static int32_t ducky_fnc_sysrq(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); - bad_usb->hid->kb_press(bad_usb->hid_inst, key); - bad_usb->hid->release_all(bad_usb->hid_inst); - return 0; -} - -static int32_t ducky_fnc_altchar(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - ducky_numlock_on(bad_usb); - bool state = ducky_altchar(bad_usb, line); - if(!state) { - return ducky_error(bad_usb, "Invalid altchar %s", line); - } - return 0; -} - -static int32_t ducky_fnc_altstring(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - ducky_numlock_on(bad_usb); - bool state = ducky_altstring(bad_usb, line); - if(!state) { - return ducky_error(bad_usb, "Invalid altstring %s", line); - } - return 0; -} - -static int32_t ducky_fnc_hold(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - bad_usb->key_hold_nb++; - if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { - return ducky_error(bad_usb, "Too many keys are hold"); - } - bad_usb->hid->kb_press(bad_usb->hid_inst, key); - return 0; -} - -static int32_t ducky_fnc_release(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - if(bad_usb->key_hold_nb == 0) { - return ducky_error(bad_usb, "No keys are hold"); - } - bad_usb->key_hold_nb--; - bad_usb->hid->kb_release(bad_usb->hid_inst, key); - return 0; -} - -static int32_t ducky_fnc_media(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_media_keycode_by_name(line); - if(key == HID_CONSUMER_UNASSIGNED) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - bad_usb->hid->consumer_press(bad_usb->hid_inst, key); - bad_usb->hid->consumer_release(bad_usb->hid_inst, key); - return 0; -} - -static int32_t ducky_fnc_globe(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - - bad_usb->hid->consumer_press(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); - bad_usb->hid->kb_press(bad_usb->hid_inst, key); - bad_usb->hid->kb_release(bad_usb->hid_inst, key); - bad_usb->hid->consumer_release(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); - return 0; -} - -static int32_t ducky_fnc_waitforbutton(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - UNUSED(bad_usb); - UNUSED(line); - - return SCRIPT_STATE_WAIT_FOR_BTN; -} - -static const DuckyCmd ducky_commands[] = { - {"REM", NULL, -1}, - {"ID", NULL, -1}, - {"DELAY", ducky_fnc_delay, -1}, - {"STRING", ducky_fnc_string, 0}, - {"STRINGLN", ducky_fnc_string, 1}, - {"DEFAULT_DELAY", ducky_fnc_defdelay, -1}, - {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, - {"STRINGDELAY", ducky_fnc_strdelay, -1}, - {"STRING_DELAY", ducky_fnc_strdelay, -1}, - {"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1}, - {"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -1}, - {"REPEAT", ducky_fnc_repeat, -1}, - {"SYSRQ", ducky_fnc_sysrq, -1}, - {"ALTCHAR", ducky_fnc_altchar, -1}, - {"ALTSTRING", ducky_fnc_altstring, -1}, - {"ALTCODE", ducky_fnc_altstring, -1}, - {"HOLD", ducky_fnc_hold, -1}, - {"RELEASE", ducky_fnc_release, -1}, - {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, - {"MEDIA", ducky_fnc_media, -1}, - {"GLOBE", ducky_fnc_globe, -1}, -}; - -#define TAG "BadBle" - -#define WORKER_TAG TAG "Worker" - -int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line) { - size_t cmd_word_len = strcspn(line, " "); - for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { - size_t cmd_compare_len = strlen(ducky_commands[i].name); - - if(cmd_compare_len != cmd_word_len) { - continue; - } - - if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) { - if(ducky_commands[i].callback == NULL) { - return 0; - } else { - return (ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param); - } - } - } - - return SCRIPT_STATE_CMD_UNKNOWN; -} diff --git a/applications/system/bad_ble/helpers/ducky_script_i.h b/applications/system/bad_ble/helpers/ducky_script_i.h deleted file mode 100644 index a5581d2065..0000000000 --- a/applications/system/bad_ble/helpers/ducky_script_i.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include "ducky_script.h" -#include "bad_ble_hid.h" - -#define SCRIPT_STATE_ERROR (-1) -#define SCRIPT_STATE_END (-2) -#define SCRIPT_STATE_NEXT_LINE (-3) -#define SCRIPT_STATE_CMD_UNKNOWN (-4) -#define SCRIPT_STATE_STRING_START (-5) -#define SCRIPT_STATE_WAIT_FOR_BTN (-6) - -#define FILE_BUFFER_LEN 16 - -struct BadBleScript { - FuriHalUsbHidConfig hid_cfg; - const BadBleHidApi* hid; - void* hid_inst; - FuriThread* thread; - BadBleState st; - - FuriString* file_path; - uint8_t file_buf[FILE_BUFFER_LEN + 1]; - uint8_t buf_start; - uint8_t buf_len; - bool file_end; - - uint32_t defdelay; - uint32_t stringdelay; - uint32_t defstringdelay; - uint16_t layout[128]; - - FuriString* line; - FuriString* line_prev; - uint32_t repeat_cnt; - uint8_t key_hold_nb; - - FuriString* string_print; - size_t string_print_pos; -}; - -uint16_t ducky_get_keycode(BadBleScript* bad_usb, const char* param, bool accept_chars); - -uint32_t ducky_get_command_len(const char* line); - -bool ducky_is_line_end(const char chr); - -uint16_t ducky_get_keycode_by_name(const char* param); - -uint16_t ducky_get_media_keycode_by_name(const char* param); - -bool ducky_get_number(const char* param, uint32_t* val); - -void ducky_numlock_on(BadBleScript* bad_usb); - -bool ducky_numpad_press(BadBleScript* bad_usb, const char num); - -bool ducky_altchar(BadBleScript* bad_usb, const char* charcode); - -bool ducky_altstring(BadBleScript* bad_usb, const char* param); - -bool ducky_string(BadBleScript* bad_usb, const char* param); - -int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line); - -int32_t ducky_error(BadBleScript* bad_usb, const char* text, ...); - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_keycodes.c b/applications/system/bad_ble/helpers/ducky_script_keycodes.c deleted file mode 100644 index 290618c131..0000000000 --- a/applications/system/bad_ble/helpers/ducky_script_keycodes.c +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include "ducky_script_i.h" - -typedef struct { - char* name; - uint16_t keycode; -} DuckyKey; - -static const DuckyKey ducky_keys[] = { - {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, - {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, - {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, - {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, - {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, - {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, - - {"CTRL", KEY_MOD_LEFT_CTRL}, - {"CONTROL", KEY_MOD_LEFT_CTRL}, - {"SHIFT", KEY_MOD_LEFT_SHIFT}, - {"ALT", KEY_MOD_LEFT_ALT}, - {"GUI", KEY_MOD_LEFT_GUI}, - {"WINDOWS", KEY_MOD_LEFT_GUI}, - - {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, - {"DOWN", HID_KEYBOARD_DOWN_ARROW}, - {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, - {"LEFT", HID_KEYBOARD_LEFT_ARROW}, - {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, - {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, - {"UPARROW", HID_KEYBOARD_UP_ARROW}, - {"UP", HID_KEYBOARD_UP_ARROW}, - - {"ENTER", HID_KEYBOARD_RETURN}, - {"BREAK", HID_KEYBOARD_PAUSE}, - {"PAUSE", HID_KEYBOARD_PAUSE}, - {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, - {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, - {"BACKSPACE", HID_KEYBOARD_DELETE}, - {"END", HID_KEYBOARD_END}, - {"ESC", HID_KEYBOARD_ESCAPE}, - {"ESCAPE", HID_KEYBOARD_ESCAPE}, - {"HOME", HID_KEYBOARD_HOME}, - {"INSERT", HID_KEYBOARD_INSERT}, - {"NUMLOCK", HID_KEYPAD_NUMLOCK}, - {"PAGEUP", HID_KEYBOARD_PAGE_UP}, - {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, - {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, - {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, - {"SPACE", HID_KEYBOARD_SPACEBAR}, - {"TAB", HID_KEYBOARD_TAB}, - {"MENU", HID_KEYBOARD_APPLICATION}, - {"APP", HID_KEYBOARD_APPLICATION}, - - {"F1", HID_KEYBOARD_F1}, - {"F2", HID_KEYBOARD_F2}, - {"F3", HID_KEYBOARD_F3}, - {"F4", HID_KEYBOARD_F4}, - {"F5", HID_KEYBOARD_F5}, - {"F6", HID_KEYBOARD_F6}, - {"F7", HID_KEYBOARD_F7}, - {"F8", HID_KEYBOARD_F8}, - {"F9", HID_KEYBOARD_F9}, - {"F10", HID_KEYBOARD_F10}, - {"F11", HID_KEYBOARD_F11}, - {"F12", HID_KEYBOARD_F12}, - {"F13", HID_KEYBOARD_F13}, - {"F14", HID_KEYBOARD_F14}, - {"F15", HID_KEYBOARD_F15}, - {"F16", HID_KEYBOARD_F16}, - {"F17", HID_KEYBOARD_F17}, - {"F18", HID_KEYBOARD_F18}, - {"F19", HID_KEYBOARD_F19}, - {"F20", HID_KEYBOARD_F20}, - {"F21", HID_KEYBOARD_F21}, - {"F22", HID_KEYBOARD_F22}, - {"F23", HID_KEYBOARD_F23}, - {"F24", HID_KEYBOARD_F24}, -}; - -static const DuckyKey ducky_media_keys[] = { - {"POWER", HID_CONSUMER_POWER}, - {"REBOOT", HID_CONSUMER_RESET}, - {"SLEEP", HID_CONSUMER_SLEEP}, - {"LOGOFF", HID_CONSUMER_AL_LOGOFF}, - - {"EXIT", HID_CONSUMER_AC_EXIT}, - {"HOME", HID_CONSUMER_AC_HOME}, - {"BACK", HID_CONSUMER_AC_BACK}, - {"FORWARD", HID_CONSUMER_AC_FORWARD}, - {"REFRESH", HID_CONSUMER_AC_REFRESH}, - - {"SNAPSHOT", HID_CONSUMER_SNAPSHOT}, - - {"PLAY", HID_CONSUMER_PLAY}, - {"PAUSE", HID_CONSUMER_PAUSE}, - {"PLAY_PAUSE", HID_CONSUMER_PLAY_PAUSE}, - {"NEXT_TRACK", HID_CONSUMER_SCAN_NEXT_TRACK}, - {"PREV_TRACK", HID_CONSUMER_SCAN_PREVIOUS_TRACK}, - {"STOP", HID_CONSUMER_STOP}, - {"EJECT", HID_CONSUMER_EJECT}, - - {"MUTE", HID_CONSUMER_MUTE}, - {"VOLUME_UP", HID_CONSUMER_VOLUME_INCREMENT}, - {"VOLUME_DOWN", HID_CONSUMER_VOLUME_DECREMENT}, - - {"FN", HID_CONSUMER_FN_GLOBE}, - {"BRIGHT_UP", HID_CONSUMER_BRIGHTNESS_INCREMENT}, - {"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT}, -}; - -uint16_t ducky_get_keycode_by_name(const char* param) { - for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { - size_t key_cmd_len = strlen(ducky_keys[i].name); - if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && - (ducky_is_line_end(param[key_cmd_len]))) { - return ducky_keys[i].keycode; - } - } - - return HID_KEYBOARD_NONE; -} - -uint16_t ducky_get_media_keycode_by_name(const char* param) { - for(size_t i = 0; i < COUNT_OF(ducky_media_keys); i++) { - size_t key_cmd_len = strlen(ducky_media_keys[i].name); - if((strncmp(param, ducky_media_keys[i].name, key_cmd_len) == 0) && - (ducky_is_line_end(param[key_cmd_len]))) { - return ducky_media_keys[i].keycode; - } - } - - return HID_CONSUMER_UNASSIGNED; -} diff --git a/applications/system/bad_ble/icon.png b/applications/system/bad_ble/icon.png deleted file mode 100644 index 27355f8dbab9f62f03c3114bd345117b72703df2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsse8IOhE&W+{!>5Ur5*F)|L$Vj tulr2n@!{d|IW8GdR-3Ztdxz&lMuxeII5)+8P-F*b^>p=fS?83{1OR(_90~vc diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.c b/applications/system/bad_ble/scenes/bad_ble_scene.c deleted file mode 100644 index 351bb1e794..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "bad_ble_scene.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const bad_ble_scene_on_enter_handlers[])(void*) = { -#include "bad_ble_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 bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "bad_ble_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 bad_ble_scene_on_exit_handlers[])(void* context) = { -#include "bad_ble_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers bad_ble_scene_handlers = { - .on_enter_handlers = bad_ble_scene_on_enter_handlers, - .on_event_handlers = bad_ble_scene_on_event_handlers, - .on_exit_handlers = bad_ble_scene_on_exit_handlers, - .scene_num = BadBleSceneNum, -}; diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.h b/applications/system/bad_ble/scenes/bad_ble_scene.h deleted file mode 100644 index 25b19fc4b5..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) BadBleScene##id, -typedef enum { -#include "bad_ble_scene_config.h" - BadBleSceneNum, -} BadBleScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers bad_ble_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "bad_ble_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 "bad_ble_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 "bad_ble_scene_config.h" -#undef ADD_SCENE diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.c b/applications/system/bad_ble/scenes/bad_ble_scene_config.c deleted file mode 100644 index 1f64f19039..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_config.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "../bad_ble_app_i.h" - -enum SubmenuIndex { - ConfigIndexKeyboardLayout, - ConfigIndexBleUnpair, -}; - -void bad_ble_scene_config_select_callback(void* context, uint32_t index) { - BadBleApp* bad_ble = context; - - view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index); -} - -static void draw_menu(BadBleApp* bad_ble) { - VariableItemList* var_item_list = bad_ble->var_item_list; - - variable_item_list_reset(var_item_list); - - variable_item_list_add(var_item_list, "Keyboard Layout (Global)", 0, NULL, NULL); - - variable_item_list_add(var_item_list, "Unpair Device", 0, NULL, NULL); -} - -void bad_ble_scene_config_on_enter(void* context) { - BadBleApp* bad_ble = context; - VariableItemList* var_item_list = bad_ble->var_item_list; - - variable_item_list_set_enter_callback( - var_item_list, bad_ble_scene_config_select_callback, bad_ble); - draw_menu(bad_ble); - variable_item_list_set_selected_item(var_item_list, 0); - - view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig); -} - -bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) { - BadBleApp* bad_ble = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == ConfigIndexKeyboardLayout) { - scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout); - } else if(event.event == ConfigIndexBleUnpair) { - scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfirmUnpair); - } else { - furi_crash("Unknown key type"); - } - } - - return consumed; -} - -void bad_ble_scene_config_on_exit(void* context) { - BadBleApp* bad_ble = context; - VariableItemList* var_item_list = bad_ble->var_item_list; - - variable_item_list_reset(var_item_list); -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.h b/applications/system/bad_ble/scenes/bad_ble_scene_config.h deleted file mode 100644 index 5675fca59b..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_config.h +++ /dev/null @@ -1,7 +0,0 @@ -ADD_SCENE(bad_ble, file_select, FileSelect) -ADD_SCENE(bad_ble, work, Work) -ADD_SCENE(bad_ble, error, Error) -ADD_SCENE(bad_ble, config, Config) -ADD_SCENE(bad_ble, config_layout, ConfigLayout) -ADD_SCENE(bad_ble, confirm_unpair, ConfirmUnpair) -ADD_SCENE(bad_ble, unpair_done, UnpairDone) diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c deleted file mode 100644 index 594525dd7b..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "../bad_ble_app_i.h" -#include - -static bool bad_ble_layout_select(BadBleApp* bad_ble) { - furi_assert(bad_ble); - - FuriString* predefined_path; - predefined_path = furi_string_alloc(); - if(!furi_string_empty(bad_ble->keyboard_layout)) { - furi_string_set(predefined_path, bad_ble->keyboard_layout); - } else { - furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER); - } - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px); - browser_options.base_path = BAD_BLE_APP_PATH_LAYOUT_FOLDER; - browser_options.skip_assets = false; - - // Input events and views are managed by file_browser - bool res = dialog_file_browser_show( - bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options); - - furi_string_free(predefined_path); - return res; -} - -void bad_ble_scene_config_layout_on_enter(void* context) { - BadBleApp* bad_ble = context; - - if(bad_ble_layout_select(bad_ble)) { - scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneWork); - } else { - scene_manager_previous_scene(bad_ble->scene_manager); - } -} - -bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - // BadBleApp* bad_ble = context; - return false; -} - -void bad_ble_scene_config_layout_on_exit(void* context) { - UNUSED(context); - // BadBleApp* bad_ble = context; -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_error.c b/applications/system/bad_ble/scenes/bad_ble_scene_error.c deleted file mode 100644 index c9c2b12da2..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_error.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "../bad_ble_app_i.h" - -typedef enum { - BadBleCustomEventErrorBack, -} BadBleCustomEvent; - -static void - bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - BadBleApp* app = context; - - if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { - view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack); - } -} - -void bad_ble_scene_error_on_enter(void* context) { - BadBleApp* app = context; - - if(app->error == BadBleAppErrorNoFiles) { - widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); - widget_add_string_multiline_element( - app->widget, - 81, - 4, - AlignCenter, - AlignTop, - FontSecondary, - "No SD card or\napp data found.\nThis app will not\nwork without\nrequired files."); - widget_add_button_element( - app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app); - } else if(app->error == BadBleAppErrorCloseRpc) { - widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); - widget_add_string_multiline_element( - app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nIs Active!"); - widget_add_string_multiline_element( - app->widget, - 3, - 30, - AlignLeft, - AlignTop, - FontSecondary, - "Disconnect from\nPC or phone to\nuse this function."); - } - - view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWidget); -} - -bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) { - BadBleApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == BadBleCustomEventErrorBack) { - view_dispatcher_stop(app->view_dispatcher); - consumed = true; - } - } - return consumed; -} - -void bad_ble_scene_error_on_exit(void* context) { - BadBleApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c deleted file mode 100644 index 2a182a874d..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "../bad_ble_app_i.h" -#include -#include - -static bool bad_ble_file_select(BadBleApp* bad_ble) { - furi_assert(bad_ble); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px); - browser_options.base_path = BAD_BLE_APP_BASE_FOLDER; - browser_options.skip_assets = true; - - // Input events and views are managed by file_browser - bool res = dialog_file_browser_show( - bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options); - - return res; -} - -void bad_ble_scene_file_select_on_enter(void* context) { - BadBleApp* bad_ble = context; - - if(bad_ble->bad_ble_script) { - bad_ble_script_close(bad_ble->bad_ble_script); - bad_ble->bad_ble_script = NULL; - } - - if(bad_ble_file_select(bad_ble)) { - scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork); - } else { - view_dispatcher_stop(bad_ble->view_dispatcher); - } -} - -bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - // BadBleApp* bad_ble = context; - return false; -} - -void bad_ble_scene_file_select_on_exit(void* context) { - UNUSED(context); - // BadBleApp* bad_ble = context; -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c deleted file mode 100644 index 4c1fe3366b..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "../bad_ble_app_i.h" - -static void bad_ble_scene_unpair_done_popup_callback(void* context) { - BadBleApp* bad_ble = context; - scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneConfig); -} - -void bad_ble_scene_unpair_done_on_enter(void* context) { - BadBleApp* bad_ble = context; - Popup* popup = bad_ble->popup; - - popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58); - popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom); - popup_set_callback(popup, bad_ble_scene_unpair_done_popup_callback); - popup_set_context(popup, bad_ble); - popup_set_timeout(popup, 1000); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewPopup); -} - -bool bad_ble_scene_unpair_done_on_event(void* context, SceneManagerEvent event) { - BadBleApp* bad_ble = context; - UNUSED(bad_ble); - UNUSED(event); - - bool consumed = false; - - return consumed; -} - -void bad_ble_scene_unpair_done_on_exit(void* context) { - BadBleApp* bad_ble = context; - Popup* popup = bad_ble->popup; - - popup_reset(popup); -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_work.c b/applications/system/bad_ble/scenes/bad_ble_scene_work.c deleted file mode 100644 index ff71edc3c2..0000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_work.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "../helpers/ducky_script.h" -#include "../bad_ble_app_i.h" -#include "../views/bad_ble_view.h" -#include -#include "toolbox/path.h" - -void bad_ble_scene_work_button_callback(InputKey key, void* context) { - furi_assert(context); - BadBleApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, key); -} - -bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) { - BadBleApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == InputKeyLeft) { - if(bad_ble_view_is_idle_state(app->bad_ble_view)) { - bad_ble_script_close(app->bad_ble_script); - app->bad_ble_script = NULL; - - scene_manager_next_scene(app->scene_manager, BadBleSceneConfig); - } - consumed = true; - } else if(event.event == InputKeyOk) { - bad_ble_script_start_stop(app->bad_ble_script); - consumed = true; - } else if(event.event == InputKeyRight) { - bad_ble_script_pause_resume(app->bad_ble_script); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeTick) { - bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); - } - return consumed; -} - -void bad_ble_scene_work_on_enter(void* context) { - BadBleApp* app = context; - - app->bad_ble_script = bad_ble_script_open(app->file_path, app->interface); - bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout); - - FuriString* file_name; - file_name = furi_string_alloc(); - path_extract_filename(app->file_path, file_name, true); - bad_ble_view_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name)); - furi_string_free(file_name); - - FuriString* layout; - layout = furi_string_alloc(); - path_extract_filename(app->keyboard_layout, layout, true); - bad_ble_view_set_layout(app->bad_ble_view, furi_string_get_cstr(layout)); - furi_string_free(layout); - - bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); - - bad_ble_view_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app); - view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork); -} - -void bad_ble_scene_work_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/system/bad_ble/views/bad_ble_view.c b/applications/system/bad_ble/views/bad_ble_view.c deleted file mode 100644 index 28f935733e..0000000000 --- a/applications/system/bad_ble/views/bad_ble_view.c +++ /dev/null @@ -1,284 +0,0 @@ -#include "bad_ble_view.h" -#include "../helpers/ducky_script.h" -#include -#include -#include -#include "bad_ble_icons.h" - -#define MAX_NAME_LEN 64 - -struct BadBle { - View* view; - BadBleButtonCallback callback; - void* context; -}; - -typedef struct { - char file_name[MAX_NAME_LEN]; - char layout[MAX_NAME_LEN]; - BadBleState state; - bool pause_wait; - uint8_t anim_frame; -} BadBleModel; - -static void bad_ble_draw_callback(Canvas* canvas, void* _model) { - BadBleModel* model = _model; - - FuriString* disp_str; - disp_str = furi_string_alloc_set(model->file_name); - elements_string_fit_width(canvas, disp_str, 128 - 2); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); - - if(strlen(model->layout) == 0) { - furi_string_set(disp_str, "(default)"); - } else { - furi_string_printf(disp_str, "(%s)", model->layout); - } - elements_string_fit_width(canvas, disp_str, 128 - 2); - canvas_draw_str( - canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); - - furi_string_reset(disp_str); - - canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22); - - BadBleWorkerState state = model->state.state; - - if((state == BadBleStateIdle) || (state == BadBleStateDone) || - (state == BadBleStateNotConnected)) { - elements_button_center(canvas, "Run"); - elements_button_left(canvas, "Config"); - } else if((state == BadBleStateRunning) || (state == BadBleStateDelay)) { - elements_button_center(canvas, "Stop"); - if(!model->pause_wait) { - elements_button_right(canvas, "Pause"); - } - } else if(state == BadBleStatePaused) { - elements_button_center(canvas, "End"); - elements_button_right(canvas, "Resume"); - } else if(state == BadBleStateWaitForBtn) { - elements_button_center(canvas, "Press to continue"); - } else if(state == BadBleStateWillRun) { - elements_button_center(canvas, "Cancel"); - } - - if(state == BadBleStateNotConnected) { - canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); - canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device"); - } else if(state == BadBleStateWillRun) { - canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); - canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); - } else if(state == BadBleStateFileError) { - canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); - canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); - } else if(state == BadBleStateScriptError) { - canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); - canvas_set_font(canvas, FontSecondary); - furi_string_printf(disp_str, "line %zu", model->state.error_line); - canvas_draw_str_aligned( - canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - - furi_string_set_str(disp_str, model->state.error); - elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); - canvas_draw_str_aligned( - canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - } else if(state == BadBleStateIdle) { - canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); - canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(state == BadBleStateRunning) { - if(model->anim_frame == 0) { - canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); - } else { - canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); - } - canvas_set_font(canvas, FontBigNumbers); - furi_string_printf( - disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); - canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(state == BadBleStateDone) { - canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); - canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(state == BadBleStateDelay) { - if(model->anim_frame == 0) { - canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); - } else { - canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); - } - canvas_set_font(canvas, FontBigNumbers); - furi_string_printf( - disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); - canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - canvas_set_font(canvas, FontSecondary); - furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); - canvas_draw_str_aligned( - canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - } else if((state == BadBleStatePaused) || (state == BadBleStateWaitForBtn)) { - if(model->anim_frame == 0) { - canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); - } else { - canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); - } - canvas_set_font(canvas, FontBigNumbers); - furi_string_printf( - disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); - canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); - furi_string_reset(disp_str); - } else { - canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); - } - - furi_string_free(disp_str); -} - -static bool bad_ble_input_callback(InputEvent* event, void* context) { - furi_assert(context); - BadBle* bad_ble = context; - bool consumed = false; - - if(event->type == InputTypeShort) { - if(event->key == InputKeyLeft) { - consumed = true; - furi_assert(bad_ble->callback); - bad_ble->callback(event->key, bad_ble->context); - } else if(event->key == InputKeyOk) { - with_view_model( - bad_ble->view, BadBleModel * model, { model->pause_wait = false; }, true); - consumed = true; - furi_assert(bad_ble->callback); - bad_ble->callback(event->key, bad_ble->context); - } else if(event->key == InputKeyRight) { - with_view_model( - bad_ble->view, - BadBleModel * model, - { - if((model->state.state == BadBleStateRunning) || - (model->state.state == BadBleStateDelay)) { - model->pause_wait = true; - } - }, - true); - consumed = true; - furi_assert(bad_ble->callback); - bad_ble->callback(event->key, bad_ble->context); - } - } - - return consumed; -} - -BadBle* bad_ble_view_alloc(void) { - BadBle* bad_ble = malloc(sizeof(BadBle)); - - bad_ble->view = view_alloc(); - view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel)); - view_set_context(bad_ble->view, bad_ble); - view_set_draw_callback(bad_ble->view, bad_ble_draw_callback); - view_set_input_callback(bad_ble->view, bad_ble_input_callback); - - return bad_ble; -} - -void bad_ble_view_free(BadBle* bad_ble) { - furi_assert(bad_ble); - view_free(bad_ble->view); - free(bad_ble); -} - -View* bad_ble_view_get_view(BadBle* bad_ble) { - furi_assert(bad_ble); - return bad_ble->view; -} - -void bad_ble_view_set_button_callback( - BadBle* bad_ble, - BadBleButtonCallback callback, - void* context) { - furi_assert(bad_ble); - furi_assert(callback); - with_view_model( - bad_ble->view, - BadBleModel * model, - { - UNUSED(model); - bad_ble->callback = callback; - bad_ble->context = context; - }, - true); -} - -void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name) { - furi_assert(name); - with_view_model( - bad_ble->view, - BadBleModel * model, - { strlcpy(model->file_name, name, MAX_NAME_LEN); }, - true); -} - -void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout) { - furi_assert(layout); - with_view_model( - bad_ble->view, - BadBleModel * model, - { strlcpy(model->layout, layout, MAX_NAME_LEN); }, - true); -} - -void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st) { - furi_assert(st); - with_view_model( - bad_ble->view, - BadBleModel * model, - { - memcpy(&(model->state), st, sizeof(BadBleState)); - model->anim_frame ^= 1; - if(model->state.state == BadBleStatePaused) { - model->pause_wait = false; - } - }, - true); -} - -bool bad_ble_view_is_idle_state(BadBle* bad_ble) { - bool is_idle = false; - with_view_model( - bad_ble->view, - BadBleModel * model, - { - if((model->state.state == BadBleStateIdle) || - (model->state.state == BadBleStateDone) || - (model->state.state == BadBleStateNotConnected)) { - is_idle = true; - } - }, - false); - return is_idle; -} diff --git a/applications/system/bad_ble/views/bad_ble_view.h b/applications/system/bad_ble/views/bad_ble_view.h deleted file mode 100644 index e26488818e..0000000000 --- a/applications/system/bad_ble/views/bad_ble_view.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include "../helpers/ducky_script.h" - -typedef struct BadBle BadBle; -typedef void (*BadBleButtonCallback)(InputKey key, void* context); - -BadBle* bad_ble_view_alloc(void); - -void bad_ble_view_free(BadBle* bad_ble); - -View* bad_ble_view_get_view(BadBle* bad_ble); - -void bad_ble_view_set_button_callback( - BadBle* bad_ble, - BadBleButtonCallback callback, - void* context); - -void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name); - -void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout); - -void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st); - -bool bad_ble_view_is_idle_state(BadBle* bad_ble); diff --git a/applications/system/bad_ble/assets/Bad_BLE_48x22.png b/assets/icons/BadUsb/Bad_BLE_48x22.png similarity index 100% rename from applications/system/bad_ble/assets/Bad_BLE_48x22.png rename to assets/icons/BadUsb/Bad_BLE_48x22.png diff --git a/furi/core/timer.c b/furi/core/timer.c index ddd82e3319..fe3f3db17f 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -119,8 +119,6 @@ FuriStatus furi_timer_stop(FuriTimer* instance) { furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - furi_timer_flush(); - return FuriStatusOk; } From dc9548d0dc9ab7c2813707eb6ab47d8e23f06da4 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Wed, 16 Oct 2024 01:17:33 +0800 Subject: [PATCH 152/236] New Static Keys for Mifare Classic Dictionary (#3947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../main/nfc/resources/nfc/assets/mf_classic_dict.nfc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index 25f750102f..e2b74f8475 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1351,3 +1351,8 @@ E69DD9015A43 C8382A233993 7B304F2A12A6 FC9418BF788B + +# H World Hotel Chain Room Keys +543071543071 +5F01015F0101 +200510241234 From c917135c9488ee58947d797c44c6bee182825c10 Mon Sep 17 00:00:00 2001 From: Kowalski Dragon Date: Tue, 15 Oct 2024 19:23:49 +0200 Subject: [PATCH 153/236] [BadUSB] Improve ChromeOS and GNOME demo scripts (#3948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BadUSB] Gnome Demo: Support most terminals and force sh shell when not using Bash as default * [BadUSB] ChromeOS Demo: Minor improvements, such as exit overview, select omnibox and add a page title Signed-off-by: Kowalski Dragon (kowalski7cc) <5065094+kowalski7cc@users.noreply.github.com> Co-authored-by: Kowalski Dragon (kowalski7cc) <5065094+kowalski7cc@users.noreply.github.com> Co-authored-by: あく --- .../bad_usb/resources/badusb/demo_chromeos.txt | 9 +++++++-- .../bad_usb/resources/badusb/demo_gnome.txt | 18 +++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/applications/main/bad_usb/resources/badusb/demo_chromeos.txt b/applications/main/bad_usb/resources/badusb/demo_chromeos.txt index c5f675fb33..7f42574ce1 100644 --- a/applications/main/bad_usb/resources/badusb/demo_chromeos.txt +++ b/applications/main/bad_usb/resources/badusb/demo_chromeos.txt @@ -1,12 +1,17 @@ -REM This is BadUSB demo script for ChromeOS by kowalski7cc +REM This is BadUSB demo script for Chrome and ChromeOS by kowalski7cc +REM Exit from Overview +ESC REM Open a new tab CTRL t REM wait for some slower chromebooks DELAY 1000 +REM Make sure we have omnibox focus +CTRL l +DELAY 200 REM Open an empty editable page DEFAULT_DELAY 50 -STRING data:text/html,