Merge branch 'dev' into keeloq_move_mf_to_keystore

This commit is contained in:
MX
2023-05-31 10:38:28 +03:00
378 changed files with 23484 additions and 8573 deletions

View File

@@ -44,7 +44,6 @@ env.Append(
"variant",
)
),
File("u8g2/u8g2.h"),
],
CPPDEFINES=[
'"M_MEMORY_FULL(x)=abort()"',

View File

@@ -215,12 +215,12 @@ void digital_signal_prepare_arr(DigitalSignal* signal) {
uint32_t edge_scaled = (internals->factor * signal->edge_timings[pos]) / (1024 * 1024);
uint32_t pulse_duration = edge_scaled + internals->reload_reg_remainder;
if(pulse_duration < 10 || pulse_duration > 10000000) {
FURI_LOG_D(
/*FURI_LOG_D(
TAG,
"[prepare] pulse_duration out of range: %lu = %lu * %llu",
pulse_duration,
signal->edge_timings[pos],
internals->factor);
internals->factor);*/
pulse_duration = 100;
}
uint32_t pulse_ticks = (pulse_duration + T_TIM_DIV2) / T_TIM;
@@ -243,10 +243,12 @@ static void digital_signal_stop_timer() {
LL_TIM_DisableCounter(TIM2);
LL_TIM_DisableUpdateEvent(TIM2);
LL_TIM_DisableDMAReq_UPDATE(TIM2);
furi_hal_bus_disable(FuriHalBusTIM2);
}
static void digital_signal_setup_timer() {
digital_signal_stop_timer();
furi_hal_bus_enable(FuriHalBusTIM2);
LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
@@ -482,13 +484,13 @@ static void digital_sequence_finish(DigitalSequence* sequence) {
prev_timer = DWT->CYCCNT;
}
if(DWT->CYCCNT - prev_timer > wait_ticks) {
FURI_LOG_D(
/*FURI_LOG_D(
TAG,
"[SEQ] hung %lu ms in finish (ARR 0x%08lx, read %lu, write %lu)",
wait_ms,
TIM2->ARR,
dma_buffer->read_pos,
dma_buffer->write_pos);
dma_buffer->write_pos);*/
break;
}
} while(1);
@@ -516,13 +518,13 @@ static void digital_sequence_queue_pulse(DigitalSequence* sequence, uint32_t len
prev_timer = DWT->CYCCNT;
}
if(DWT->CYCCNT - prev_timer > wait_ticks) {
FURI_LOG_D(
/*FURI_LOG_D(
TAG,
"[SEQ] hung %lu ms in queue (ARR 0x%08lx, read %lu, write %lu)",
wait_ms,
TIM2->ARR,
dma_buffer->read_pos,
dma_buffer->write_pos);
dma_buffer->write_pos);*/
break;
}
} while(1);

View File

@@ -40,8 +40,12 @@ struct InfraredWorkerSignal {
size_t timings_cnt;
union {
InfraredMessage message;
/* +1 is for pause we add at the beginning */
uint32_t timings[MAX_TIMINGS_AMOUNT + 1];
struct {
/* +1 is for pause we add at the beginning */
uint32_t timings[MAX_TIMINGS_AMOUNT + 1];
uint32_t frequency;
float duty_cycle;
} raw;
};
};
@@ -146,7 +150,7 @@ static void
}
if(instance->signal.timings_cnt < MAX_TIMINGS_AMOUNT) {
instance->signal.timings[instance->signal.timings_cnt] = duration;
instance->signal.raw.timings[instance->signal.timings_cnt] = duration;
++instance->signal.timings_cnt;
} else {
uint32_t flags_set = furi_thread_flags_set(
@@ -300,7 +304,7 @@ void infrared_worker_get_raw_signal(
furi_assert(timings);
furi_assert(timings_cnt);
*timings = signal->timings;
*timings = signal->raw.timings;
*timings_cnt = signal->timings_cnt;
}
@@ -390,8 +394,8 @@ static bool infrared_get_new_signal(InfraredWorker* instance) {
infrared_get_protocol_duty_cycle(instance->signal.message.protocol);
} else {
furi_assert(instance->signal.timings_cnt > 1);
new_tx_frequency = INFRARED_COMMON_CARRIER_FREQUENCY;
new_tx_duty_cycle = INFRARED_COMMON_DUTY_CYCLE;
new_tx_frequency = instance->signal.raw.frequency;
new_tx_duty_cycle = instance->signal.raw.duty_cycle;
}
instance->tx.tx_raw_cnt = 0;
@@ -426,7 +430,7 @@ static bool infrared_worker_tx_fill_buffer(InfraredWorker* instance) {
if(instance->signal.decoded) {
status = infrared_encode(instance->infrared_encoder, &timing.duration, &timing.level);
} else {
timing.duration = instance->signal.timings[instance->tx.tx_raw_cnt];
timing.duration = instance->signal.raw.timings[instance->tx.tx_raw_cnt];
/* raw always starts from Mark, but we fill it with space delay at start */
timing.level = (instance->tx.tx_raw_cnt % 2);
++instance->tx.tx_raw_cnt;
@@ -597,15 +601,21 @@ void infrared_worker_set_decoded_signal(InfraredWorker* instance, const Infrared
void infrared_worker_set_raw_signal(
InfraredWorker* instance,
const uint32_t* timings,
size_t timings_cnt) {
size_t timings_cnt,
uint32_t frequency,
float duty_cycle) {
furi_assert(instance);
furi_assert(timings);
furi_assert(timings_cnt > 0);
size_t max_copy_num = COUNT_OF(instance->signal.timings) - 1;
furi_assert((frequency <= INFRARED_MAX_FREQUENCY) && (frequency >= INFRARED_MIN_FREQUENCY));
furi_assert((duty_cycle < 1.0f) && (duty_cycle > 0.0f));
size_t max_copy_num = COUNT_OF(instance->signal.raw.timings) - 1;
furi_check(timings_cnt <= max_copy_num);
instance->signal.timings[0] = INFRARED_RAW_TX_TIMING_DELAY_US;
memcpy(&instance->signal.timings[1], timings, timings_cnt * sizeof(uint32_t));
instance->signal.raw.frequency = frequency;
instance->signal.raw.duty_cycle = duty_cycle;
instance->signal.raw.timings[0] = INFRARED_RAW_TX_TIMING_DELAY_US;
memcpy(&instance->signal.raw.timings[1], timings, timings_cnt * sizeof(uint32_t));
instance->signal.decoded = false;
instance->signal.timings_cnt = timings_cnt + 1;
}

View File

@@ -130,9 +130,9 @@ void infrared_worker_tx_set_signal_sent_callback(
/** Callback to pass to infrared_worker_tx_set_get_signal_callback() if signal
* is steady and will not be changed between infrared_worker start and stop.
* Before starting transmission, desired steady signal must be set with
* infrared_worker_make_decoded_signal() or infrared_worker_make_raw_signal().
* infrared_worker_set_decoded_signal() or infrared_worker_set_raw_signal().
*
* This function should not be implicitly called.
* This function should not be called directly.
*
* @param[in] context - context
* @param[out] instance - InfraredWorker instance
@@ -172,11 +172,15 @@ void infrared_worker_set_decoded_signal(InfraredWorker* instance, const Infrared
* @param[out] instance - InfraredWorker instance
* @param[in] timings - array of raw timings
* @param[in] timings_cnt - size of array of raw timings
* @param[in] frequency - carrier frequency in Hertz
* @param[in] duty_cycle - carrier duty cycle (0.0 - 1.0)
*/
void infrared_worker_set_raw_signal(
InfraredWorker* instance,
const uint32_t* timings,
size_t timings_cnt);
size_t timings_cnt,
uint32_t frequency,
float duty_cycle);
#ifdef __cplusplus
}

View File

@@ -151,9 +151,7 @@ static int32_t lfrfid_raw_read_worker_thread(void* thread_context) {
if(file_valid) {
// setup carrier
furi_hal_rfid_pins_read();
furi_hal_rfid_tim_read(worker->frequency, worker->duty_cycle);
furi_hal_rfid_tim_read_start();
furi_hal_rfid_tim_read_start(worker->frequency, worker->duty_cycle);
// stabilize detector
furi_delay_ms(1500);

View File

@@ -100,24 +100,21 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal(
uint32_t timeout,
ProtocolId* result_protocol) {
LFRFIDWorkerReadState state = LFRFIDWorkerReadTimeout;
furi_hal_rfid_pins_read();
if(feature & LFRFIDFeatureASK) {
furi_hal_rfid_tim_read(125000, 0.5);
furi_hal_rfid_tim_read_start(125000, 0.5);
FURI_LOG_D(TAG, "Start ASK");
if(worker->read_cb) {
worker->read_cb(LFRFIDWorkerReadStartASK, PROTOCOL_NO, worker->cb_ctx);
}
} else {
furi_hal_rfid_tim_read(62500, 0.25);
furi_hal_rfid_tim_read_start(62500, 0.25);
FURI_LOG_D(TAG, "Start PSK");
if(worker->read_cb) {
worker->read_cb(LFRFIDWorkerReadStartPSK, PROTOCOL_NO, worker->cb_ctx);
}
}
furi_hal_rfid_tim_read_start();
// stabilize detector
lfrfid_worker_delay(worker, LFRFID_WORKER_READ_STABILIZE_TIME_MS);
@@ -205,7 +202,7 @@ static LFRFIDWorkerReadState lfrfid_worker_read_internal(
average_index = 0;
if(worker->read_cb) {
if(average > 0.2 && average < 0.8) {
if(average > 0.2f && average < 0.8f) {
if(!card_detected) {
card_detected = true;
worker->read_cb(

View File

@@ -16,6 +16,7 @@
#include "protocol_pac_stanley.h"
#include "protocol_keri.h"
#include "protocol_gallagher.h"
#include "protocol_nexwatch.h"
const ProtocolBase* lfrfid_protocols[] = {
[LFRFIDProtocolEM4100] = &protocol_em4100,
@@ -35,4 +36,5 @@ const ProtocolBase* lfrfid_protocols[] = {
[LFRFIDProtocolPACStanley] = &protocol_pac_stanley,
[LFRFIDProtocolKeri] = &protocol_keri,
[LFRFIDProtocolGallagher] = &protocol_gallagher,
};
[LFRFIDProtocolNexwatch] = &protocol_nexwatch,
};

View File

@@ -25,6 +25,7 @@ typedef enum {
LFRFIDProtocolPACStanley,
LFRFIDProtocolKeri,
LFRFIDProtocolGallagher,
LFRFIDProtocolNexwatch,
LFRFIDProtocolMax,
} LFRFIDProtocol;
@@ -39,4 +40,4 @@ typedef struct {
union {
LFRFIDT5577 t5577;
};
} LFRFIDWriteRequest;
} LFRFIDWriteRequest;

View File

@@ -0,0 +1,323 @@
#include <furi.h>
#include <toolbox/protocols/protocol.h>
#include <lfrfid/tools/bit_lib.h>
#include "lfrfid_protocols.h"
#define NEXWATCH_PREAMBLE_BIT_SIZE (8)
#define NEXWATCH_PREAMBLE_DATA_SIZE (1)
#define NEXWATCH_ENCODED_BIT_SIZE (96)
#define NEXWATCH_ENCODED_DATA_SIZE ((NEXWATCH_ENCODED_BIT_SIZE) / 8)
#define NEXWATCH_DECODED_BIT_SIZE (NEXWATCH_DECODED_DATA_SIZE * 8)
#define NEXWATCH_DECODED_DATA_SIZE (8)
#define NEXWATCH_US_PER_BIT (255)
#define NEXWATCH_ENCODER_PULSES_PER_BIT (16)
typedef struct {
uint8_t magic;
char desc[13];
uint8_t chk;
} ProtocolNexwatchMagic;
ProtocolNexwatchMagic magic_items[] = {
{0xBE, "Quadrakey", 0},
{0x88, "Nexkey", 0},
{0x86, "Honeywell", 0}};
typedef struct {
uint8_t data_index;
uint8_t bit_clock_index;
bool last_bit;
bool current_polarity;
bool pulse_phase;
} ProtocolNexwatchEncoder;
typedef struct {
uint8_t encoded_data[NEXWATCH_ENCODED_DATA_SIZE];
uint8_t negative_encoded_data[NEXWATCH_ENCODED_DATA_SIZE];
uint8_t corrupted_encoded_data[NEXWATCH_ENCODED_DATA_SIZE];
uint8_t corrupted_negative_encoded_data[NEXWATCH_ENCODED_DATA_SIZE];
uint8_t data[NEXWATCH_DECODED_DATA_SIZE];
ProtocolNexwatchEncoder encoder;
} ProtocolNexwatch;
ProtocolNexwatch* protocol_nexwatch_alloc(void) {
ProtocolNexwatch* protocol = malloc(sizeof(ProtocolNexwatch));
return protocol;
};
void protocol_nexwatch_free(ProtocolNexwatch* protocol) {
free(protocol);
};
uint8_t* protocol_nexwatch_get_data(ProtocolNexwatch* protocol) {
return protocol->data;
};
void protocol_nexwatch_decoder_start(ProtocolNexwatch* protocol) {
memset(protocol->encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE);
memset(protocol->negative_encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE);
memset(protocol->corrupted_encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE);
memset(protocol->corrupted_negative_encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE);
};
static bool protocol_nexwatch_check_preamble(uint8_t* data, size_t bit_index) {
// 01010110
if(bit_lib_get_bits(data, bit_index, 8) != 0b01010110) return false;
return true;
}
static uint8_t protocol_nexwatch_parity_swap(uint8_t parity) {
uint8_t a = (((parity >> 3) & 1));
a |= (((parity >> 1) & 1) << 1);
a |= (((parity >> 2) & 1) << 2);
a |= ((parity & 1) << 3);
return a;
}
static uint8_t protocol_nexwatch_parity(const uint8_t hexid[5]) {
uint8_t p = 0;
for(uint8_t i = 0; i < 5; i++) {
p ^= ((hexid[i]) & 0xF0) >> 4;
p ^= ((hexid[i]) & 0x0F);
}
return protocol_nexwatch_parity_swap(p);
}
static uint8_t protocol_nexwatch_checksum(uint8_t magic, uint32_t id, uint8_t parity) {
uint8_t a = ((id >> 24) & 0xFF);
a -= ((id >> 16) & 0xFF);
a -= ((id >> 8) & 0xFF);
a -= (id & 0xFF);
a -= magic;
a -= (bit_lib_reverse_8_fast(parity) >> 4);
return bit_lib_reverse_8_fast(a);
}
static bool protocol_nexwatch_can_be_decoded(uint8_t* data) {
if(!protocol_nexwatch_check_preamble(data, 0)) return false;
// Check for reserved word (32-bit)
if(bit_lib_get_bits_32(data, 8, 32) != 0) {
return false;
}
uint8_t parity = bit_lib_get_bits(data, 76, 4);
// parity check
// from 32b hex id, 4b mode
uint8_t hex[5] = {0};
for(uint8_t i = 0; i < 5; i++) {
hex[i] = bit_lib_get_bits(data, 40 + (i * 8), 8);
}
//mode is only 4 bits.
hex[4] &= 0xf0;
uint8_t calc_parity = protocol_nexwatch_parity(hex);
if(calc_parity != parity) {
return false;
}
return true;
}
static bool protocol_nexwatch_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) {
time += (NEXWATCH_US_PER_BIT / 2);
size_t bit_count = (time / NEXWATCH_US_PER_BIT);
bool result = false;
if(bit_count < NEXWATCH_ENCODED_BIT_SIZE) {
for(size_t i = 0; i < bit_count; i++) {
bit_lib_push_bit(data, NEXWATCH_ENCODED_DATA_SIZE, polarity);
if(protocol_nexwatch_can_be_decoded(data)) {
result = true;
break;
}
}
}
return result;
}
static void protocol_nexwatch_descramble(uint32_t* id, uint32_t* scrambled) {
// 255 = Not used/Unknown other values are the bit offset in the ID/FC values
const uint8_t hex_2_id[] = {31, 27, 23, 19, 15, 11, 7, 3, 30, 26, 22, 18, 14, 10, 6, 2,
29, 25, 21, 17, 13, 9, 5, 1, 28, 24, 20, 16, 12, 8, 4, 0};
*id = 0;
for(uint8_t idx = 0; idx < 32; idx++) {
bool bit_state = (*scrambled >> hex_2_id[idx]) & 1;
*id |= (bit_state << (31 - idx));
}
}
static void protocol_nexwatch_decoder_save(uint8_t* data_to, const uint8_t* data_from) {
uint32_t id = bit_lib_get_bits_32(data_from, 40, 32);
data_to[4] = (uint8_t)id;
data_to[3] = (uint8_t)(id >>= 8);
data_to[2] = (uint8_t)(id >>= 8);
data_to[1] = (uint8_t)(id >>= 8);
data_to[0] = (uint8_t)(id >>= 8);
uint32_t check = bit_lib_get_bits_32(data_from, 72, 24);
data_to[7] = (uint8_t)check;
data_to[6] = (uint8_t)(check >>= 8);
data_to[5] = (uint8_t)(check >>= 8);
}
bool protocol_nexwatch_decoder_feed(ProtocolNexwatch* protocol, bool level, uint32_t duration) {
bool result = false;
if(duration > (NEXWATCH_US_PER_BIT / 2)) {
if(protocol_nexwatch_decoder_feed_internal(level, duration, protocol->encoded_data)) {
protocol_nexwatch_decoder_save(protocol->data, protocol->encoded_data);
result = true;
return result;
}
if(protocol_nexwatch_decoder_feed_internal(
!level, duration, protocol->negative_encoded_data)) {
protocol_nexwatch_decoder_save(protocol->data, protocol->negative_encoded_data);
result = true;
return result;
}
}
if(duration > (NEXWATCH_US_PER_BIT / 4)) {
// Try to decode wrong phase synced data
if(level) {
duration += 120;
} else {
if(duration > 120) {
duration -= 120;
}
}
if(protocol_nexwatch_decoder_feed_internal(
level, duration, protocol->corrupted_encoded_data)) {
protocol_nexwatch_decoder_save(protocol->data, protocol->corrupted_encoded_data);
result = true;
return result;
}
if(protocol_nexwatch_decoder_feed_internal(
!level, duration, protocol->corrupted_negative_encoded_data)) {
protocol_nexwatch_decoder_save(
protocol->data, protocol->corrupted_negative_encoded_data);
result = true;
return result;
}
}
return result;
};
bool protocol_nexwatch_encoder_start(ProtocolNexwatch* protocol) {
memset(protocol->encoded_data, 0, NEXWATCH_ENCODED_DATA_SIZE);
*(uint32_t*)&protocol->encoded_data[0] = 0b00000000000000000000000001010110;
bit_lib_copy_bits(protocol->encoded_data, 32, 32, protocol->data, 0);
bit_lib_copy_bits(protocol->encoded_data, 64, 32, protocol->data, 32);
protocol->encoder.last_bit =
bit_lib_get_bit(protocol->encoded_data, NEXWATCH_ENCODED_BIT_SIZE - 1);
protocol->encoder.data_index = 0;
protocol->encoder.current_polarity = true;
protocol->encoder.pulse_phase = true;
protocol->encoder.bit_clock_index = 0;
return true;
};
LevelDuration protocol_nexwatch_encoder_yield(ProtocolNexwatch* protocol) {
LevelDuration level_duration;
ProtocolNexwatchEncoder* encoder = &protocol->encoder;
if(encoder->pulse_phase) {
level_duration = level_duration_make(encoder->current_polarity, 1);
encoder->pulse_phase = false;
} else {
level_duration = level_duration_make(!encoder->current_polarity, 1);
encoder->pulse_phase = true;
encoder->bit_clock_index++;
if(encoder->bit_clock_index >= NEXWATCH_ENCODER_PULSES_PER_BIT) {
encoder->bit_clock_index = 0;
bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index);
if(current_bit != encoder->last_bit) {
encoder->current_polarity = !encoder->current_polarity;
}
encoder->last_bit = current_bit;
bit_lib_increment_index(encoder->data_index, NEXWATCH_ENCODED_BIT_SIZE);
}
}
return level_duration;
};
void protocol_nexwatch_render_data(ProtocolNexwatch* protocol, FuriString* result) {
uint32_t id = 0;
uint32_t scrambled = bit_lib_get_bits_32(protocol->data, 8, 32);
protocol_nexwatch_descramble(&id, &scrambled);
uint8_t m_idx;
uint8_t mode = bit_lib_get_bits(protocol->data, 40, 4);
uint8_t parity = bit_lib_get_bits(protocol->data, 44, 4);
uint8_t chk = bit_lib_get_bits(protocol->data, 48, 8);
for(m_idx = 0; m_idx < 3; m_idx++) {
magic_items[m_idx].chk = protocol_nexwatch_checksum(magic_items[m_idx].magic, id, parity);
if(magic_items[m_idx].chk == chk) {
break;
}
}
furi_string_printf(result, "ID: %lu, M:%u\r\nType: %s\r\n", id, mode, magic_items[m_idx].desc);
}
bool protocol_nexwatch_write_data(ProtocolNexwatch* protocol, void* data) {
LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data;
bool result = false;
protocol_nexwatch_encoder_start(protocol);
if(request->write_type == LFRFIDWriteTypeT5577) {
request->t5577.block[0] = LFRFID_T5577_MODULATION_PSK1 | LFRFID_T5577_BITRATE_RF_32 |
(3 << LFRFID_T5577_MAXBLOCK_SHIFT);
request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32);
request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32);
request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32);
request->t5577.blocks_to_write = 4;
result = true;
}
return result;
};
const ProtocolBase protocol_nexwatch = {
.name = "Nexwatch",
.manufacturer = "Honeywell",
.data_size = NEXWATCH_DECODED_DATA_SIZE,
.features = LFRFIDFeaturePSK,
.validate_count = 6,
.alloc = (ProtocolAlloc)protocol_nexwatch_alloc,
.free = (ProtocolFree)protocol_nexwatch_free,
.get_data = (ProtocolGetData)protocol_nexwatch_get_data,
.decoder =
{
.start = (ProtocolDecoderStart)protocol_nexwatch_decoder_start,
.feed = (ProtocolDecoderFeed)protocol_nexwatch_decoder_feed,
},
.encoder =
{
.start = (ProtocolEncoderStart)protocol_nexwatch_encoder_start,
.yield = (ProtocolEncoderYield)protocol_nexwatch_encoder_yield,
},
.render_data = (ProtocolRenderData)protocol_nexwatch_render_data,
.render_brief_data = (ProtocolRenderData)protocol_nexwatch_render_data,
.write_data = (ProtocolWriteData)protocol_nexwatch_write_data,
};

View File

@@ -0,0 +1,4 @@
#pragma once
#include <toolbox/protocols/protocol.h>
extern const ProtocolBase protocol_nexwatch;

View File

@@ -14,9 +14,7 @@
#define T5577_OPCODE_RESET 0b00
static void t5577_start() {
furi_hal_rfid_tim_read(125000, 0.5);
furi_hal_rfid_pins_read();
furi_hal_rfid_tim_read_start();
furi_hal_rfid_tim_read_start(125000, 0.5);
// do not ground the antenna
furi_hal_rfid_pin_pull_release();
@@ -24,14 +22,13 @@ static void t5577_start() {
static void t5577_stop() {
furi_hal_rfid_tim_read_stop();
furi_hal_rfid_tim_reset();
furi_hal_rfid_pins_reset();
}
static void t5577_write_gap(uint32_t gap_time) {
furi_hal_rfid_tim_read_stop();
furi_hal_rfid_tim_read_pause();
furi_delay_us(gap_time * 8);
furi_hal_rfid_tim_read_start();
furi_hal_rfid_tim_read_continue();
}
static void t5577_write_bit(bool value) {
@@ -54,7 +51,12 @@ static void t5577_write_reset() {
t5577_write_bit(0);
}
static void t5577_write_block(uint8_t block, bool lock_bit, uint32_t data) {
static void t5577_write_block_pass(
uint8_t block,
bool lock_bit,
uint32_t data,
bool with_pass,
uint32_t password) {
furi_delay_us(T5577_TIMING_WAIT_TIME * 8);
// start gap
@@ -63,6 +65,13 @@ static void t5577_write_block(uint8_t block, bool lock_bit, uint32_t data) {
// opcode for page 0
t5577_write_opcode(T5577_OPCODE_PAGE_0);
// password
if(with_pass) {
for(uint8_t i = 0; i < 32; i++) {
t5577_write_bit((password >> (31 - i)) & 1);
}
}
// lock bit
t5577_write_bit(lock_bit);
@@ -82,11 +91,26 @@ static void t5577_write_block(uint8_t block, bool lock_bit, uint32_t data) {
t5577_write_reset();
}
static void t5577_write_block_simple(uint8_t block, bool lock_bit, uint32_t data) {
t5577_write_block_pass(block, lock_bit, data, false, 0);
}
void t5577_write(LFRFIDT5577* data) {
t5577_start();
FURI_CRITICAL_ENTER();
for(size_t i = 0; i < data->blocks_to_write; i++) {
t5577_write_block(i, false, data->block[i]);
t5577_write_block_simple(i, false, data->block[i]);
}
t5577_write_reset();
FURI_CRITICAL_EXIT();
t5577_stop();
}
void t5577_write_with_pass(LFRFIDT5577* data, uint32_t password) {
t5577_start();
FURI_CRITICAL_ENTER();
for(size_t i = 0; i < data->blocks_to_write; i++) {
t5577_write_block_pass(0, false, data->block[i], true, password);
}
t5577_write_reset();
FURI_CRITICAL_EXIT();

View File

@@ -51,6 +51,8 @@ typedef struct {
*/
void t5577_write(LFRFIDT5577* data);
void t5577_write_with_pass(LFRFIDT5577* data, uint32_t password);
#ifdef __cplusplus
}
#endif

View File

@@ -45,6 +45,8 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) {
return "NTAG I2C Plus 2K";
} else if(type == MfUltralightTypeNTAG203) {
return "NTAG203";
} else if(type == MfUltralightTypeULC) {
return "Mifare Ultralight C";
} else if(type == MfUltralightTypeUL11 && full_name) {
return "Mifare Ultralight 11";
} else if(type == MfUltralightTypeUL21 && full_name) {

View File

@@ -464,6 +464,19 @@ static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxCont
do {
if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break;
if(!mf_df_read_card(tx_rx, data)) break;
FURI_LOG_I(TAG, "Trying to parse a supported card ...");
// The model for parsing DESFire is a little different to other cards;
// we don't have parsers to provide encryption keys, so we can read the
// data normally, and then pass the read data to a parser.
//
// There are fully-protected DESFire cards, but providing keys for them
// is difficult (and unnessesary for many transit cards).
for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) {
if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareDesfire) {
if(nfc_supported_card[i].parse(nfc_worker->dev_data)) break;
}
}
read_success = true;
} while(false);

View File

@@ -6,6 +6,7 @@
#include "troika_4k_parser.h"
#include "two_cities.h"
#include "all_in_one.h"
#include "opal.h"
NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
[NfcSupportedCardTypePlantain] =
@@ -50,6 +51,14 @@ NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
.read = all_in_one_parser_read,
.parse = all_in_one_parser_parse,
},
[NfcSupportedCardTypeOpal] =
{
.protocol = NfcDeviceProtocolMifareDesfire,
.verify = stub_parser_verify_read,
.read = stub_parser_verify_read,
.parse = opal_parser_parse,
},
};
bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) {
@@ -65,3 +74,9 @@ bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) {
return card_parsed;
}
bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
UNUSED(nfc_worker);
UNUSED(tx_rx);
return false;
}

View File

@@ -11,6 +11,7 @@ typedef enum {
NfcSupportedCardTypeTroika4K,
NfcSupportedCardTypeTwoCities,
NfcSupportedCardTypeAllInOne,
NfcSupportedCardTypeOpal,
NfcSupportedCardTypeEnd,
} NfcSupportedCardType;
@@ -31,3 +32,8 @@ typedef struct {
extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd];
bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data);
// stub_parser_verify_read does nothing, and always reports that it does not
// support the card. This is needed for DESFire card parsers which can't
// provide keys, and only use NfcSupportedCard->parse.
bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);

204
lib/nfc/parsers/opal.c Normal file
View File

@@ -0,0 +1,204 @@
/*
* opal.c - Parser for Opal card (Sydney, Australia).
*
* Copyright 2023 Michael Farrell <micolous+git@gmail.com>
*
* This will only read "standard" MIFARE DESFire-based Opal cards. Free travel
* cards (including School Opal cards, veteran, vision-impaired persons and
* TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C
* cards and not supported.
*
* Reference: https://github.com/metrodroid/metrodroid/wiki/Opal
*
* Note: The card values are all little-endian (like Flipper), but the above
* reference was originally written based on Java APIs, which are big-endian.
* This implementation presumes a little-endian system.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nfc_supported_card.h"
#include "opal.h"
#include <applications/services/locale/locale.h>
#include <gui/modules/widget.h>
#include <nfc_worker_i.h>
#include <furi_hal.h>
static const uint8_t opal_aid[3] = {0x31, 0x45, 0x53};
static const char* opal_modes[5] =
{"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"};
static const char* opal_usages[14] = {
"New / Unused",
"Tap on: new journey",
"Tap on: transfer from same mode",
"Tap on: transfer from other mode",
"", // Manly Ferry: new journey
"", // Manly Ferry: transfer from ferry
"", // Manly Ferry: transfer from other
"Tap off: distance fare",
"Tap off: flat fare",
"Automated tap off: failed to tap off",
"Tap off: end of trip without start",
"Tap off: reversal",
"Tap on: rejected",
"Unknown usage",
};
// Opal file 0x7 structure. Assumes a little-endian CPU.
typedef struct __attribute__((__packed__)) {
uint32_t serial : 32;
uint8_t check_digit : 4;
bool blocked : 1;
uint16_t txn_number : 16;
int32_t balance : 21;
uint16_t days : 15;
uint16_t minutes : 11;
uint8_t mode : 3;
uint16_t usage : 4;
bool auto_topup : 1;
uint8_t weekly_journeys : 4;
uint16_t checksum : 16;
} OpalFile;
static_assert(sizeof(OpalFile) == 16);
// Converts an Opal timestamp to FuriHalRtcDateTime.
//
// Opal measures days since 1980-01-01 and minutes since midnight, and presumes
// all days are 1440 minutes.
void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) {
if(!out) return;
uint16_t diy;
out->year = 1980;
out->month = 1;
// 1980-01-01 is a Tuesday
out->weekday = ((days + 1) % 7) + 1;
out->hour = minutes / 60;
out->minute = minutes % 60;
out->second = 0;
// What year is it?
for(;;) {
diy = furi_hal_rtc_get_days_per_year(out->year);
if(days < diy) break;
days -= diy;
out->year++;
}
// 1-index the day of the year
days++;
// What month is it?
bool is_leap = furi_hal_rtc_is_leap_year(out->year);
for(;;) {
uint8_t dim = furi_hal_rtc_get_days_per_month(is_leap, out->month);
if(days <= dim) break;
days -= dim;
out->month++;
}
out->day = days;
}
bool opal_parser_parse(NfcDeviceData* dev_data) {
if(dev_data->protocol != NfcDeviceProtocolMifareDesfire) {
return false;
}
MifareDesfireApplication* app = mf_df_get_application(&dev_data->mf_df_data, &opal_aid);
if(app == NULL) {
return false;
}
MifareDesfireFile* f = mf_df_get_file(app, 0x07);
if(f == NULL || f->type != MifareDesfireFileTypeStandard || f->settings.data.size != 16 ||
!f->contents) {
return false;
}
OpalFile* o = (OpalFile*)f->contents;
uint8_t serial2 = o->serial / 10000000;
uint16_t serial3 = (o->serial / 1000) % 10000;
uint16_t serial4 = (o->serial % 1000);
if(o->check_digit > 9) {
return false;
}
char* sign = "";
if(o->balance < 0) {
// Negative balance. Make this a positive value again and record the
// sign separately, because then we can handle balances of -99..-1
// cents, as the "dollars" division below would result in a positive
// zero value.
o->balance = abs(o->balance); //-V1081
sign = "-";
}
uint8_t cents = o->balance % 100;
int32_t dollars = o->balance / 100;
FuriHalRtcDateTime timestamp;
opal_date_time_to_furi(o->days, o->minutes, &timestamp);
if(o->mode >= 3) {
// 3..7 are "reserved", but we use 4 to indicate the Manly Ferry.
o->mode = 3;
}
if(o->usage >= 4 && o->usage <= 6) {
// Usages 4..6 associated with the Manly Ferry, which correspond to
// usages 1..3 for other modes.
o->usage -= 3;
o->mode = 4;
}
const char* mode_str = (o->mode <= 4 ? opal_modes[o->mode] : opal_modes[3]); //-V547
const char* usage_str = (o->usage <= 12 ? opal_usages[o->usage] : opal_usages[13]);
furi_string_printf(
dev_data->parsed_data,
"\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n",
sign,
dollars,
cents,
serial2,
serial3,
serial4,
o->check_digit,
mode_str,
usage_str);
FuriString* timestamp_str = furi_string_alloc();
locale_format_date(timestamp_str, &timestamp, locale_get_date_format(), "-");
furi_string_cat(dev_data->parsed_data, timestamp_str);
furi_string_cat_str(dev_data->parsed_data, " at ");
locale_format_time(timestamp_str, &timestamp, locale_get_time_format(), false);
furi_string_cat(dev_data->parsed_data, timestamp_str);
furi_string_free(timestamp_str);
furi_string_cat_printf(
dev_data->parsed_data,
"\nWeekly journeys: %hhu, Txn #%hu\n",
o->weekly_journeys,
o->txn_number);
if(o->auto_topup) {
furi_string_cat_str(dev_data->parsed_data, "Auto-topup enabled\n");
}
if(o->blocked) {
furi_string_cat_str(dev_data->parsed_data, "Card blocked\n");
}
return true;
}

5
lib/nfc/parsers/opal.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include "nfc_supported_card.h"
bool opal_parser_parse(NfcDeviceData* dev_data);

View File

@@ -42,6 +42,30 @@ void mf_df_clear(MifareDesfireData* data) {
data->app_head = NULL;
}
MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]) {
if(!data) {
return NULL;
}
for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
if(memcmp(aid, app->id, 3) == 0) {
return app;
}
}
return NULL;
}
MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id) {
if(!app) {
return NULL;
}
for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
if(file->id == id) {
return file;
}
}
return NULL;
}
void mf_df_cat_data(MifareDesfireData* data, FuriString* out) {
mf_df_cat_card_info(data, out);
for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {

View File

@@ -130,6 +130,9 @@ void mf_df_cat_file(MifareDesfireFile* file, FuriString* out);
bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]);
MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id);
uint16_t mf_df_prepare_get_version(uint8_t* dest);
bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out);

View File

@@ -79,6 +79,8 @@ static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) {
MfUltralightSupportSectorSelect;
case MfUltralightTypeNTAG203:
return MfUltralightSupportCompatWrite | MfUltralightSupportCounterInMemory;
case MfUltralightTypeULC:
return MfUltralightSupportCompatWrite | MfUltralightSupport3DesAuth;
default:
// Assumed original MFUL 512-bit
return MfUltralightSupportCompatWrite;
@@ -95,6 +97,11 @@ static void mf_ul_set_version_ntag203(MfUltralightReader* reader, MfUltralightDa
reader->pages_to_read = 42;
}
static void mf_ul_set_version_ulc(MfUltralightReader* reader, MfUltralightData* data) {
data->type = MfUltralightTypeULC;
reader->pages_to_read = 48;
}
bool mf_ultralight_read_version(
FuriHalNfcTxRxContext* tx_rx,
MfUltralightReader* reader,
@@ -175,7 +182,7 @@ bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint
do {
FURI_LOG_D(TAG, "Authenticating");
tx_rx->tx_data[0] = MF_UL_AUTH;
tx_rx->tx_data[0] = MF_UL_PWD_AUTH;
nfc_util_num2bytes(key, 4, &tx_rx->tx_data[1]);
tx_rx->tx_bits = 40;
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
@@ -716,6 +723,21 @@ bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralight
return flag_read == 2;
}
static bool mf_ul_probe_3des_auth(FuriHalNfcTxRxContext* tx_rx) {
tx_rx->tx_data[0] = MF_UL_AUTHENTICATE_1;
tx_rx->tx_data[1] = 0;
tx_rx->tx_bits = 16;
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
bool rc = furi_hal_nfc_tx_rx(tx_rx, 50) && tx_rx->rx_bits == 9 * 8 &&
tx_rx->rx_data[0] == 0xAF;
// Reset just in case, we're not going to finish authenticating and need to if tag doesn't support auth
furi_hal_nfc_sleep();
furi_hal_nfc_activate_nfca(300, NULL);
return rc;
}
bool mf_ul_read_card(
FuriHalNfcTxRxContext* tx_rx,
MfUltralightReader* reader,
@@ -733,16 +755,20 @@ bool mf_ul_read_card(
mf_ultralight_read_signature(tx_rx, data);
}
} else {
// No GET_VERSION command, check for NTAG203 by reading last page (41)
uint8_t dummy[16];
if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) {
// No GET_VERSION command, check if AUTHENTICATE command available (detect UL C).
if(mf_ul_probe_3des_auth(tx_rx)) {
mf_ul_set_version_ulc(reader, data);
} else if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) {
// No AUTHENTICATE, check for NTAG203 by reading last page (41)
mf_ul_set_version_ntag203(reader, data);
reader->supported_features = mf_ul_get_features(data->type);
} else {
// We're really an original Mifare Ultralight, reset tag for safety
furi_hal_nfc_sleep();
furi_hal_nfc_activate_nfca(300, NULL);
}
reader->supported_features = mf_ul_get_features(data->type);
}
card_read = mf_ultralight_read_pages(tx_rx, reader, data);
@@ -1228,6 +1254,10 @@ static void mf_ul_emulate_write(
emulator->data_changed = true;
}
bool mf_ul_emulation_supported(MfUltralightData* data) {
return data->type != MfUltralightTypeULC;
}
void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) {
emulator->comp_write_cmd_started = false;
emulator->sector_select_cmd_started = false;
@@ -1732,7 +1762,7 @@ bool mf_ul_prepare_emulation_response(
}
}
}
} else if(cmd == MF_UL_AUTH) {
} else if(cmd == MF_UL_PWD_AUTH) {
if(emulator->supported_features & MfUltralightSupportAuth) {
if(buff_rx_len == (1 + 4) * 8) {
// Record password sent by PCD

View File

@@ -16,7 +16,8 @@
#define MF_UL_COMP_WRITE (0xA0)
#define MF_UL_READ_CNT (0x39)
#define MF_UL_INC_CNT (0xA5)
#define MF_UL_AUTH (0x1B)
#define MF_UL_AUTHENTICATE_1 (0x1A)
#define MF_UL_PWD_AUTH (0x1B)
#define MF_UL_READ_SIG (0x3C)
#define MF_UL_CHECK_TEARING (0x3E)
#define MF_UL_READ_VCSL (0x4B)
@@ -41,6 +42,7 @@ typedef enum {
typedef enum {
MfUltralightTypeUnknown,
MfUltralightTypeNTAG203,
MfUltralightTypeULC,
// Below have config pages and GET_VERSION support
MfUltralightTypeUL11,
MfUltralightTypeUL21,
@@ -77,6 +79,7 @@ typedef enum {
MfUltralightSupportAsciiMirror = 1 << 11,
// NTAG203 counter that's in memory rather than through a command
MfUltralightSupportCounterInMemory = 1 << 12,
MfUltralightSupport3DesAuth = 1 << 13,
} MfUltralightFeatures;
typedef enum {
@@ -237,6 +240,8 @@ bool mf_ul_read_card(
MfUltralightReader* reader,
MfUltralightData* data);
bool mf_ul_emulation_supported(MfUltralightData* data);
void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle);
void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data);

View File

@@ -52,7 +52,7 @@ ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) {
uint16_t received = 0;
for(size_t block = 0; block < nfcv_data->block_num; block++) {
uint8_t rxBuf[32];
FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1));
//FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1));
ReturnCode ret = ERR_NONE;
for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
@@ -64,18 +64,18 @@ ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) {
}
}
if(ret != ERR_NONE) {
FURI_LOG_D(TAG, "failed to read: %d", ret);
//FURI_LOG_D(TAG, "failed to read: %d", ret);
return ret;
}
memcpy(
&(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size);
FURI_LOG_D(
/*FURI_LOG_D(
TAG,
" %02X %02X %02X %02X",
nfcv_data->data[block * nfcv_data->block_size + 0],
nfcv_data->data[block * nfcv_data->block_size + 1],
nfcv_data->data[block * nfcv_data->block_size + 2],
nfcv_data->data[block * nfcv_data->block_size + 3]);
nfcv_data->data[block * nfcv_data->block_size + 3]);*/
}
return ERR_NONE;
@@ -86,7 +86,7 @@ ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
uint16_t received = 0;
ReturnCode ret = ERR_NONE;
FURI_LOG_D(TAG, "Read SYSTEM INFORMATION...");
//FURI_LOG_D(TAG, "Read SYSTEM INFORMATION...");
for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
/* TODO: needs proper abstraction via fury_hal(_ll)_* */
@@ -110,7 +110,7 @@ ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
nfcv_data->block_num = rxBuf[NFCV_UID_LENGTH + 4] + 1;
nfcv_data->block_size = rxBuf[NFCV_UID_LENGTH + 5] + 1;
nfcv_data->ic_ref = rxBuf[NFCV_UID_LENGTH + 6];
FURI_LOG_D(
/*FURI_LOG_D(
TAG,
" UID: %02X %02X %02X %02X %02X %02X %02X %02X",
nfc_data->uid[0],
@@ -128,10 +128,10 @@ ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
nfcv_data->afi,
nfcv_data->block_num,
nfcv_data->block_size,
nfcv_data->ic_ref);
nfcv_data->ic_ref);*/
return ret;
}
FURI_LOG_D(TAG, "Failed: %d", ret);
//FURI_LOG_D(TAG, "Failed: %d", ret);
return ret;
}
@@ -1081,9 +1081,9 @@ void nfcv_emu_sniff_packet(
break;
}
if(strlen(nfcv_data->last_command) > 0) {
/*if(strlen(nfcv_data->last_command) > 0) {
FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command);
}
}*/
}
void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
@@ -1137,7 +1137,7 @@ void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
}
}
FURI_LOG_D(TAG, "Starting NfcV emulation");
/*FURI_LOG_D(TAG, "Starting NfcV emulation");
FURI_LOG_D(
TAG,
" UID: %02X %02X %02X %02X %02X %02X %02X %02X",
@@ -1148,7 +1148,7 @@ void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
nfc_data->uid[4],
nfc_data->uid[5],
nfc_data->uid[6],
nfc_data->uid[7]);
nfc_data->uid[7]);*/
switch(nfcv_data->sub_type) {
case NfcVTypeSlixL:

View File

@@ -135,6 +135,7 @@ void pulse_reader_stop(PulseReader* signal) {
LL_DMA_DisableChannel(DMA1, signal->dma_channel + 1);
LL_DMAMUX_DisableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0);
LL_TIM_DisableCounter(TIM2);
furi_hal_bus_disable(FuriHalBusTIM2);
furi_hal_gpio_init_simple(signal->gpio, GpioModeAnalog);
}
@@ -146,6 +147,8 @@ void pulse_reader_start(PulseReader* signal) {
signal->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)signal->gpio_buffer;
signal->dma_config_gpio.NbData = signal->size;
furi_hal_bus_enable(FuriHalBusTIM2);
/* start counter */
LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);

View File

@@ -345,7 +345,7 @@ static void
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->btn);
}
subghz_custom_btn_set_max(3);
subghz_custom_btn_set_max(4);
}
/**
@@ -377,7 +377,7 @@ static uint64_t subghz_protocol_secplus_v2_encode_half(uint8_t roll_array[], uin
/**
* Defines the button value for the current btn_id
* Basic set | 0x68 | 0x80 | 0x81 | 0xE2 |
* Basic set | 0x68 | 0x80 | 0x81 | 0xE2 | 0x78
* @return Button code
*/
static uint8_t subghz_protocol_secplus_v2_get_btn_code();
@@ -856,6 +856,9 @@ static uint8_t subghz_protocol_secplus_v2_get_btn_code() {
case 0xE2:
btn = 0x80;
break;
case 0x78:
btn = 0x80;
break;
default:
break;
@@ -874,6 +877,9 @@ static uint8_t subghz_protocol_secplus_v2_get_btn_code() {
case 0xE2:
btn = 0x81;
break;
case 0x78:
btn = 0x81;
break;
default:
break;
@@ -892,6 +898,30 @@ static uint8_t subghz_protocol_secplus_v2_get_btn_code() {
case 0xE2:
btn = 0x68;
break;
case 0x78:
btn = 0xE2;
break;
default:
break;
}
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) {
switch(original_btn_code) {
case 0x68:
btn = 0x78;
break;
case 0x80:
btn = 0x78;
break;
case 0x81:
btn = 0x78;
break;
case 0xE2:
btn = 0x78;
break;
case 0x78:
btn = 0x68;
break;
default:
break;

View File

@@ -8,6 +8,7 @@ env.Append(
"#/lib/toolbox",
],
SDK_HEADERS=[
File("api_lock.h"),
File("value_index.h"),
File("manchester_decoder.h"),
File("manchester_encoder.h"),

View File

@@ -5,7 +5,7 @@
#define VERSION_MAGIC (0xBE40u)
#define VERSION_MAJOR (0x1u)
#define VERSION_MINOR (0x0u)
#define VERSION_MINOR (0x1u)
struct Version {
// Header
@@ -20,6 +20,9 @@ struct Version {
// Payload bits and pieces
const uint8_t target;
const bool build_is_dirty;
// v 1.1
const char* firmware_origin;
const char* git_origin;
const char* custom_flipper_name;
};
@@ -38,6 +41,8 @@ static Version version = {
,
.target = TARGET,
.build_is_dirty = BUILD_DIRTY,
.firmware_origin = FIRMWARE_ORIGIN,
.git_origin = GIT_ORIGIN,
.custom_flipper_name = NULL,
};
@@ -83,3 +88,11 @@ uint8_t version_get_target(const Version* v) {
bool version_get_dirty_flag(const Version* v) {
return v ? v->build_is_dirty : version.build_is_dirty;
}
const char* version_get_firmware_origin(const Version* v) {
return v ? v->firmware_origin : version.firmware_origin;
}
const char* version_get_git_origin(const Version* v) {
return v ? v->git_origin : version.git_origin;
}

View File

@@ -100,6 +100,17 @@ uint8_t version_get_target(const Version* v);
*/
bool version_get_dirty_flag(const Version* v);
/**
* Get firmware origin. "Official" for mainline firmware, fork name for forks.
* Set by FIRMWARE_ORIGIN fbt argument.
*/
const char* version_get_firmware_origin(const Version* v);
/**
* Get git repo origin
*/
const char* version_get_git_origin(const Version* v);
#ifdef __cplusplus
}
#endif