Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
noproto
2025-08-24 22:55:51 -04:00
583 changed files with 14421 additions and 4617 deletions
+1
View File
@@ -43,6 +43,7 @@ libs = env.BuildModules(
"ble_profile",
"bit_lib",
"datetime",
"ieee754_parse_wrap",
],
)
+4 -1
View File
@@ -380,7 +380,10 @@ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta)
#define CONNECTION_INTERVAL_MAX (0x24)
static GapConfig template_config = {
.adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
.adv_service = {
.UUID_Type = UUID_TYPE_16,
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
},
.appearance_char = GAP_APPEARANCE_KEYBOARD,
.bonding_mode = true,
.pairing_method = GapPairingPinCodeVerifyYesNo,
+19 -19
View File
@@ -35,7 +35,7 @@ typedef struct {
static bq25896_regs_t bq25896_regs;
bool bq25896_init(FuriHalI2cBusHandle* handle) {
bool bq25896_init(const FuriHalI2cBusHandle* handle) {
bool result = true;
bq25896_regs.r14.REG_RST = 1;
@@ -78,19 +78,19 @@ bool bq25896_init(FuriHalI2cBusHandle* handle) {
return result;
}
void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim) {
void bq25896_set_boost_lim(const FuriHalI2cBusHandle* handle, BoostLim boost_lim) {
bq25896_regs.r0A.BOOST_LIM = boost_lim;
furi_hal_i2c_write_reg_8(
handle, BQ25896_ADDRESS, 0x0A, *(uint8_t*)&bq25896_regs.r0A, BQ25896_I2C_TIMEOUT);
}
void bq25896_poweroff(FuriHalI2cBusHandle* handle) {
void bq25896_poweroff(const FuriHalI2cBusHandle* handle) {
bq25896_regs.r09.BATFET_DIS = 1;
furi_hal_i2c_write_reg_8(
handle, BQ25896_ADDRESS, 0x09, *(uint8_t*)&bq25896_regs.r09, BQ25896_I2C_TIMEOUT);
}
ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) {
ChrgStat bq25896_get_charge_status(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_mem(
handle,
BQ25896_ADDRESS,
@@ -103,52 +103,52 @@ ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) {
return bq25896_regs.r0B.CHRG_STAT;
}
bool bq25896_is_charging(FuriHalI2cBusHandle* handle) {
bool bq25896_is_charging(const FuriHalI2cBusHandle* handle) {
// Include precharge, fast charging, and charging termination done as "charging"
return bq25896_get_charge_status(handle) != ChrgStatNo;
}
bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle) {
bool bq25896_is_charging_done(const FuriHalI2cBusHandle* handle) {
return bq25896_get_charge_status(handle) == ChrgStatDone;
}
void bq25896_enable_charging(FuriHalI2cBusHandle* handle) {
void bq25896_enable_charging(const FuriHalI2cBusHandle* handle) {
bq25896_regs.r03.CHG_CONFIG = 1;
furi_hal_i2c_write_reg_8(
handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT);
}
void bq25896_disable_charging(FuriHalI2cBusHandle* handle) {
void bq25896_disable_charging(const FuriHalI2cBusHandle* handle) {
bq25896_regs.r03.CHG_CONFIG = 0;
furi_hal_i2c_write_reg_8(
handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT);
}
void bq25896_enable_otg(FuriHalI2cBusHandle* handle) {
void bq25896_enable_otg(const FuriHalI2cBusHandle* handle) {
bq25896_regs.r03.OTG_CONFIG = 1;
furi_hal_i2c_write_reg_8(
handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT);
}
void bq25896_disable_otg(FuriHalI2cBusHandle* handle) {
void bq25896_disable_otg(const FuriHalI2cBusHandle* handle) {
bq25896_regs.r03.OTG_CONFIG = 0;
furi_hal_i2c_write_reg_8(
handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT);
}
bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle) {
bool bq25896_is_otg_enabled(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_reg_8(
handle, BQ25896_ADDRESS, 0x03, (uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT);
return bq25896_regs.r03.OTG_CONFIG;
}
uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) {
uint16_t bq25896_get_vreg_voltage(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_reg_8(
handle, BQ25896_ADDRESS, 0x06, (uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT);
return (uint16_t)bq25896_regs.r06.VREG * 16 + 3840;
}
void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) {
void bq25896_set_vreg_voltage(const FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) {
if(vreg_voltage < 3840) {
// Minimum valid value is 3840 mV
vreg_voltage = 3840;
@@ -166,13 +166,13 @@ void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage
handle, BQ25896_ADDRESS, 0x06, *(uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT);
}
bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle) {
bool bq25896_check_otg_fault(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_reg_8(
handle, BQ25896_ADDRESS, 0x0C, (uint8_t*)&bq25896_regs.r0C, BQ25896_I2C_TIMEOUT);
return bq25896_regs.r0C.BOOST_FAULT;
}
uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle) {
uint16_t bq25896_get_vbus_voltage(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_reg_8(
handle, BQ25896_ADDRESS, 0x11, (uint8_t*)&bq25896_regs.r11, BQ25896_I2C_TIMEOUT);
if(bq25896_regs.r11.VBUS_GD) {
@@ -182,25 +182,25 @@ uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle) {
}
}
uint16_t bq25896_get_vsys_voltage(FuriHalI2cBusHandle* handle) {
uint16_t bq25896_get_vsys_voltage(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_reg_8(
handle, BQ25896_ADDRESS, 0x0F, (uint8_t*)&bq25896_regs.r0F, BQ25896_I2C_TIMEOUT);
return (uint16_t)bq25896_regs.r0F.SYSV * 20 + 2304;
}
uint16_t bq25896_get_vbat_voltage(FuriHalI2cBusHandle* handle) {
uint16_t bq25896_get_vbat_voltage(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_reg_8(
handle, BQ25896_ADDRESS, 0x0E, (uint8_t*)&bq25896_regs.r0E, BQ25896_I2C_TIMEOUT);
return (uint16_t)bq25896_regs.r0E.BATV * 20 + 2304;
}
uint16_t bq25896_get_vbat_current(FuriHalI2cBusHandle* handle) {
uint16_t bq25896_get_vbat_current(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_reg_8(
handle, BQ25896_ADDRESS, 0x12, (uint8_t*)&bq25896_regs.r12, BQ25896_I2C_TIMEOUT);
return (uint16_t)bq25896_regs.r12.ICHGR * 50;
}
uint32_t bq25896_get_ntc_mpct(FuriHalI2cBusHandle* handle) {
uint32_t bq25896_get_ntc_mpct(const FuriHalI2cBusHandle* handle) {
furi_hal_i2c_read_reg_8(
handle, BQ25896_ADDRESS, 0x10, (uint8_t*)&bq25896_regs.r10, BQ25896_I2C_TIMEOUT);
return (uint32_t)bq25896_regs.r10.TSPCT * 465 + 21000;
+19 -19
View File
@@ -7,61 +7,61 @@
#include <furi_hal_i2c.h>
/** Initialize Driver */
bool bq25896_init(FuriHalI2cBusHandle* handle);
bool bq25896_init(const FuriHalI2cBusHandle* handle);
/** Set boost lim*/
void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim);
void bq25896_set_boost_lim(const FuriHalI2cBusHandle* handle, BoostLim boost_lim);
/** Send device into shipping mode */
void bq25896_poweroff(FuriHalI2cBusHandle* handle);
void bq25896_poweroff(const FuriHalI2cBusHandle* handle);
/** Get charging status */
ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle);
ChrgStat bq25896_get_charge_status(const FuriHalI2cBusHandle* handle);
/** Is currently charging */
bool bq25896_is_charging(FuriHalI2cBusHandle* handle);
bool bq25896_is_charging(const FuriHalI2cBusHandle* handle);
/** Is charging completed while connected to charger */
bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle);
bool bq25896_is_charging_done(const FuriHalI2cBusHandle* handle);
/** Enable charging */
void bq25896_enable_charging(FuriHalI2cBusHandle* handle);
void bq25896_enable_charging(const FuriHalI2cBusHandle* handle);
/** Disable charging */
void bq25896_disable_charging(FuriHalI2cBusHandle* handle);
void bq25896_disable_charging(const FuriHalI2cBusHandle* handle);
/** Enable otg */
void bq25896_enable_otg(FuriHalI2cBusHandle* handle);
void bq25896_enable_otg(const FuriHalI2cBusHandle* handle);
/** Disable otg */
void bq25896_disable_otg(FuriHalI2cBusHandle* handle);
void bq25896_disable_otg(const FuriHalI2cBusHandle* handle);
/** Is otg enabled */
bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle);
bool bq25896_is_otg_enabled(const FuriHalI2cBusHandle* handle);
/** Get VREG (charging limit) voltage in mV */
uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle);
uint16_t bq25896_get_vreg_voltage(const FuriHalI2cBusHandle* handle);
/** Set VREG (charging limit) voltage in mV
*
* Valid range: 3840mV - 4208mV, in steps of 16mV
*/
void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage);
void bq25896_set_vreg_voltage(const FuriHalI2cBusHandle* handle, uint16_t vreg_voltage);
/** Check OTG BOOST Fault status */
bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle);
bool bq25896_check_otg_fault(const FuriHalI2cBusHandle* handle);
/** Get VBUS Voltage in mV */
uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle);
uint16_t bq25896_get_vbus_voltage(const FuriHalI2cBusHandle* handle);
/** Get VSYS Voltage in mV */
uint16_t bq25896_get_vsys_voltage(FuriHalI2cBusHandle* handle);
uint16_t bq25896_get_vsys_voltage(const FuriHalI2cBusHandle* handle);
/** Get VBAT Voltage in mV */
uint16_t bq25896_get_vbat_voltage(FuriHalI2cBusHandle* handle);
uint16_t bq25896_get_vbat_voltage(const FuriHalI2cBusHandle* handle);
/** Get VBAT current in mA */
uint16_t bq25896_get_vbat_current(FuriHalI2cBusHandle* handle);
uint16_t bq25896_get_vbat_current(const FuriHalI2cBusHandle* handle);
/** Get NTC voltage in mpct of REGN */
uint32_t bq25896_get_ntc_mpct(FuriHalI2cBusHandle* handle);
uint32_t bq25896_get_ntc_mpct(const FuriHalI2cBusHandle* handle);
+29 -23
View File
@@ -43,7 +43,7 @@
#endif
static inline bool bq27220_read_reg(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
uint8_t address,
uint8_t* buffer,
size_t buffer_size) {
@@ -52,7 +52,7 @@ static inline bool bq27220_read_reg(
}
static inline bool bq27220_write(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
uint8_t address,
const uint8_t* buffer,
size_t buffer_size) {
@@ -60,11 +60,11 @@ static inline bool bq27220_write(
handle, BQ27220_ADDRESS, address, buffer, buffer_size, BQ27220_I2C_TIMEOUT);
}
static inline bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) {
static inline bool bq27220_control(const 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) {
static uint16_t bq27220_read_word(const FuriHalI2cBusHandle* handle, uint8_t address) {
uint16_t buf = BQ27220_ERROR;
if(!bq27220_read_reg(handle, address, (uint8_t*)&buf, 2)) {
@@ -83,7 +83,7 @@ static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) {
}
static bool bq27220_parameter_check(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
uint16_t address,
uint32_t value,
size_t size,
@@ -163,7 +163,7 @@ static bool bq27220_parameter_check(
}
static bool bq27220_data_memory_check(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
const BQ27220DMData* data_memory,
bool update) {
if(update) {
@@ -268,7 +268,7 @@ static bool bq27220_data_memory_check(
return result;
}
bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) {
bool bq27220_init(const FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) {
bool result = false;
bool reset_and_provisioning_required = false;
@@ -365,7 +365,7 @@ bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory)
return result;
}
bool bq27220_reset(FuriHalI2cBusHandle* handle) {
bool bq27220_reset(const FuriHalI2cBusHandle* handle) {
bool result = false;
do {
if(!bq27220_control(handle, Control_RESET)) {
@@ -396,7 +396,7 @@ bool bq27220_reset(FuriHalI2cBusHandle* handle) {
return result;
}
bool bq27220_seal(FuriHalI2cBusHandle* handle) {
bool bq27220_seal(const FuriHalI2cBusHandle* handle) {
Bq27220OperationStatus operation_status = {0};
bool result = false;
do {
@@ -431,7 +431,7 @@ bool bq27220_seal(FuriHalI2cBusHandle* handle) {
return result;
}
bool bq27220_unseal(FuriHalI2cBusHandle* handle) {
bool bq27220_unseal(const FuriHalI2cBusHandle* handle) {
Bq27220OperationStatus operation_status = {0};
bool result = false;
do {
@@ -465,7 +465,7 @@ bool bq27220_unseal(FuriHalI2cBusHandle* handle) {
return result;
}
bool bq27220_full_access(FuriHalI2cBusHandle* handle) {
bool bq27220_full_access(const FuriHalI2cBusHandle* handle) {
bool result = false;
do {
@@ -518,29 +518,35 @@ bool bq27220_full_access(FuriHalI2cBusHandle* handle) {
return result;
}
uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) {
uint16_t bq27220_get_voltage(const FuriHalI2cBusHandle* handle) {
return bq27220_read_word(handle, CommandVoltage);
}
int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) {
int16_t bq27220_get_current(const FuriHalI2cBusHandle* handle) {
return bq27220_read_word(handle, CommandCurrent);
}
bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status) {
bool bq27220_get_control_status(
const 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) {
bool bq27220_get_battery_status(
const FuriHalI2cBusHandle* handle,
Bq27220BatteryStatus* battery_status) {
return bq27220_read_reg(handle, CommandBatteryStatus, (uint8_t*)battery_status, 2);
}
bool bq27220_get_operation_status(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
Bq27220OperationStatus* operation_status) {
return bq27220_read_reg(handle, CommandOperationStatus, (uint8_t*)operation_status, 2);
}
bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status) {
bool bq27220_get_gauging_status(
const 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");
@@ -552,26 +558,26 @@ bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatu
return bq27220_read_reg(handle, CommandMACData, (uint8_t*)gauging_status, 2);
}
uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle) {
uint16_t bq27220_get_temperature(const FuriHalI2cBusHandle* handle) {
return bq27220_read_word(handle, CommandTemperature);
}
uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle) {
uint16_t bq27220_get_full_charge_capacity(const FuriHalI2cBusHandle* handle) {
return bq27220_read_word(handle, CommandFullChargeCapacity);
}
uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle) {
uint16_t bq27220_get_design_capacity(const FuriHalI2cBusHandle* handle) {
return bq27220_read_word(handle, CommandDesignCapacity);
}
uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle) {
uint16_t bq27220_get_remaining_capacity(const FuriHalI2cBusHandle* handle) {
return bq27220_read_word(handle, CommandRemainingCapacity);
}
uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle) {
uint16_t bq27220_get_state_of_charge(const FuriHalI2cBusHandle* handle) {
return bq27220_read_word(handle, CommandStateOfCharge);
}
uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle) {
uint16_t bq27220_get_state_of_health(const FuriHalI2cBusHandle* handle) {
return bq27220_read_word(handle, CommandStateOfHealth);
}
+23 -17
View File
@@ -136,7 +136,7 @@ typedef struct BQ27220DMData BQ27220DMData;
*
* @return true on success, false otherwise
*/
bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory);
bool bq27220_init(const FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory);
/** Reset gauge
*
@@ -144,7 +144,7 @@ bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory)
*
* @return true on success, false otherwise
*/
bool bq27220_reset(FuriHalI2cBusHandle* handle);
bool bq27220_reset(const FuriHalI2cBusHandle* handle);
/** Seal gauge access
*
@@ -152,7 +152,7 @@ bool bq27220_reset(FuriHalI2cBusHandle* handle);
*
* @return true on success, false otherwise
*/
bool bq27220_seal(FuriHalI2cBusHandle* handle);
bool bq27220_seal(const FuriHalI2cBusHandle* handle);
/** Unseal gauge access
*
@@ -160,7 +160,7 @@ bool bq27220_seal(FuriHalI2cBusHandle* handle);
*
* @return true on success, false otherwise
*/
bool bq27220_unseal(FuriHalI2cBusHandle* handle);
bool bq27220_unseal(const FuriHalI2cBusHandle* handle);
/** Get full access
*
@@ -170,7 +170,7 @@ bool bq27220_unseal(FuriHalI2cBusHandle* handle);
*
* @return true on success, false otherwise
*/
bool bq27220_full_access(FuriHalI2cBusHandle* handle);
bool bq27220_full_access(const FuriHalI2cBusHandle* handle);
/** Get battery voltage
*
@@ -178,7 +178,7 @@ bool bq27220_full_access(FuriHalI2cBusHandle* handle);
*
* @return voltage in mV or BQ27220_ERROR
*/
uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle);
uint16_t bq27220_get_voltage(const FuriHalI2cBusHandle* handle);
/** Get current
*
@@ -186,7 +186,7 @@ uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle);
*
* @return current in mA or BQ27220_ERROR
*/
int16_t bq27220_get_current(FuriHalI2cBusHandle* handle);
int16_t bq27220_get_current(const FuriHalI2cBusHandle* handle);
/** Get control status
*
@@ -195,7 +195,9 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle);
*
* @return true on success, false otherwise
*/
bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status);
bool bq27220_get_control_status(
const FuriHalI2cBusHandle* handle,
Bq27220ControlStatus* control_status);
/** Get battery status
*
@@ -204,7 +206,9 @@ bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatu
*
* @return true on success, false otherwise
*/
bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status);
bool bq27220_get_battery_status(
const FuriHalI2cBusHandle* handle,
Bq27220BatteryStatus* battery_status);
/** Get operation status
*
@@ -214,7 +218,7 @@ bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatu
* @return true on success, false otherwise
*/
bool bq27220_get_operation_status(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
Bq27220OperationStatus* operation_status);
/** Get gauging status
@@ -224,7 +228,9 @@ bool bq27220_get_operation_status(
*
* @return true on success, false otherwise
*/
bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status);
bool bq27220_get_gauging_status(
const FuriHalI2cBusHandle* handle,
Bq27220GaugingStatus* gauging_status);
/** Get temperature
*
@@ -232,7 +238,7 @@ bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatu
*
* @return temperature in units of 0.1°K
*/
uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle);
uint16_t bq27220_get_temperature(const FuriHalI2cBusHandle* handle);
/** Get compensated full charge capacity
*
@@ -240,7 +246,7 @@ uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle);
*
* @return full charge capacity in mAh or BQ27220_ERROR
*/
uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle);
uint16_t bq27220_get_full_charge_capacity(const FuriHalI2cBusHandle* handle);
/** Get design capacity
*
@@ -248,7 +254,7 @@ uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle);
*
* @return design capacity in mAh or BQ27220_ERROR
*/
uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle);
uint16_t bq27220_get_design_capacity(const FuriHalI2cBusHandle* handle);
/** Get remaining capacity
*
@@ -256,7 +262,7 @@ uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle);
*
* @return remaining capacity in mAh or BQ27220_ERROR
*/
uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle);
uint16_t bq27220_get_remaining_capacity(const FuriHalI2cBusHandle* handle);
/** Get predicted remaining battery capacity
*
@@ -264,7 +270,7 @@ uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle);
*
* @return state of charge in percents or BQ27220_ERROR
*/
uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle);
uint16_t bq27220_get_state_of_charge(const FuriHalI2cBusHandle* handle);
/** Get ratio of full charge capacity over design capacity
*
@@ -272,4 +278,4 @@ uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle);
*
* @return state of health in percents or BQ27220_ERROR
*/
uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle);
uint16_t bq27220_get_state_of_health(const FuriHalI2cBusHandle* handle);
+26 -22
View File
@@ -3,7 +3,8 @@
#include <string.h>
#include <furi_hal_cortex.h>
static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) {
static bool
cc1101_spi_trx(const FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) {
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(CC1101_TIMEOUT * 1000);
while(furi_hal_gpio_read(handle->miso)) {
@@ -16,7 +17,7 @@ static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx
return true;
}
CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) {
CC1101Status cc1101_strobe(const FuriHalSpiBusHandle* handle, uint8_t strobe) {
uint8_t tx[1] = {strobe};
CC1101Status rx[1] = {0};
rx[0].CHIP_RDYn = 1;
@@ -27,7 +28,7 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) {
return rx[0];
}
CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
CC1101Status cc1101_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
uint8_t tx[2] = {reg, data};
CC1101Status rx[2] = {0};
rx[0].CHIP_RDYn = 1;
@@ -39,7 +40,7 @@ CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t
return rx[1];
}
CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data) {
CC1101Status cc1101_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data) {
assert(sizeof(CC1101Status) == 1);
uint8_t tx[2] = {reg | CC1101_READ, 0};
CC1101Status rx[2] = {0};
@@ -52,33 +53,36 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t*
return rx[0];
}
uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle) {
uint8_t cc1101_get_partnumber(const FuriHalSpiBusHandle* handle) {
uint8_t partnumber = 0;
cc1101_read_reg(handle, CC1101_STATUS_PARTNUM | CC1101_BURST, &partnumber);
return partnumber;
}
uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle) {
uint8_t cc1101_get_version(const FuriHalSpiBusHandle* handle) {
uint8_t version = 0;
cc1101_read_reg(handle, CC1101_STATUS_VERSION | CC1101_BURST, &version);
return version;
}
uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle) {
uint8_t cc1101_get_rssi(const FuriHalSpiBusHandle* handle) {
uint8_t rssi = 0;
cc1101_read_reg(handle, CC1101_STATUS_RSSI | CC1101_BURST, &rssi);
return rssi;
}
CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_reset(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_SRES);
}
CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_get_status(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_SNOP);
}
bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, uint32_t timeout_us) {
bool cc1101_wait_status_state(
const FuriHalSpiBusHandle* handle,
CC1101State state,
uint32_t timeout_us) {
bool result = false;
CC1101Status status = {0};
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_us);
@@ -92,35 +96,35 @@ bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, ui
return result;
}
CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_shutdown(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_SPWD);
}
CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_calibrate(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_SCAL);
}
CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_switch_to_idle(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_SIDLE);
}
CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_switch_to_rx(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_SRX);
}
CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_switch_to_tx(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_STX);
}
CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_flush_rx(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_SFRX);
}
CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle) {
CC1101Status cc1101_flush_tx(const FuriHalSpiBusHandle* handle) {
return cc1101_strobe(handle, CC1101_STROBE_SFTX);
}
uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) {
uint32_t cc1101_set_frequency(const FuriHalSpiBusHandle* handle, uint32_t value) {
uint64_t real_value = (uint64_t)value * CC1101_FDIV / CC1101_QUARTZ;
// Sanity check
@@ -135,7 +139,7 @@ uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) {
return (uint32_t)real_frequency;
}
uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t value) {
uint32_t cc1101_set_intermediate_frequency(const FuriHalSpiBusHandle* handle, uint32_t value) {
uint64_t real_value = value * CC1101_IFDIV / CC1101_QUARTZ;
assert((real_value & 0xFF) == real_value);
@@ -146,7 +150,7 @@ uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t
return (uint32_t)real_frequency;
}
void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) {
void cc1101_set_pa_table(const FuriHalSpiBusHandle* handle, const uint8_t value[8]) {
uint8_t tx[9] = {CC1101_PATABLE | CC1101_BURST}; //-V1009
CC1101Status rx[9] = {0};
rx[0].CHIP_RDYn = 1;
@@ -159,7 +163,7 @@ void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) {
assert((rx[0].CHIP_RDYn | rx[8].CHIP_RDYn) == 0);
}
uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size) {
uint8_t cc1101_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size) {
uint8_t buff_tx[64];
uint8_t buff_rx[64];
buff_tx[0] = CC1101_FIFO | CC1101_BURST;
@@ -170,7 +174,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint
return size;
}
uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size) {
uint8_t cc1101_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size) {
uint8_t buff_trx[2];
buff_trx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST;
+24 -21
View File
@@ -19,7 +19,7 @@ extern "C" {
*
* @return device status
*/
CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe);
CC1101Status cc1101_strobe(const FuriHalSpiBusHandle* handle, uint8_t strobe);
/** Write device register
*
@@ -29,7 +29,7 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe);
*
* @return device status
*/
CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
CC1101Status cc1101_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
/** Read device register
*
@@ -39,7 +39,7 @@ CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t
*
* @return device status
*/
CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data);
CC1101Status cc1101_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data);
/* High level API */
@@ -49,7 +49,7 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t*
*
* @return CC1101Status structure
*/
CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_reset(const FuriHalSpiBusHandle* handle);
/** Get status
*
@@ -57,7 +57,7 @@ CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle);
*
* @return CC1101Status structure
*/
CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_get_status(const FuriHalSpiBusHandle* handle);
/** Wait specific chip state
*
@@ -67,7 +67,10 @@ CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle);
*
* @return true on success, false otherwise
*/
bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, uint32_t timeout_us);
bool cc1101_wait_status_state(
const FuriHalSpiBusHandle* handle,
CC1101State state,
uint32_t timeout_us);
/** Enable shutdown mode
*
@@ -75,7 +78,7 @@ bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, ui
*
* @return CC1101Status structure
*/
CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_shutdown(const FuriHalSpiBusHandle* handle);
/** Get Partnumber
*
@@ -83,7 +86,7 @@ CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle);
*
* @return part number id
*/
uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle);
uint8_t cc1101_get_partnumber(const FuriHalSpiBusHandle* handle);
/** Get Version
*
@@ -91,7 +94,7 @@ uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle);
*
* @return version
*/
uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle);
uint8_t cc1101_get_version(const FuriHalSpiBusHandle* handle);
/** Get raw RSSI value
*
@@ -99,7 +102,7 @@ uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle);
*
* @return rssi value
*/
uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle);
uint8_t cc1101_get_rssi(const FuriHalSpiBusHandle* handle);
/** Calibrate oscillator
*
@@ -107,13 +110,13 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle);
*
* @return CC1101Status structure
*/
CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_calibrate(const FuriHalSpiBusHandle* handle);
/** Switch to idle
*
* @param handle - pointer to FuriHalSpiHandle
*/
CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_switch_to_idle(const FuriHalSpiBusHandle* handle);
/** Switch to RX
*
@@ -121,7 +124,7 @@ CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle);
*
* @return CC1101Status structure
*/
CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_switch_to_rx(const FuriHalSpiBusHandle* handle);
/** Switch to TX
*
@@ -129,7 +132,7 @@ CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle);
*
* @return CC1101Status structure
*/
CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_switch_to_tx(const FuriHalSpiBusHandle* handle);
/** Flush RX FIFO
*
@@ -137,13 +140,13 @@ CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle);
*
* @return CC1101Status structure
*/
CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_flush_rx(const FuriHalSpiBusHandle* handle);
/** Flush TX FIFO
*
* @param handle - pointer to FuriHalSpiHandle
*/
CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle);
CC1101Status cc1101_flush_tx(const FuriHalSpiBusHandle* handle);
/** Set Frequency
*
@@ -152,7 +155,7 @@ CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle);
*
* @return real frequency that were synthesized
*/
uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value);
uint32_t cc1101_set_frequency(const FuriHalSpiBusHandle* handle, uint32_t value);
/** Set Intermediate Frequency
*
@@ -161,14 +164,14 @@ uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value);
*
* @return real inermediate frequency that were synthesized
*/
uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t value);
uint32_t cc1101_set_intermediate_frequency(const FuriHalSpiBusHandle* handle, uint32_t value);
/** Set Power Amplifier level table, ramp
*
* @param handle - pointer to FuriHalSpiHandle
* @param value - array of power level values
*/
void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]);
void cc1101_set_pa_table(const FuriHalSpiBusHandle* handle, const uint8_t value[8]);
/** Write FIFO
*
@@ -178,7 +181,7 @@ void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]);
*
* @return size, written bytes count
*/
uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size);
uint8_t cc1101_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size);
/** Read FIFO
*
@@ -188,7 +191,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint
*
* @return size, read bytes count
*/
uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size);
uint8_t cc1101_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size);
#ifdef __cplusplus
}
+20 -11
View File
@@ -3,12 +3,12 @@
#include "lp5562_reg.h"
#include <furi_hal.h>
void lp5562_reset(FuriHalI2cBusHandle* handle) {
void lp5562_reset(const FuriHalI2cBusHandle* handle) {
Reg0D_Reset reg = {.value = 0xFF};
furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x0D, *(uint8_t*)&reg, LP5562_I2C_TIMEOUT);
}
void lp5562_configure(FuriHalI2cBusHandle* handle) {
void lp5562_configure(const FuriHalI2cBusHandle* handle) {
Reg08_Config config = {.INT_CLK_EN = true, .PS_EN = true, .PWM_HF = true};
furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x08, *(uint8_t*)&config, LP5562_I2C_TIMEOUT);
@@ -21,14 +21,17 @@ void lp5562_configure(FuriHalI2cBusHandle* handle) {
furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x70, *(uint8_t*)&map, LP5562_I2C_TIMEOUT);
}
void lp5562_enable(FuriHalI2cBusHandle* handle) {
void lp5562_enable(const FuriHalI2cBusHandle* handle) {
Reg00_Enable reg = {.CHIP_EN = true, .LOG_EN = true};
furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x00, *(uint8_t*)&reg, LP5562_I2C_TIMEOUT);
//>488μs delay is required after writing to 0x00 register, otherwise program engine will not work
furi_delay_us(500);
}
void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value) {
void lp5562_set_channel_current(
const FuriHalI2cBusHandle* handle,
LP5562Channel channel,
uint8_t value) {
uint8_t reg_no;
if(channel == LP5562ChannelRed) {
reg_no = LP5562_CHANNEL_RED_CURRENT_REGISTER;
@@ -44,7 +47,10 @@ void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel chann
furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, reg_no, value, LP5562_I2C_TIMEOUT);
}
void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value) {
void lp5562_set_channel_value(
const FuriHalI2cBusHandle* handle,
LP5562Channel channel,
uint8_t value) {
uint8_t reg_no;
if(channel == LP5562ChannelRed) {
reg_no = LP5562_CHANNEL_RED_VALUE_REGISTER;
@@ -60,7 +66,7 @@ void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel
furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, reg_no, value, LP5562_I2C_TIMEOUT);
}
uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel) {
uint8_t lp5562_get_channel_value(const FuriHalI2cBusHandle* handle, LP5562Channel channel) {
uint8_t reg_no;
uint8_t value;
if(channel == LP5562ChannelRed) {
@@ -78,7 +84,10 @@ uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel chan
return value;
}
void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src) {
void lp5562_set_channel_src(
const FuriHalI2cBusHandle* handle,
LP5562Channel channel,
LP5562Engine src) {
uint8_t reg_val = 0;
uint8_t bit_offset = 0;
@@ -107,7 +116,7 @@ void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel,
}
void lp5562_execute_program(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
LP5562Engine eng,
LP5562Channel ch,
uint16_t* program) {
@@ -155,7 +164,7 @@ void lp5562_execute_program(
furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x00, enable_reg, LP5562_I2C_TIMEOUT);
}
void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng) {
void lp5562_stop_program(const FuriHalI2cBusHandle* handle, LP5562Engine eng) {
if((eng < LP5562Engine1) || (eng > LP5562Engine3)) return;
uint8_t reg_val = 0;
uint8_t bit_offset = 0;
@@ -169,7 +178,7 @@ void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng) {
}
void lp5562_execute_ramp(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
LP5562Engine eng,
LP5562Channel ch,
uint8_t val_start,
@@ -213,7 +222,7 @@ void lp5562_execute_ramp(
}
void lp5562_execute_blink(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
LP5562Engine eng,
LP5562Channel ch,
uint16_t on_time,
+20 -11
View File
@@ -20,39 +20,48 @@ typedef enum {
} LP5562Engine;
/** Initialize Driver */
void lp5562_reset(FuriHalI2cBusHandle* handle);
void lp5562_reset(const FuriHalI2cBusHandle* handle);
/** Configure Driver */
void lp5562_configure(FuriHalI2cBusHandle* handle);
void lp5562_configure(const FuriHalI2cBusHandle* handle);
/** Enable Driver */
void lp5562_enable(FuriHalI2cBusHandle* handle);
void lp5562_enable(const FuriHalI2cBusHandle* handle);
/** Set channel current */
void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value);
void lp5562_set_channel_current(
const FuriHalI2cBusHandle* handle,
LP5562Channel channel,
uint8_t value);
/** Set channel PWM value */
void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value);
void lp5562_set_channel_value(
const FuriHalI2cBusHandle* handle,
LP5562Channel channel,
uint8_t value);
/** Get channel PWM value */
uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel);
uint8_t lp5562_get_channel_value(const FuriHalI2cBusHandle* handle, LP5562Channel channel);
/** Set channel source */
void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src);
void lp5562_set_channel_src(
const FuriHalI2cBusHandle* handle,
LP5562Channel channel,
LP5562Engine src);
/** Execute program sequence */
void lp5562_execute_program(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
LP5562Engine eng,
LP5562Channel ch,
uint16_t* program);
/** Stop program sequence */
void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng);
void lp5562_stop_program(const FuriHalI2cBusHandle* handle, LP5562Engine eng);
/** Execute ramp program sequence */
void lp5562_execute_ramp(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
LP5562Engine eng,
LP5562Channel ch,
uint8_t val_start,
@@ -61,7 +70,7 @@ void lp5562_execute_ramp(
/** Start blink program sequence */
void lp5562_execute_blink(
FuriHalI2cBusHandle* handle,
const FuriHalI2cBusHandle* handle,
LP5562Engine eng,
LP5562Channel ch,
uint16_t on_time,
+4 -4
View File
@@ -2,7 +2,7 @@
#include <furi.h>
void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask) {
void st25r3916_mask_irq(const FuriHalSpiBusHandle* handle, uint32_t mask) {
furi_assert(handle);
uint8_t irq_mask_regs[4] = {
@@ -14,7 +14,7 @@ void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask) {
st25r3916_write_burst_regs(handle, ST25R3916_REG_IRQ_MASK_MAIN, irq_mask_regs, 4);
}
uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle) {
uint32_t st25r3916_get_irq(const FuriHalSpiBusHandle* handle) {
furi_assert(handle);
uint8_t irq_regs[4] = {};
@@ -32,7 +32,7 @@ uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle) {
return irq;
}
void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits) {
void st25r3916_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits) {
furi_assert(handle);
furi_assert(buff);
@@ -45,7 +45,7 @@ void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size
}
bool st25r3916_read_fifo(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t* buff,
size_t buff_size,
size_t* buff_bits) {
+4 -4
View File
@@ -75,7 +75,7 @@ extern "C" {
* @param handle - pointer to FuriHalSpiBusHandle instance
* @param mask - mask of interrupts to be disabled
*/
void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask);
void st25r3916_mask_irq(const FuriHalSpiBusHandle* handle, uint32_t mask);
/** Get st25r3916 interrupts
*
@@ -83,7 +83,7 @@ void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask);
*
* @return received interrupts
*/
uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle);
uint32_t st25r3916_get_irq(const FuriHalSpiBusHandle* handle);
/** Write FIFO
*
@@ -91,7 +91,7 @@ uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle);
* @param buff - buffer to write to FIFO
* @param bits - number of bits to write
*/
void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits);
void st25r3916_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits);
/** Read FIFO
*
@@ -103,7 +103,7 @@ void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size
* @return true if read success, false otherwise
*/
bool st25r3916_read_fifo(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t* buff,
size_t buff_size,
size_t* buff_bits);
+29 -20
View File
@@ -28,18 +28,18 @@
(ST25R3916_CMD_LEN + \
ST25R3916_FIFO_DEPTH) /*!< ST25R3916 communication buffer: CMD + FIFO length */
static void st25r3916_reg_tx_byte(FuriHalSpiBusHandle* handle, uint8_t byte) {
static void st25r3916_reg_tx_byte(const FuriHalSpiBusHandle* handle, uint8_t byte) {
uint8_t val = byte;
furi_hal_spi_bus_tx(handle, &val, 1, 5);
}
void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) {
void st25r3916_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) {
furi_check(handle);
st25r3916_read_burst_regs(handle, reg, val, 1);
}
void st25r3916_read_burst_regs(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg_start,
uint8_t* values,
uint8_t length) {
@@ -59,14 +59,14 @@ void st25r3916_read_burst_regs(
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) {
void st25r3916_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) {
furi_check(handle);
uint8_t reg_val = val;
st25r3916_write_burst_regs(handle, reg, &reg_val, 1);
}
void st25r3916_write_burst_regs(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg_start,
const uint8_t* values,
uint8_t length) {
@@ -86,7 +86,10 @@ void st25r3916_write_burst_regs(
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length) {
void st25r3916_reg_write_fifo(
const FuriHalSpiBusHandle* handle,
const uint8_t* buff,
size_t length) {
furi_check(handle);
furi_check(buff);
furi_check(length);
@@ -98,7 +101,7 @@ void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff,
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) {
void st25r3916_reg_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) {
furi_check(handle);
furi_check(buff);
furi_check(length);
@@ -110,7 +113,10 @@ void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) {
void st25r3916_write_pta_mem(
const FuriHalSpiBusHandle* handle,
const uint8_t* values,
size_t length) {
furi_check(handle);
furi_check(values);
furi_check(length);
@@ -122,7 +128,7 @@ void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values,
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) {
void st25r3916_read_pta_mem(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) {
furi_check(handle);
furi_check(buff);
furi_check(length);
@@ -136,7 +142,10 @@ void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t l
memcpy(buff, tmp_buff + 1, length);
}
void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) {
void st25r3916_write_ptf_mem(
const FuriHalSpiBusHandle* handle,
const uint8_t* values,
size_t length) {
furi_check(handle);
furi_check(values);
@@ -146,7 +155,7 @@ void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values,
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) {
void st25r3916_write_pttsn_mem(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) {
furi_check(handle);
furi_check(buff);
@@ -156,7 +165,7 @@ void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd) {
void st25r3916_direct_cmd(const FuriHalSpiBusHandle* handle, uint8_t cmd) {
furi_check(handle);
furi_hal_gpio_write(handle->cs, false);
@@ -164,7 +173,7 @@ void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd) {
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) {
void st25r3916_read_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) {
furi_check(handle);
furi_hal_gpio_write(handle->cs, false);
@@ -174,7 +183,7 @@ void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t*
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) {
void st25r3916_write_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) {
furi_check(handle);
furi_hal_gpio_write(handle->cs, false);
@@ -184,7 +193,7 @@ void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t
furi_hal_gpio_write(handle->cs, true);
}
void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask) {
void st25r3916_clear_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask) {
furi_check(handle);
uint8_t reg_val = 0;
@@ -195,7 +204,7 @@ void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t
}
}
void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask) {
void st25r3916_set_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask) {
furi_check(handle);
uint8_t reg_val = 0;
@@ -207,7 +216,7 @@ void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t se
}
void st25r3916_change_reg_bits(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg,
uint8_t mask,
uint8_t value) {
@@ -217,7 +226,7 @@ void st25r3916_change_reg_bits(
}
void st25r3916_modify_reg(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg,
uint8_t clr_mask,
uint8_t set_mask) {
@@ -233,7 +242,7 @@ void st25r3916_modify_reg(
}
void st25r3916_change_test_reg_bits(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg,
uint8_t mask,
uint8_t value) {
@@ -248,7 +257,7 @@ void st25r3916_change_test_reg_bits(
}
}
bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val) {
bool st25r3916_check_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val) {
furi_check(handle);
uint8_t reg_val = 0;
+28 -19
View File
@@ -967,7 +967,7 @@ extern "C" {
* @param reg - register address
* @param val - pointer to the variable to store the read value
*/
void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val);
void st25r3916_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val);
/** Read multiple registers
*
@@ -977,7 +977,7 @@ void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val);
* @param length - number of registers to read
*/
void st25r3916_read_burst_regs(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg_start,
uint8_t* values,
uint8_t length);
@@ -988,7 +988,7 @@ void st25r3916_read_burst_regs(
* @param reg - register address
* @param val - value to write
*/
void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val);
void st25r3916_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val);
/** Write multiple registers
*
@@ -998,7 +998,7 @@ void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val);
* @param length - number of registers to write
*/
void st25r3916_write_burst_regs(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg_start,
const uint8_t* values,
uint8_t length);
@@ -1009,7 +1009,10 @@ void st25r3916_write_burst_regs(
* @param buff - buffer to write to FIFO
* @param length - number of bytes to write
*/
void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length);
void st25r3916_reg_write_fifo(
const FuriHalSpiBusHandle* handle,
const uint8_t* buff,
size_t length);
/** Read fifo register
*
@@ -1017,7 +1020,7 @@ void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff,
* @param buff - buffer to store the read values
* @param length - number of bytes to read
*/
void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length);
void st25r3916_reg_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length);
/** Write PTA memory register
*
@@ -1025,7 +1028,10 @@ void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t
* @param values - pointer to buffer to write
* @param length - number of bytes to write
*/
void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length);
void st25r3916_write_pta_mem(
const FuriHalSpiBusHandle* handle,
const uint8_t* values,
size_t length);
/** Read PTA memory register
*
@@ -1033,7 +1039,7 @@ void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values,
* @param values - buffer to store the read values
* @param length - number of bytes to read
*/
void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length);
void st25r3916_read_pta_mem(const FuriHalSpiBusHandle* handle, uint8_t* values, size_t length);
/** Write PTF memory register
*
@@ -1041,7 +1047,10 @@ void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t
* @param values - pointer to buffer to write
* @param length - number of bytes to write
*/
void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length);
void st25r3916_write_ptf_mem(
const FuriHalSpiBusHandle* handle,
const uint8_t* values,
size_t length);
/** Read PTTSN memory register
*
@@ -1049,21 +1058,21 @@ void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values,
* @param values - pointer to buffer to write
* @param length - number of bytes to write
*/
void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length);
void st25r3916_write_pttsn_mem(const FuriHalSpiBusHandle* handle, uint8_t* values, size_t length);
/** Send Direct command
*
* @param handle - pointer to FuriHalSpiBusHandle instance
* @param cmd - direct command
*/
void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd);
void st25r3916_direct_cmd(const FuriHalSpiBusHandle* handle, uint8_t cmd);
/** Read test register
* @param handle - pointer to FuriHalSpiBusHandle instance
* @param reg - register address
* @param val - pointer to the variable to store the read value
*/
void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val);
void st25r3916_read_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val);
/** Write test register
*
@@ -1071,7 +1080,7 @@ void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t*
* @param reg - register address
* @param val - value to write
*/
void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val);
void st25r3916_write_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val);
/** Clear register bits
*
@@ -1079,7 +1088,7 @@ void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t
* @param reg - register address
* @param clr_mask - bit mask to clear
*/
void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask);
void st25r3916_clear_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask);
/** Set register bits
*
@@ -1087,7 +1096,7 @@ void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t
* @param reg - register address
* @param set_mask - bit mask to set
*/
void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask);
void st25r3916_set_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask);
/** Change register bits
*
@@ -1097,7 +1106,7 @@ void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t se
* @param value - new register value to write
*/
void st25r3916_change_reg_bits(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg,
uint8_t mask,
uint8_t value);
@@ -1110,7 +1119,7 @@ void st25r3916_change_reg_bits(
* @param set_mask - bit mask to set
*/
void st25r3916_modify_reg(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg,
uint8_t clr_mask,
uint8_t set_mask);
@@ -1123,7 +1132,7 @@ void st25r3916_modify_reg(
* @param value - new register value to write
*/
void st25r3916_change_test_reg_bits(
FuriHalSpiBusHandle* handle,
const FuriHalSpiBusHandle* handle,
uint8_t reg,
uint8_t mask,
uint8_t value);
@@ -1137,7 +1146,7 @@ void st25r3916_change_test_reg_bits(
*
* @return true if register value matches the expected value, false otherwise
*/
bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val);
bool st25r3916_check_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val);
#ifdef __cplusplus
}
+16
View File
@@ -8,6 +8,11 @@
#include "flipper_format_stream.h"
#include "flipper_format_stream_i.h"
// permits direct casting between `FlipperFormatOffset` and `StreamOffset`
static_assert((size_t)FlipperFormatOffsetFromCurrent == (size_t)StreamOffsetFromCurrent);
static_assert((size_t)FlipperFormatOffsetFromStart == (size_t)StreamOffsetFromStart);
static_assert((size_t)FlipperFormatOffsetFromEnd == (size_t)StreamOffsetFromEnd);
/********************************** Private **********************************/
struct FlipperFormat {
Stream* stream;
@@ -127,6 +132,17 @@ bool flipper_format_rewind(FlipperFormat* flipper_format) {
return stream_rewind(flipper_format->stream);
}
size_t flipper_format_tell(FlipperFormat* flipper_format) {
furi_check(flipper_format);
return stream_tell(flipper_format->stream);
}
bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor) {
furi_check(flipper_format);
// direct usage of `anchor` made valid by `static_assert`s at the top of this file
return stream_seek(flipper_format->stream, offset, (StreamOffset)anchor);
}
bool flipper_format_seek_to_end(FlipperFormat* flipper_format) {
furi_check(flipper_format);
return stream_seek(flipper_format->stream, 0, StreamOffsetFromEnd);
+24
View File
@@ -94,6 +94,12 @@ extern "C" {
typedef struct FlipperFormat FlipperFormat;
typedef enum {
FlipperFormatOffsetFromCurrent,
FlipperFormatOffsetFromStart,
FlipperFormatOffsetFromEnd,
} FlipperFormatOffset;
/** Allocate FlipperFormat as string.
*
* @return FlipperFormat* pointer to a FlipperFormat instance
@@ -216,6 +222,24 @@ void flipper_format_set_strict_mode(FlipperFormat* flipper_format, bool strict_m
*/
bool flipper_format_rewind(FlipperFormat* flipper_format);
/** Get the RW pointer position
*
* @param flipper_format Pointer to a FlipperFormat instance
*
* @return RW pointer position
*/
size_t flipper_format_tell(FlipperFormat* flipper_format);
/** Set the RW pointer position to an arbitrary value
*
* @param flipper_format Pointer to a FlipperFormat instance
* @param offset Offset relative to the anchor point
* @param anchor Anchor point (e.g. start of file)
*
* @return True on success
*/
bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor);
/** Move the RW pointer at the end. Can be useful if you want to add some data
* after reading.
*
+14 -5
View File
@@ -1,9 +1,10 @@
#include "ibutton_worker_i.h"
#include <core/check.h>
#include <core/record.h>
#include <furi_hal_rfid.h>
#include <furi_hal_power.h>
#include <power/power_service/power.h>
#include "ibutton_protocols.h"
@@ -75,7 +76,9 @@ void ibutton_worker_mode_idle_stop(iButtonWorker* worker) {
void ibutton_worker_mode_read_start(iButtonWorker* worker) {
UNUSED(worker);
furi_hal_power_enable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, true);
furi_record_close(RECORD_POWER);
}
void ibutton_worker_mode_read_tick(iButtonWorker* worker) {
@@ -90,7 +93,9 @@ void ibutton_worker_mode_read_tick(iButtonWorker* worker) {
void ibutton_worker_mode_read_stop(iButtonWorker* worker) {
UNUSED(worker);
furi_hal_power_disable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, false);
furi_record_close(RECORD_POWER);
}
/*********************** EMULATE ***********************/
@@ -120,7 +125,9 @@ void ibutton_worker_mode_emulate_stop(iButtonWorker* worker) {
void ibutton_worker_mode_write_common_start(iButtonWorker* worker) { //-V524
UNUSED(worker);
furi_hal_power_enable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, true);
furi_record_close(RECORD_POWER);
}
void ibutton_worker_mode_write_id_tick(iButtonWorker* worker) {
@@ -149,5 +156,7 @@ void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker) {
void ibutton_worker_mode_write_common_stop(iButtonWorker* worker) { //-V524
UNUSED(worker);
furi_hal_power_disable_otg();
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, false);
furi_record_close(RECORD_POWER);
}
@@ -6,7 +6,7 @@
#include "protocol_ds1971.h"
#include "protocol_ds_generic.h"
const iButtonProtocolDallasBase* ibutton_protocols_dallas[] = {
const iButtonProtocolDallasBase* const ibutton_protocols_dallas[] = {
[iButtonProtocolDS1990] = &ibutton_protocol_ds1990,
[iButtonProtocolDS1992] = &ibutton_protocol_ds1992,
[iButtonProtocolDS1996] = &ibutton_protocol_ds1996,
@@ -14,4 +14,4 @@ typedef enum {
iButtonProtocolDSMax,
} iButtonProtocolDallas;
extern const iButtonProtocolDallasBase* ibutton_protocols_dallas[];
extern const iButtonProtocolDallasBase* const ibutton_protocols_dallas[];
@@ -3,7 +3,7 @@
#include "protocol_cyfral.h"
#include "protocol_metakom.h"
const ProtocolBase* ibutton_protocols_misc[] = {
const ProtocolBase* const ibutton_protocols_misc[] = {
[iButtonProtocolMiscCyfral] = &ibutton_protocol_misc_cyfral,
[iButtonProtocolMiscMetakom] = &ibutton_protocol_misc_metakom,
/* Add new misc protocols here */
@@ -8,4 +8,4 @@ typedef enum {
iButtonProtocolMiscMax,
} iButtonProtocolMisc;
extern const ProtocolBase* ibutton_protocols_misc[];
extern const ProtocolBase* const ibutton_protocols_misc[];
+1 -1
View File
@@ -3,7 +3,7 @@
#include "dallas/protocol_group_dallas.h"
#include "misc/protocol_group_misc.h"
const iButtonProtocolGroupBase* ibutton_protocol_groups[] = {
const iButtonProtocolGroupBase* const ibutton_protocol_groups[] = {
[iButtonProtocolGroupDallas] = &ibutton_protocol_group_dallas,
[iButtonProtocolGroupMisc] = &ibutton_protocol_group_misc,
};
+1 -1
View File
@@ -8,4 +8,4 @@ typedef enum {
iButtonProtocolGroupMax
} iButtonProtocolGroup;
extern const iButtonProtocolGroupBase* ibutton_protocol_groups[];
extern const iButtonProtocolGroupBase* const ibutton_protocol_groups[];
+31
View File
@@ -0,0 +1,31 @@
Import("env")
wrapped_fn_list = [
"strtof",
"strtod",
]
for wrapped_fn in wrapped_fn_list:
env.Append(
LINKFLAGS=[
"-Wl,--wrap," + wrapped_fn,
]
)
env.Append(
SDK_HEADERS=[
File("wrappers.h"),
],
LINT_SOURCES=[
Dir("."),
],
)
libenv = env.Clone(FW_LIB_NAME="ieee754_parse_wrap")
libenv.ApplyLibFlags()
sources = libenv.GlobRecursive("*.c*", ".")
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
libenv.Install("${LIB_DIST_DIR}", lib)
Return("lib")
+14
View File
@@ -0,0 +1,14 @@
#include "wrappers.h"
// Based on the disassembly, providing NULL as `locale` is fine.
// The default `strtof` and `strtod` provided in the same libc_nano also just
// call these functions, but with an actual locale structure which was taking up
// lots of .data space (364 bytes).
float __wrap_strtof(const char* in, char** tail) {
return strtof_l(in, tail, NULL);
}
double __wrap_strtod(const char* in, char** tail) {
return strtod_l(in, tail, NULL);
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
float __wrap_strtof(const char* in, char** tail);
double __wrap_strtod(const char* in, char** tail);
#ifdef __cplusplus
}
#endif
+67 -45
View File
@@ -499,9 +499,6 @@ static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) {
static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) {
LFRFIDProtocol protocol = worker->protocol;
LFRFIDWriteRequest* request = malloc(sizeof(LFRFIDWriteRequest));
request->write_type = LFRFIDWriteTypeT5577;
bool can_be_written = protocol_dict_get_write_data(worker->protocols, protocol, request);
uint32_t write_start_time = furi_get_tick();
bool too_long = false;
@@ -510,63 +507,88 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) {
size_t data_size = protocol_dict_get_data_size(worker->protocols, protocol);
uint8_t* verify_data = malloc(data_size);
uint8_t* read_data = malloc(data_size);
protocol_dict_get_data(worker->protocols, protocol, verify_data, data_size);
if(can_be_written) {
while(!lfrfid_worker_check_for_stop(worker)) {
FURI_LOG_D(TAG, "Data write");
t5577_write(&request->t5577);
while(!lfrfid_worker_check_for_stop(worker)) {
FURI_LOG_D(TAG, "Data write");
uint16_t skips = 0;
for(size_t i = 0; i < LFRFIDWriteTypeMax; i++) {
memset(request, 0, sizeof(LFRFIDWriteRequest));
LFRFIDWriteType write_type = i;
request->write_type = write_type;
ProtocolId read_result = PROTOCOL_NO;
LFRFIDWorkerReadState state = lfrfid_worker_read_internal(
worker,
protocol_dict_get_features(worker->protocols, protocol),
LFRFID_WORKER_WRITE_VERIFY_TIME_MS,
&read_result);
protocol_dict_set_data(worker->protocols, protocol, verify_data, data_size);
if(state == LFRFIDWorkerReadOK) {
bool read_success = false;
bool can_be_written =
protocol_dict_get_write_data(worker->protocols, protocol, request);
if(read_result == protocol) {
protocol_dict_get_data(worker->protocols, protocol, read_data, data_size);
if(memcmp(read_data, verify_data, data_size) == 0) {
read_success = true;
}
}
if(read_success) {
if(!can_be_written) {
skips++;
if(skips == LFRFIDWriteTypeMax) {
if(worker->write_cb) {
worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx);
worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx);
}
break;
} else {
unsuccessful_reads++;
}
continue;
}
if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) {
if(worker->write_cb) {
worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx);
}
memset(read_data, 0, data_size);
if(request->write_type == LFRFIDWriteTypeT5577) {
t5577_write(&request->t5577);
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
em4305_write(&request->em4305);
} else {
furi_crash("Unknown write type");
}
}
ProtocolId read_result = PROTOCOL_NO;
LFRFIDWorkerReadState state = lfrfid_worker_read_internal(
worker,
protocol_dict_get_features(worker->protocols, protocol),
LFRFID_WORKER_WRITE_VERIFY_TIME_MS,
&read_result);
if(state == LFRFIDWorkerReadOK) {
bool read_success = false;
if(read_result == protocol) {
protocol_dict_get_data(worker->protocols, protocol, read_data, data_size);
if(memcmp(read_data, verify_data, data_size) == 0) {
read_success = true;
}
}
if(read_success) {
if(worker->write_cb) {
worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx);
}
break;
} else {
unsuccessful_reads++;
if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) {
if(worker->write_cb) {
worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx);
}
}
} else if(state == LFRFIDWorkerReadExit) {
break;
}
} else if(state == LFRFIDWorkerReadExit) {
break;
}
if(!too_long &&
(furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) {
too_long = true;
if(worker->write_cb) {
worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx);
}
if(!too_long &&
(furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) {
too_long = true;
if(worker->write_cb) {
worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx);
}
}
lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS);
}
} else {
if(worker->write_cb) {
worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx);
}
lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS);
}
free(request);
+3 -1
View File
@@ -20,8 +20,9 @@
#include "protocol_nexwatch.h"
#include "protocol_securakey.h"
#include "protocol_gproxii.h"
#include "protocol_noralsy.h"
const ProtocolBase* lfrfid_protocols[] = {
const ProtocolBase* const lfrfid_protocols[] = {
[LFRFIDProtocolEM4100] = &protocol_em4100,
[LFRFIDProtocolEM410032] = &protocol_em4100_32,
[LFRFIDProtocolEM410016] = &protocol_em4100_16,
@@ -45,4 +46,5 @@ const ProtocolBase* lfrfid_protocols[] = {
[LFRFIDProtocolNexwatch] = &protocol_nexwatch,
[LFRFIDProtocolSecurakey] = &protocol_securakey,
[LFRFIDProtocolGProxII] = &protocol_gproxii,
[LFRFIDProtocolNoralsy] = &protocol_noralsy,
};
+8 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <toolbox/protocols/protocol.h>
#include "../tools/t5577.h"
#include "../tools/em4305.h"
typedef enum {
LFRFIDFeatureASK = 1 << 0, /** ASK Demodulation */
@@ -31,18 +32,24 @@ typedef enum {
LFRFIDProtocolNexwatch,
LFRFIDProtocolSecurakey,
LFRFIDProtocolGProxII,
LFRFIDProtocolNoralsy,
LFRFIDProtocolMax,
} LFRFIDProtocol;
extern const ProtocolBase* lfrfid_protocols[];
extern const ProtocolBase* const lfrfid_protocols[];
typedef enum {
LFRFIDWriteTypeT5577,
LFRFIDWriteTypeEM4305,
LFRFIDWriteTypeMax,
} LFRFIDWriteType;
typedef struct {
LFRFIDWriteType write_type;
union {
LFRFIDT5577 t5577;
LFRFIDEM4305 em4305;
};
} LFRFIDWriteRequest;
+18
View File
@@ -407,6 +407,24 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) {
request->t5577.blocks_to_write = 5;
result = true;
}
if(request->write_type == LFRFIDWriteTypeEM4305) {
request->em4305.word[4] =
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(64) | (8 << EM4x05_MAXBLOCK_SHIFT));
uint64_t encoded_data_reversed = 0;
uint64_t encoded_epilogue_reversed = 0;
for(uint8_t i = 0; i < 64; i++) {
encoded_data_reversed = (encoded_data_reversed << 1) |
((protocol->encoded_base_data >> i) & 1);
encoded_epilogue_reversed = (encoded_epilogue_reversed << 1) |
((protocol->encoded_epilogue >> i) & 1);
}
request->em4305.word[5] = encoded_data_reversed & 0xFFFFFFFF;
request->em4305.word[6] = encoded_data_reversed >> 32;
request->em4305.word[7] = encoded_epilogue_reversed & 0xFFFFFFFF;
request->em4305.word[8] = encoded_epilogue_reversed >> 32;
request->em4305.mask = 0x01F0;
result = true;
}
return result;
}
+26
View File
@@ -69,6 +69,19 @@ uint32_t protocol_em4100_get_t5577_bitrate(ProtocolEM4100* proto) {
}
}
uint32_t protocol_em4100_get_em4305_bitrate(ProtocolEM4100* proto) {
switch(proto->clock_per_bit) {
case 64:
return EM4x05_SET_BITRATE(64);
case 32:
return EM4x05_SET_BITRATE(32);
case 16:
return EM4x05_SET_BITRATE(16);
default:
return EM4x05_SET_BITRATE(64);
}
}
uint16_t protocol_em4100_get_short_time_low(ProtocolEM4100* proto) {
return EM_READ_SHORT_TIME_BASE / protocol_em4100_get_time_divisor(proto) -
EM_READ_JITTER_TIME_BASE / protocol_em4100_get_time_divisor(proto);
@@ -339,6 +352,19 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) {
request->t5577.block[2] = protocol->encoded_data;
request->t5577.blocks_to_write = 3;
result = true;
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
request->em4305.word[4] =
(EM4x05_MODULATION_MANCHESTER | protocol_em4100_get_em4305_bitrate(protocol) |
(6 << EM4x05_MAXBLOCK_SHIFT));
uint64_t encoded_data_reversed = 0;
for(uint8_t i = 0; i < 64; i++) {
encoded_data_reversed = (encoded_data_reversed << 1) |
((protocol->encoded_data >> i) & 1);
}
request->em4305.word[5] = encoded_data_reversed;
request->em4305.word[6] = encoded_data_reversed >> 32;
request->em4305.mask = 0x70;
result = true;
}
return result;
}
+14
View File
@@ -264,6 +264,20 @@ bool protocol_gallagher_write_data(ProtocolGallagher* protocol, void* data) {
request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32);
request->t5577.blocks_to_write = 4;
result = true;
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
request->em4305.word[4] =
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(32) | (7 << EM4x05_MAXBLOCK_SHIFT));
uint32_t encoded_data_reversed[3] = {0};
for(uint8_t i = 0; i < (32 * 3); i++) {
encoded_data_reversed[i / 32] =
(encoded_data_reversed[i / 32] << 1) |
(bit_lib_get_bit(protocol->encoded_data, ((32 * 3) - i)) & 1);
}
request->em4305.word[5] = encoded_data_reversed[2];
request->em4305.word[6] = encoded_data_reversed[1];
request->em4305.word[7] = encoded_data_reversed[0];
request->em4305.mask = 0xF0;
result = true;
}
return result;
}
+1 -1
View File
@@ -21,7 +21,7 @@ typedef struct {
uint8_t chk;
} ProtocolNexwatchMagic;
ProtocolNexwatchMagic magic_items[] = {
static ProtocolNexwatchMagic magic_items[] = {
{0xBE, "Quadrakey", 0},
{0x88, "Nexkey", 0},
{0x86, "Honeywell", 0}};
+220
View File
@@ -0,0 +1,220 @@
#include <furi.h>
#include <toolbox/protocols/protocol.h>
#include <toolbox/manchester_decoder.h>
#include <bit_lib/bit_lib.h>
#include "lfrfid_protocols.h"
#define NORALSY_CLOCK_PER_BIT (32)
#define NORALSY_ENCODED_BIT_SIZE (96)
#define NORALSY_ENCODED_BYTE_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8)
#define NORALSY_PREAMBLE_BIT_SIZE (32)
#define NORALSY_PREAMBLE_BYTE_SIZE ((NORALSY_PREAMBLE_BIT_SIZE) / 8)
#define NORALSY_ENCODED_BYTE_FULL_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8)
#define NORALSY_DECODED_DATA_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8)
#define NORALSY_READ_SHORT_TIME (128)
#define NORALSY_READ_LONG_TIME (256)
#define NORALSY_READ_JITTER_TIME (60)
#define NORALSY_READ_SHORT_TIME_LOW (NORALSY_READ_SHORT_TIME - NORALSY_READ_JITTER_TIME)
#define NORALSY_READ_SHORT_TIME_HIGH (NORALSY_READ_SHORT_TIME + NORALSY_READ_JITTER_TIME)
#define NORALSY_READ_LONG_TIME_LOW (NORALSY_READ_LONG_TIME - NORALSY_READ_JITTER_TIME)
#define NORALSY_READ_LONG_TIME_HIGH (NORALSY_READ_LONG_TIME + NORALSY_READ_JITTER_TIME)
#define TAG "NORALSY"
typedef struct {
uint8_t data[NORALSY_ENCODED_BYTE_SIZE];
uint8_t encoded_data[NORALSY_ENCODED_BYTE_SIZE];
uint8_t encoded_data_index;
bool encoded_polarity;
ManchesterState decoder_manchester_state;
} ProtocolNoralsy;
ProtocolNoralsy* protocol_noralsy_alloc(void) {
ProtocolNoralsy* protocol = malloc(sizeof(ProtocolNoralsy));
return (void*)protocol;
}
void protocol_noralsy_free(ProtocolNoralsy* protocol) {
free(protocol);
}
static uint8_t noralsy_chksum(uint8_t* bits, uint8_t len) {
uint8_t sum = 0;
for(uint8_t i = 0; i < len; i += 4)
sum ^= bit_lib_get_bits(bits, i, 4);
return sum & 0x0F;
}
uint8_t* protocol_noralsy_get_data(ProtocolNoralsy* protocol) {
return protocol->data;
}
static void protocol_noralsy_decode(ProtocolNoralsy* protocol) {
bit_lib_copy_bits(protocol->data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->encoded_data, 0);
}
static bool protocol_noralsy_can_be_decoded(ProtocolNoralsy* protocol) {
// check 12 bits preamble
// If necessary, use 0xBB0214FF for 32 bit preamble check
// However, it is not confirmed the 13-16 bit are static.
if(bit_lib_get_bits_16(protocol->encoded_data, 0, 12) != 0b101110110000) return false;
uint8_t calc1 = noralsy_chksum(&protocol->encoded_data[4], 40);
uint8_t calc2 = noralsy_chksum(&protocol->encoded_data[0], 76);
uint8_t chk1 = bit_lib_get_bits(protocol->encoded_data, 72, 4);
uint8_t chk2 = bit_lib_get_bits(protocol->encoded_data, 76, 4);
if(calc1 != chk1 || calc2 != chk2) return false;
return true;
}
void protocol_noralsy_decoder_start(ProtocolNoralsy* protocol) {
memset(protocol->encoded_data, 0, NORALSY_ENCODED_BYTE_FULL_SIZE);
manchester_advance(
protocol->decoder_manchester_state,
ManchesterEventReset,
&protocol->decoder_manchester_state,
NULL);
}
bool protocol_noralsy_decoder_feed(ProtocolNoralsy* protocol, bool level, uint32_t duration) {
bool result = false;
ManchesterEvent event = ManchesterEventReset;
if(duration > NORALSY_READ_SHORT_TIME_LOW && duration < NORALSY_READ_SHORT_TIME_HIGH) {
if(!level) {
event = ManchesterEventShortHigh;
} else {
event = ManchesterEventShortLow;
}
} else if(duration > NORALSY_READ_LONG_TIME_LOW && duration < NORALSY_READ_LONG_TIME_HIGH) {
if(!level) {
event = ManchesterEventLongHigh;
} else {
event = ManchesterEventLongLow;
}
}
if(event != ManchesterEventReset) {
bool data;
bool data_ok = manchester_advance(
protocol->decoder_manchester_state, event, &protocol->decoder_manchester_state, &data);
if(data_ok) {
bit_lib_push_bit(protocol->encoded_data, NORALSY_ENCODED_BYTE_FULL_SIZE, data);
if(protocol_noralsy_can_be_decoded(protocol)) {
protocol_noralsy_decode(protocol);
result = true;
}
}
}
return result;
}
bool protocol_noralsy_encoder_start(ProtocolNoralsy* protocol) {
bit_lib_copy_bits(protocol->encoded_data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->data, 0);
return true;
}
LevelDuration protocol_noralsy_encoder_yield(ProtocolNoralsy* protocol) {
bool level = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_data_index);
uint32_t duration = NORALSY_CLOCK_PER_BIT / 2;
if(protocol->encoded_polarity) {
protocol->encoded_polarity = false;
} else {
level = !level;
protocol->encoded_polarity = true;
bit_lib_increment_index(protocol->encoded_data_index, NORALSY_ENCODED_BIT_SIZE);
}
return level_duration_make(level, duration);
}
bool protocol_noralsy_write_data(ProtocolNoralsy* protocol, void* data) {
LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data;
bool result = false;
// Correct protocol data by redecoding
protocol_noralsy_encoder_start(protocol);
protocol_noralsy_decode(protocol);
protocol_noralsy_encoder_start(protocol);
if(request->write_type == LFRFIDWriteTypeT5577) {
request->t5577.block[0] =
(LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_32 |
(3 << LFRFID_T5577_MAXBLOCK_SHIFT) | LFRFID_T5577_ST_TERMINATOR);
// In fact, base on the current two dump samples from Iceman server,
// Noralsy are usually T5577s with config = 0x00088C6A
// But the `C` and `A` are not explainable by the ATA5577C datasheet
// and they don't affect reading whatsoever.
// So we are mimicing Proxmark's solution here. Leave those nibbles as zero.
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;
}
static void protocol_noralsy_render_data_internal(ProtocolNoralsy* protocol, FuriString* result) {
UNUSED(protocol);
uint32_t raw2 = bit_lib_get_bits_32(protocol->data, 32, 32);
uint32_t raw3 = bit_lib_get_bits_32(protocol->data, 64, 32);
uint32_t cardid = ((raw2 & 0xFFF00000) >> 20) << 16;
cardid |= (raw2 & 0xFF) << 8;
cardid |= ((raw3 & 0xFF000000) >> 24);
uint8_t year = (raw2 & 0x000ff000) >> 12;
bool tag_is_gen_z = (year > 0x60);
furi_string_printf(
result,
"Card ID: %07lx\n"
"Year: %s%02x",
cardid,
tag_is_gen_z ? "19" : "20",
year);
}
void protocol_noralsy_render_data(ProtocolNoralsy* protocol, FuriString* result) {
protocol_noralsy_render_data_internal(protocol, result);
}
void protocol_noralsy_render_brief_data(ProtocolNoralsy* protocol, FuriString* result) {
protocol_noralsy_render_data_internal(protocol, result);
}
const ProtocolBase protocol_noralsy = {
.name = "Noralsy",
.manufacturer = "Noralsy",
.data_size = NORALSY_DECODED_DATA_SIZE,
.features = LFRFIDFeatureASK,
.validate_count = 3,
.alloc = (ProtocolAlloc)protocol_noralsy_alloc,
.free = (ProtocolFree)protocol_noralsy_free,
.get_data = (ProtocolGetData)protocol_noralsy_get_data,
.decoder =
{
.start = (ProtocolDecoderStart)protocol_noralsy_decoder_start,
.feed = (ProtocolDecoderFeed)protocol_noralsy_decoder_feed,
},
.encoder =
{
.start = (ProtocolEncoderStart)protocol_noralsy_encoder_start,
.yield = (ProtocolEncoderYield)protocol_noralsy_encoder_yield,
},
.render_data = (ProtocolRenderData)protocol_noralsy_render_data,
.render_brief_data = (ProtocolRenderData)protocol_noralsy_render_brief_data,
.write_data = (ProtocolWriteData)protocol_noralsy_write_data,
};
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#include <toolbox/protocols/protocol.h>
extern const ProtocolBase protocol_noralsy;
+13 -8
View File
@@ -61,17 +61,22 @@ uint8_t* protocol_securakey_get_data(ProtocolSecurakey* protocol) {
static bool protocol_securakey_can_be_decoded(ProtocolSecurakey* protocol) {
// check 19 bits preamble + format flag
if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111000000000) {
protocol->bit_format = 0;
return true;
if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 54, BitLibParityAlways0, 9)) {
protocol->bit_format = 0;
return true;
}
} else if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111001011010) {
protocol->bit_format = 26;
return true;
if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 90, BitLibParityAlways0, 9)) {
protocol->bit_format = 26;
return true;
}
} else if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111001100000) {
protocol->bit_format = 32;
return true;
} else {
return false;
if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 90, BitLibParityAlways0, 9)) {
protocol->bit_format = 32;
return true;
}
}
return false;
}
static void protocol_securakey_decode(ProtocolSecurakey* protocol) {
+13
View File
@@ -171,6 +171,19 @@ bool protocol_viking_write_data(ProtocolViking* protocol, void* data) {
request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32);
request->t5577.blocks_to_write = 3;
result = true;
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
request->em4305.word[4] =
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(32) | (6 << EM4x05_MAXBLOCK_SHIFT));
uint32_t encoded_data_reversed[2] = {0};
for(uint8_t i = 0; i < 64; i++) {
encoded_data_reversed[i / 32] =
(encoded_data_reversed[i / 32] << 1) |
(bit_lib_get_bit(protocol->encoded_data, (63 - i)) & 1);
}
request->em4305.word[5] = encoded_data_reversed[1];
request->em4305.word[6] = encoded_data_reversed[0];
request->em4305.mask = 0x70;
result = true;
}
return result;
}
+152
View File
@@ -0,0 +1,152 @@
#include "em4305.h"
#include <furi.h>
#include <furi_hal_rfid.h>
#define TAG "EM4305"
#define EM4305_TIMING_1 (32)
#define EM4305_TIMING_0_OFF (23)
#define EM4305_TIMING_0_ON (18)
#define EM4305_FIELD_STOP_OFF_CYCLES (55)
#define EM4305_FIELD_STOP_ON_CYCLES (18)
#define EM4305_TIMING_POWER_CHECK (1480)
#define EM4305_TIMING_EEPROM_WRITE (9340)
static bool em4305_line_parity(uint8_t data) {
uint8_t parity = 0;
for(uint8_t i = 0; i < 8; i++) {
parity ^= (data >> i) & 1;
}
return parity;
}
static uint64_t em4305_prepare_data(uint32_t data) {
uint8_t i, j;
uint64_t data_with_parity = 0;
// 4 lines of 8 bits of data
// line even parity at bits 8 17 26 35
// column even parity at bits 36-43
// bit 44 is always 0
// final table is 5 lines of 9 bits
// line parity
for(i = 0; i < 4; i++) {
for(j = 0; j < 8; j++) {
data_with_parity = (data_with_parity << 1) | ((data >> (i * 8 + j)) & 1);
}
data_with_parity = (data_with_parity << 1) | (uint64_t)em4305_line_parity(data >> (i * 8));
}
// column parity
for(i = 0; i < 8; i++) {
uint8_t column_parity = 0;
for(j = 0; j < 4; j++) {
column_parity ^= (data >> (j * 8 + i)) & 1;
}
data_with_parity = (data_with_parity << 1) | column_parity;
}
// bit 44
data_with_parity = (data_with_parity << 1) | 0;
return data_with_parity;
}
static void em4305_start(void) {
furi_hal_rfid_tim_read_start(125000, 0.5);
// do not ground the antenna
furi_hal_rfid_pin_pull_release();
}
static void em4305_stop(void) {
furi_hal_rfid_tim_read_stop();
furi_hal_rfid_pins_reset();
}
static void em4305_write_bit(bool value) {
if(value) {
furi_delay_us(EM4305_TIMING_1 * 8);
} else {
furi_hal_rfid_tim_read_pause();
furi_delay_us(EM4305_TIMING_0_OFF * 8);
furi_hal_rfid_tim_read_continue();
furi_delay_us(EM4305_TIMING_0_ON * 8);
}
}
static void em4305_write_opcode(uint8_t value) {
// 3 bit opcode
for(uint8_t i = 0; i < 3; i++) {
em4305_write_bit((value >> i) & 1);
}
// parity
bool parity = 0;
for(uint8_t i = 0; i < 3; i++) {
parity ^= (value >> i) & 1;
}
em4305_write_bit(parity);
}
static void em4305_field_stop() {
furi_hal_rfid_tim_read_pause();
furi_delay_us(EM4305_FIELD_STOP_OFF_CYCLES * 8);
furi_hal_rfid_tim_read_continue();
furi_delay_us(EM4305_FIELD_STOP_ON_CYCLES * 8);
}
static void em4305_write_word(uint8_t address, uint32_t data) {
// parity
uint64_t data_with_parity = em4305_prepare_data(data);
// power up the tag
furi_delay_us(8000);
// field stop
em4305_field_stop();
// start bit
em4305_write_bit(0);
// opcode
em4305_write_opcode(EM4x05_OPCODE_WRITE);
// address
bool address_parity = 0;
for(uint8_t i = 0; i < 4; i++) {
em4305_write_bit((address >> (i)) & 1);
address_parity ^= (address >> (i)) & 1;
}
em4305_write_bit(0);
em4305_write_bit(0);
em4305_write_bit(address_parity);
// data
for(uint8_t i = 0; i < 45; i++) {
em4305_write_bit((data_with_parity >> (44 - i)) & 1);
}
// wait for power check and eeprom write
furi_delay_us(EM4305_TIMING_POWER_CHECK);
furi_delay_us(EM4305_TIMING_EEPROM_WRITE);
}
void em4305_write(LFRFIDEM4305* data) {
furi_check(data);
em4305_start();
FURI_CRITICAL_ENTER();
for(uint8_t i = 0; i < EM4x05_WORD_COUNT; i++) {
if(data->mask & (1 << i)) {
em4305_write_word(i, data->word[i]);
}
}
FURI_CRITICAL_EXIT();
em4305_stop();
}
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
// EM4305/4205 chip config definitions, thanks proxmark3!
#define EM4x05_GET_BITRATE(x) ((((x) & 0x3F) * 2) + 2)
// Note: only data rates 8, 16, 32, 40(*) and 64 are supported. (*) only with EM4305 330pF
#define EM4x05_SET_BITRATE(x) (((x) - 2) / 2)
#define EM4x05_MODULATION_NRZ (0x00000000)
#define EM4x05_MODULATION_MANCHESTER (0x00000040)
#define EM4x05_MODULATION_BIPHASE (0x00000080)
#define EM4x05_MODULATION_MILLER (0x000000C0) // not supported by all 4x05/4x69 chips
#define EM4x05_MODULATION_PSK1 (0x00000100) // not supported by all 4x05/4x69 chips
#define EM4x05_MODULATION_PSK2 (0x00000140) // not supported by all 4x05/4x69 chips
#define EM4x05_MODULATION_PSK3 (0x00000180) // not supported by all 4x05/4x69 chips
#define EM4x05_MODULATION_FSK1 (0x00000200) // not supported by all 4x05/4x69 chips
#define EM4x05_MODULATION_FSK2 (0x00000240) // not supported by all 4x05/4x69 chips
#define EM4x05_PSK_RF_2 (0)
#define EM4x05_PSK_RF_4 (0x00000400)
#define EM4x05_PSK_RF_8 (0x00000800)
#define EM4x05_MAXBLOCK_SHIFT (14)
#define EM4x05_FIRST_USER_BLOCK (5)
#define EM4x05_SET_NUM_BLOCKS(x) \
(((x) + 4) << 14) // number of blocks sent during default read mode
#define EM4x05_GET_NUM_BLOCKS(x) ((((x) >> 14) & 0xF) - 4)
#define EM4x05_READ_LOGIN_REQ (1 << 18)
#define EM4x05_READ_HK_LOGIN_REQ (1 << 19)
#define EM4x05_WRITE_LOGIN_REQ (1 << 20)
#define EM4x05_WRITE_HK_LOGIN_REQ (1 << 21)
#define EM4x05_READ_AFTER_WRITE (1 << 22)
#define EM4x05_DISABLE_ALLOWED (1 << 23)
#define EM4x05_READER_TALK_FIRST (1 << 24)
#define EM4x05_INVERT (1 << 25)
#define EM4x05_PIGEON (1 << 26)
#define EM4x05_WORD_COUNT (16)
#define EM4x05_OPCODE_LOGIN (0b001)
#define EM4x05_OPCODE_WRITE (0b010)
#define EM4x05_OPCODE_READ (0b100)
#define EM4x05_OPCODE_PROTECT (0b110)
#define EM4x05_OPCODE_DISABLE (0b101)
typedef struct {
uint32_t word[EM4x05_WORD_COUNT]; /**< Word data to write */
uint16_t mask; /**< Word mask */
} LFRFIDEM4305;
/** Write EM4305 tag data to tag
*
* @param data The data to write (mask is taken from that data)
*/
void em4305_write(LFRFIDEM4305* data);
#ifdef __cplusplus
}
#endif
+2
View File
@@ -6,6 +6,7 @@ env.Append(
"#/lib/mbedtls/include",
],
SDK_HEADERS=[
File("mbedtls/include/mbedtls/aes.h"),
File("mbedtls/include/mbedtls/des.h"),
File("mbedtls/include/mbedtls/sha1.h"),
File("mbedtls/include/mbedtls/sha256.h"),
@@ -37,6 +38,7 @@ libenv.AppendUnique(
# sources = libenv.GlobRecursive("*.c*", "mbedtls/library")
# Otherwise, we can just use the files we need:
sources = [
File("mbedtls/library/aes.c"),
File("mbedtls/library/bignum.c"),
File("mbedtls/library/bignum_core.c"),
File("mbedtls/library/ecdsa.c"),
+2 -1
View File
@@ -584,7 +584,8 @@ static void mjs_apply_(struct mjs* mjs) {
if(mjs_is_array(v)) {
nargs = mjs_array_length(mjs, v);
args = calloc(nargs, sizeof(args[0]));
for(i = 0; i < nargs; i++) args[i] = mjs_array_get(mjs, v, i);
for(i = 0; i < nargs; i++)
args[i] = mjs_array_get(mjs, v, i);
}
mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args);
free(args);
+6 -6
View File
@@ -47,7 +47,7 @@ static int ptest(struct pstate* p) {
return tok;
}
static int s_unary_ops[] = {
static const int s_unary_ops[] = {
TOK_NOT,
TOK_TILDA,
TOK_PLUS_PLUS,
@@ -56,10 +56,10 @@ static int s_unary_ops[] = {
TOK_MINUS,
TOK_PLUS,
TOK_EOF};
static int s_comparison_ops[] = {TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_EOF};
static int s_postfix_ops[] = {TOK_PLUS_PLUS, TOK_MINUS_MINUS, TOK_EOF};
static int s_equality_ops[] = {TOK_EQ, TOK_NE, TOK_EQ_EQ, TOK_NE_NE, TOK_EOF};
static int s_assign_ops[] = {
static const int s_comparison_ops[] = {TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_EOF};
static const int s_postfix_ops[] = {TOK_PLUS_PLUS, TOK_MINUS_MINUS, TOK_EOF};
static const int s_equality_ops[] = {TOK_EQ, TOK_NE, TOK_EQ_EQ, TOK_NE_NE, TOK_EOF};
static const int s_assign_ops[] = {
TOK_ASSIGN,
TOK_PLUS_ASSIGN,
TOK_MINUS_ASSIGN,
@@ -74,7 +74,7 @@ static int s_assign_ops[] = {
TOK_OR_ASSIGN,
TOK_EOF};
static int findtok(int* toks, int tok) {
static int findtok(int const* toks, int tok) {
int i = 0;
while(tok != toks[i] && toks[i] != TOK_EOF)
i++;
+19 -6
View File
@@ -9,6 +9,8 @@
#include "mjs_string_public.h"
#include "mjs_util.h"
#include <float.h>
mjs_val_t mjs_mk_null(void) {
return MJS_NULL;
}
@@ -94,7 +96,7 @@ int mjs_is_boolean(mjs_val_t v) {
}
#define MJS_IS_POINTER_LEGIT(n) \
(((n)&MJS_TAG_MASK) == 0 || ((n)&MJS_TAG_MASK) == (~0 & MJS_TAG_MASK))
(((n) & MJS_TAG_MASK) == 0 || ((n) & MJS_TAG_MASK) == (~0 & MJS_TAG_MASK))
MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p) {
uint64_t n = ((uint64_t)(uintptr_t)p);
@@ -165,13 +167,13 @@ MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) {
mjs_val_t ret = MJS_UNDEFINED;
mjs_val_t base_v = MJS_UNDEFINED;
int32_t base = 10;
int32_t num;
double num;
/* get number from `this` */
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_NUMBER, NULL)) {
goto clean;
}
num = mjs_get_int32(mjs, mjs->vals.this_obj);
num = mjs_get_double(mjs, mjs->vals.this_obj);
if(mjs_nargs(mjs) >= 1) {
/* get base from arg 0 */
@@ -181,9 +183,20 @@ MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) {
base = mjs_get_int(mjs, base_v);
}
char tmp_str[] = "-2147483648";
itoa(num, tmp_str, base);
ret = mjs_mk_string(mjs, tmp_str, ~0, true);
if(base != 10 || floor(num) == num) {
char tmp_str[] = "-2147483648";
itoa((int32_t)num, tmp_str, base);
ret = mjs_mk_string(mjs, tmp_str, ~0, true);
} else {
char tmp_str[] = "2.22514337200000e-308";
snprintf(tmp_str, sizeof(tmp_str), "%.*g", DBL_DIG, num);
size_t len = strlen(tmp_str);
while(len && tmp_str[len - 1] == '0') {
len--;
}
tmp_str[len] = '\0';
ret = mjs_mk_string(mjs, tmp_str, ~0, true);
}
clean:
mjs_return(mjs, ret);
+7 -29
View File
@@ -392,37 +392,15 @@ static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfCl
mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]);
// Set every block to 0x00
uint16_t block_num = mf_classic_get_total_block_num(type);
if(type == MfClassicType4k) {
// Set every block to 0x00
for(uint16_t i = 1; i < block_num; i++) {
if(mf_classic_is_sector_trailer(i)) {
nfc_generate_mf_classic_sector_trailer(mfc_data, i);
} else {
memset(&mfc_data->block[i].data, 0x00, 16);
}
mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]);
}
} else if(type == MfClassicType1k) {
// Set every block to 0x00
for(uint16_t i = 1; i < block_num; i++) {
if(mf_classic_is_sector_trailer(i)) {
nfc_generate_mf_classic_sector_trailer(mfc_data, i);
} else {
memset(&mfc_data->block[i].data, 0x00, 16);
}
mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]);
}
} else if(type == MfClassicTypeMini) {
// Set every block to 0x00
for(uint16_t i = 1; i < block_num; i++) {
if(mf_classic_is_sector_trailer(i)) {
nfc_generate_mf_classic_sector_trailer(mfc_data, i);
} else {
memset(&mfc_data->block[i].data, 0x00, 16);
}
mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]);
for(uint16_t i = 1; i < block_num; i++) {
if(mf_classic_is_sector_trailer(i)) {
nfc_generate_mf_classic_sector_trailer(mfc_data, i);
} else {
memset(&mfc_data->block[i].data, 0x00, MF_CLASSIC_BLOCK_SIZE);
}
mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]);
}
nfc_generate_mf_classic_block_0(
+5 -3
View File
@@ -118,7 +118,8 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) {
blocks[1] = FELICA_BLOCK_INDEX_WCNT;
blocks[2] = FELICA_BLOCK_INDEX_MAC_A;
FelicaPollerReadCommandResponse* rx_resp;
error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp);
error = felica_poller_read_blocks(
instance, sizeof(blocks), blocks, FELICA_SERVICE_RO_ACCESS, &rx_resp);
if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break;
if(felica_check_mac(
@@ -174,7 +175,7 @@ NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) {
if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break;
FelicaPollerReadCommandResponse* rx_resp;
error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp);
error = felica_poller_read_blocks(instance, 1, blocks, FELICA_SERVICE_RO_ACCESS, &rx_resp);
if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break;
instance->data->data.fs.state.SF1 = 0;
@@ -203,7 +204,8 @@ NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) {
}
FelicaPollerReadCommandResponse* response;
FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response);
FelicaError error = felica_poller_read_blocks(
instance, block_count, block_list, FELICA_SERVICE_RO_ACCESS, &response);
if(error == FelicaErrorNone) {
block_count = (response->SF1 == 0) ? response->block_count : block_count;
uint8_t* data_ptr =
+17
View File
@@ -56,6 +56,23 @@ typedef struct {
*/
FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data);
/**
* @brief Performs felica read operation for blocks provided as parameters
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] block_count Amount of blocks involved in reading procedure
* @param[in] block_numbers Array with block indexes according to felica docs
* @param[in] service_code Service code for the read operation
* @param[out] response_ptr Pointer to the response structure
* @return FelicaErrorNone on success, an error code on failure.
*/
FelicaError felica_poller_read_blocks(
FelicaPoller* instance,
const uint8_t block_count,
const uint8_t* const block_numbers,
uint16_t service_code,
FelicaPollerReadCommandResponse** const response_ptr);
#ifdef __cplusplus
}
#endif
+2 -1
View File
@@ -134,6 +134,7 @@ FelicaError felica_poller_read_blocks(
FelicaPoller* instance,
const uint8_t block_count,
const uint8_t* const block_numbers,
uint16_t service_code,
FelicaPollerReadCommandResponse** const response_ptr) {
furi_assert(instance);
furi_assert(block_count <= 4);
@@ -143,7 +144,7 @@ FelicaError felica_poller_read_blocks(
felica_poller_prepare_tx_buffer(
instance,
FELICA_CMD_READ_WITHOUT_ENCRYPTION,
FELICA_SERVICE_RO_ACCESS,
service_code,
block_count,
block_numbers,
0,
@@ -70,21 +70,6 @@ FelicaError felica_poller_polling(
const FelicaPollerPollingCommand* cmd,
FelicaPollerPollingResponse* resp);
/**
* @brief Performs felica read operation for blocks provided as parameters
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] block_count Amount of blocks involved in reading procedure
* @param[in] block_numbers Array with block indexes according to felica docs
* @param[out] response_ptr Pointer to the response structure
* @return FelicaErrorNone on success, an error code on failure.
*/
FelicaError felica_poller_read_blocks(
FelicaPoller* instance,
const uint8_t block_count,
const uint8_t* const block_numbers,
FelicaPollerReadCommandResponse** const response_ptr);
/**
* @brief Performs felica write operation with data provided as parameters
*
+36 -32
View File
@@ -173,33 +173,35 @@ bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version)
if(flipper_format_key_exist(ff, ISO15693_3_BLOCK_COUNT_KEY) &&
flipper_format_key_exist(ff, ISO15693_3_BLOCK_SIZE_KEY)) {
data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY;
uint32_t block_count;
if(!flipper_format_read_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1)) break;
data->system_info.block_count = block_count;
data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY;
if(!flipper_format_read_hex(
ff, ISO15693_3_BLOCK_SIZE_KEY, &(data->system_info.block_size), 1))
break;
simple_array_init(
data->block_data, data->system_info.block_size * data->system_info.block_count);
if(!flipper_format_read_hex(
ff,
ISO15693_3_DATA_CONTENT_KEY,
simple_array_get_data(data->block_data),
simple_array_get_count(data->block_data)))
break;
if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) {
if(data->system_info.block_count > 0 && data->system_info.block_size > 0) {
simple_array_init(
data->block_data,
data->system_info.block_size * data->system_info.block_count);
simple_array_init(data->block_security, data->system_info.block_count);
const bool security_loaded = has_lock_bits ?
iso15693_3_load_security(data, ff) :
iso15693_3_load_security_legacy(data, ff);
if(!security_loaded) break;
if(!flipper_format_read_hex(
ff,
ISO15693_3_DATA_CONTENT_KEY,
simple_array_get_data(data->block_data),
simple_array_get_count(data->block_data)))
break;
if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) {
const bool security_loaded = has_lock_bits ?
iso15693_3_load_security(data, ff) :
iso15693_3_load_security_legacy(data, ff);
if(!security_loaded) break;
}
}
}
@@ -260,22 +262,24 @@ bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff) {
ff, ISO15693_3_BLOCK_SIZE_KEY, &data->system_info.block_size, 1))
break;
if(!flipper_format_write_hex(
ff,
ISO15693_3_DATA_CONTENT_KEY,
simple_array_cget_data(data->block_data),
simple_array_get_count(data->block_data)))
break;
if(data->system_info.block_count > 0 && data->system_info.block_size > 0) {
if(!flipper_format_write_hex(
ff,
ISO15693_3_DATA_CONTENT_KEY,
simple_array_cget_data(data->block_data),
simple_array_get_count(data->block_data)))
break;
if(!flipper_format_write_comment_cstr(
ff, "Block Security Status: 01 = locked, 00 = not locked"))
break;
if(!flipper_format_write_hex(
ff,
ISO15693_3_SECURITY_STATUS_KEY,
simple_array_cget_data(data->block_security),
simple_array_get_count(data->block_security)))
break;
if(!flipper_format_write_comment_cstr(
ff, "Block Security Status: 01 = locked, 00 = not locked"))
break;
if(!flipper_format_write_hex(
ff,
ISO15693_3_SECURITY_STATUS_KEY,
simple_array_cget_data(data->block_security),
simple_array_get_count(data->block_security)))
break;
}
}
saved = true;
} while(false);
@@ -100,10 +100,12 @@ Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_
break;
}
if(system_info->block_count > 0) {
// Read blocks: Optional command
if(system_info->block_count > 0 && system_info->block_size > 0) {
simple_array_init(
data->block_data, system_info->block_count * system_info->block_size);
simple_array_init(data->block_security, system_info->block_count);
// Read blocks: Optional command
ret = iso15693_3_poller_read_blocks(
instance,
simple_array_get_data(data->block_data),
@@ -115,8 +117,6 @@ Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_
}
// Get block security status: Optional command
simple_array_init(data->block_security, system_info->block_count);
ret = iso15693_3_poller_get_blocks_security(
instance, simple_array_get_data(data->block_security), system_info->block_count);
if(ret != Iso15693_3ErrorNone) {
@@ -19,7 +19,7 @@ typedef struct {
uint8_t cmd_start_byte;
size_t cmd_len_bits;
size_t command_num;
MfClassicListenerCommandHandler* handler;
const MfClassicListenerCommandHandler* handler;
} MfClassicListenerCmd;
static void mf_classic_listener_prepare_emulation(MfClassicListener* instance) {
@@ -441,42 +441,42 @@ static MfClassicListenerCommand
return command;
}
static MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = {
static const MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = {
mf_classic_listener_halt_handler,
};
static MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = {
static const MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = {
mf_classic_listener_auth_key_a_handler,
mf_classic_listener_auth_second_part_handler,
};
static MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = {
static const MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = {
mf_classic_listener_auth_key_b_handler,
mf_classic_listener_auth_second_part_handler,
};
static MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = {
static const MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = {
mf_classic_listener_read_block_handler,
};
static MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = {
static const MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = {
mf_classic_listener_write_block_first_part_handler,
mf_classic_listener_write_block_second_part_handler,
};
static MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = {
static const MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = {
mf_classic_listener_value_dec_handler,
mf_classic_listener_value_data_receive_handler,
mf_classic_listener_value_transfer_handler,
};
static MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = {
static const MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = {
mf_classic_listener_value_inc_handler,
mf_classic_listener_value_data_receive_handler,
mf_classic_listener_value_transfer_handler,
};
static MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = {
static const MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = {
mf_classic_listener_value_restore_handler,
mf_classic_listener_value_data_receive_handler,
mf_classic_listener_value_transfer_handler,
+118 -4
View File
@@ -4,6 +4,46 @@
#define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire"
#define MF_DESFIRE_HW_MINOR_TYPE (0x00)
#define MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40 (0x02)
#define MF_DESFIRE_HW_MAJOR_TYPE_EV1 (0x01)
#define MF_DESFIRE_HW_MAJOR_TYPE_EV2 (0x12)
#define MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL (0x22)
#define MF_DESFIRE_HW_MAJOR_TYPE_EV3 (0x33)
#define MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40 (0x00)
#define MF_DESFIRE_STORAGE_SIZE_2K (0x16)
#define MF_DESFIRE_STORAGE_SIZE_4K (0x18)
#define MF_DESFIRE_STORAGE_SIZE_8K (0x1A)
#define MF_DESFIRE_STORAGE_SIZE_16K (0x1C)
#define MF_DESFIRE_STORAGE_SIZE_32K (0x1E)
#define MF_DESFIRE_STORAGE_SIZE_MF3ICD40 (0xFF)
#define MF_DESFIRE_STORAGE_SIZE_UNKNOWN (0xFF)
#define MF_DESFIRE_TEST_TYPE_MF3ICD40(major, minor, storage) \
(((major) == MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40) && \
((minor) == MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40) && \
((storage) == MF_DESFIRE_STORAGE_SIZE_MF3ICD40))
static const char* mf_desfire_type_strings[] = {
[MfDesfireTypeMF3ICD40] = "(MF3ICD40)",
[MfDesfireTypeEV1] = "EV1",
[MfDesfireTypeEV2] = "EV2",
[MfDesfireTypeEV2XL] = "EV2 XL",
[MfDesfireTypeEV3] = "EV3",
[MfDesfireTypeUnknown] = "UNK",
};
static const char* mf_desfire_size_strings[] = {
[MfDesfireSize2k] = "2K",
[MfDesfireSize4k] = "4K",
[MfDesfireSize8k] = "8K",
[MfDesfireSize16k] = "16K",
[MfDesfireSize32k] = "32K",
[MfDesfireSizeUnknown] = "",
};
const NfcDeviceBase nfc_device_mf_desfire = {
.protocol_name = MF_DESFIRE_PROTOCOL_NAME,
.alloc = (NfcDeviceAlloc)mf_desfire_alloc,
@@ -26,7 +66,7 @@ MfDesfireData* mf_desfire_alloc(void) {
data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config);
data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config);
data->applications = simple_array_alloc(&mf_desfire_application_array_config);
data->device_name = furi_string_alloc();
return data;
}
@@ -38,6 +78,7 @@ void mf_desfire_free(MfDesfireData* data) {
simple_array_free(data->application_ids);
simple_array_free(data->master_key_versions);
iso14443_4a_free(data->iso14443_4a_data);
furi_string_free(data->device_name);
free(data);
}
@@ -228,10 +269,83 @@ bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other)
simple_array_is_equal(data->applications, other->applications);
}
static MfDesfireType mf_desfire_get_type_from_version(const MfDesfireVersion* const version) {
MfDesfireType type = MfDesfireTypeUnknown;
switch(version->hw_major) {
case MF_DESFIRE_HW_MAJOR_TYPE_EV1:
type = MfDesfireTypeEV1;
break;
case MF_DESFIRE_HW_MAJOR_TYPE_EV2:
type = MfDesfireTypeEV2;
break;
case MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL:
type = MfDesfireTypeEV2XL;
break;
case MF_DESFIRE_HW_MAJOR_TYPE_EV3:
type = MfDesfireTypeEV3;
break;
default:
if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage))
type = MfDesfireTypeMF3ICD40;
break;
}
return type;
}
static MfDesfireSize mf_desfire_get_size_from_version(const MfDesfireVersion* const version) {
MfDesfireSize size = MfDesfireSizeUnknown;
switch(version->hw_storage) {
case MF_DESFIRE_STORAGE_SIZE_2K:
size = MfDesfireSize2k;
break;
case MF_DESFIRE_STORAGE_SIZE_4K:
size = MfDesfireSize4k;
break;
case MF_DESFIRE_STORAGE_SIZE_8K:
size = MfDesfireSize8k;
break;
case MF_DESFIRE_STORAGE_SIZE_16K:
size = MfDesfireSize16k;
break;
case MF_DESFIRE_STORAGE_SIZE_32K:
size = MfDesfireSize32k;
break;
default:
if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage))
size = MfDesfireSize4k;
break;
}
return size;
}
const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) {
UNUSED(data);
UNUSED(name_type);
return MF_DESFIRE_PROTOCOL_NAME;
furi_check(data);
const MfDesfireType type = mf_desfire_get_type_from_version(&data->version);
const MfDesfireSize size = mf_desfire_get_size_from_version(&data->version);
if(type == MfDesfireTypeUnknown) {
furi_string_printf(data->device_name, "Unknown %s", MF_DESFIRE_PROTOCOL_NAME);
} else if(name_type == NfcDeviceNameTypeFull) {
furi_string_printf(
data->device_name,
"%s %s %s",
MF_DESFIRE_PROTOCOL_NAME,
mf_desfire_type_strings[type],
mf_desfire_size_strings[size]);
} else {
furi_string_printf(
data->device_name,
"%s %s",
mf_desfire_type_strings[type],
mf_desfire_size_strings[size]);
}
return furi_string_get_cstr(data->device_name);
}
const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) {
+30
View File
@@ -29,6 +29,28 @@ extern "C" {
#define MF_DESFIRE_APP_ID_SIZE (3)
#define MF_DESFIRE_VALUE_SIZE (4)
typedef enum {
MfDesfireTypeMF3ICD40,
MfDesfireTypeEV1,
MfDesfireTypeEV2,
MfDesfireTypeEV2XL,
MfDesfireTypeEV3,
MfDesfireTypeUnknown,
MfDesfireTypeNum,
} MfDesfireType;
typedef enum {
MfDesfireSize2k,
MfDesfireSize4k,
MfDesfireSize8k,
MfDesfireSize16k,
MfDesfireSize32k,
MfDesfireSizeUnknown,
MfDesfireSizeNum,
} MfDesfireSize;
typedef struct {
uint8_t hw_vendor;
uint8_t hw_type;
@@ -75,6 +97,7 @@ typedef enum {
MfDesfireFileTypeValue = 2,
MfDesfireFileTypeLinearRecord = 3,
MfDesfireFileTypeCyclicRecord = 4,
MfDesfireFileTypeTransactionMac = 5,
} MfDesfireFileType;
typedef enum {
@@ -106,6 +129,11 @@ typedef struct {
uint32_t max;
uint32_t cur;
} record;
struct {
uint8_t key_option;
uint8_t key_version;
uint32_t counter_limit;
} transaction_mac;
};
} MfDesfireFileSettings;
@@ -131,6 +159,7 @@ typedef enum {
MfDesfireErrorProtocol,
MfDesfireErrorTimeout,
MfDesfireErrorAuthentication,
MfDesfireErrorCommandNotSupported,
} MfDesfireError;
typedef struct {
@@ -141,6 +170,7 @@ typedef struct {
SimpleArray* master_key_versions;
SimpleArray* application_ids;
SimpleArray* applications;
FuriString* device_name;
} MfDesfireData;
extern const NfcDeviceBase nfc_device_mf_desfire;
+70 -12
View File
@@ -1,5 +1,7 @@
#include "mf_desfire_i.h"
#include <bit_lib/bit_lib.h>
#define TAG "MfDesfire"
#define BITS_IN_BYTE (8U)
@@ -47,6 +49,10 @@
#define MF_DESFIRE_FFF_FILE_MAX_KEY "Max"
#define MF_DESFIRE_FFF_FILE_CUR_KEY "Cur"
#define MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY "Key Option"
#define MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY "Key Version"
#define MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY "Counter Limit"
bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) {
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireVersion);
@@ -168,12 +174,19 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer
uint32_t cur : 3 * BITS_IN_BYTE;
} MfDesfireFileSettingsRecord;
typedef struct FURI_PACKED {
uint8_t key_option;
uint8_t key_version;
uint8_t counter_limit[];
} MfDesfireFileSettingsTransactionMac;
typedef struct FURI_PACKED {
MfDesfireFileSettingsHeader header;
union {
MfDesfireFileSettingsData data;
MfDesfireFileSettingsValue value;
MfDesfireFileSettingsRecord record;
MfDesfireFileSettingsTransactionMac transaction_mac;
};
} MfDesfireFileSettingsLayout;
@@ -182,7 +195,7 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer
const size_t data_size = bit_buffer_get_size_bytes(buf);
const uint8_t* data_ptr = bit_buffer_get_data(buf);
const size_t min_data_size =
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData);
sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsTransactionMac);
if(data_size < min_data_size) {
FURI_LOG_E(
@@ -202,17 +215,11 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer
if(file_settings_temp.type == MfDesfireFileTypeStandard ||
file_settings_temp.type == MfDesfireFileTypeBackup) {
memcpy(
&layout.data,
&data_ptr[sizeof(MfDesfireFileSettingsHeader)],
sizeof(MfDesfireFileSettingsData));
memcpy(&layout.data, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsData));
file_settings_temp.data.size = layout.data.size;
bytes_processed += sizeof(MfDesfireFileSettingsData);
} else if(file_settings_temp.type == MfDesfireFileTypeValue) {
memcpy(
&layout.value,
&data_ptr[sizeof(MfDesfireFileSettingsHeader)],
sizeof(MfDesfireFileSettingsValue));
memcpy(&layout.value, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsValue));
file_settings_temp.value.lo_limit = layout.value.lo_limit;
file_settings_temp.value.hi_limit = layout.value.hi_limit;
file_settings_temp.value.limited_credit_value = layout.value.limited_credit_value;
@@ -222,13 +229,34 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer
file_settings_temp.type == MfDesfireFileTypeLinearRecord ||
file_settings_temp.type == MfDesfireFileTypeCyclicRecord) {
memcpy(
&layout.record,
&data_ptr[sizeof(MfDesfireFileSettingsHeader)],
sizeof(MfDesfireFileSettingsRecord));
&layout.record, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsRecord));
file_settings_temp.record.size = layout.record.size;
file_settings_temp.record.max = layout.record.max;
file_settings_temp.record.cur = layout.record.cur;
bytes_processed += sizeof(MfDesfireFileSettingsRecord);
} else if(file_settings_temp.type == MfDesfireFileTypeTransactionMac) {
const bool has_counter_limit = (layout.header.comm & 0x20) != 0;
memcpy(
&layout.transaction_mac,
&data_ptr[bytes_processed],
sizeof(MfDesfireFileSettingsTransactionMac));
file_settings_temp.transaction_mac.key_option = layout.transaction_mac.key_option;
file_settings_temp.transaction_mac.key_version = layout.transaction_mac.key_version;
if(!has_counter_limit) {
file_settings_temp.transaction_mac.counter_limit = 0;
} else {
// AES (4b) or LRP (2b)
const size_t counter_limit_size = (layout.transaction_mac.key_option & 0x02) ? 4 :
2;
memcpy(
&layout.transaction_mac,
&data_ptr[bytes_processed],
sizeof(MfDesfireFileSettingsTransactionMac) + counter_limit_size);
file_settings_temp.transaction_mac.counter_limit = bit_lib_bytes_to_num_be(
layout.transaction_mac.counter_limit, counter_limit_size);
bytes_processed += counter_limit_size;
}
bytes_processed += sizeof(MfDesfireFileSettingsTransactionMac);
} else {
FURI_LOG_W(TAG, "Unknown file type: %02x", file_settings_temp.type);
break;
@@ -468,6 +496,21 @@ bool mf_desfire_file_settings_load(
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY);
if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1))
break;
} else if(data->type == MfDesfireFileTypeTransactionMac) {
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY);
if(!flipper_format_read_hex(
ff, furi_string_get_cstr(key), &data->transaction_mac.key_option, 1))
break;
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY);
if(!flipper_format_read_hex(
ff, furi_string_get_cstr(key), &data->transaction_mac.key_version, 1))
break;
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY);
if(!flipper_format_read_uint32(
ff, furi_string_get_cstr(key), &data->transaction_mac.counter_limit, 1))
break;
}
success = true;
@@ -716,6 +759,21 @@ bool mf_desfire_file_settings_save(
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY);
if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1))
break;
} else if(data->type == MfDesfireFileTypeTransactionMac) {
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY);
if(!flipper_format_write_hex(
ff, furi_string_get_cstr(key), &data->transaction_mac.key_option, 1))
break;
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY);
if(!flipper_format_write_hex(
ff, furi_string_get_cstr(key), &data->transaction_mac.key_version, 1))
break;
furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY);
if(!flipper_format_write_uint32(
ff, furi_string_get_cstr(key), &data->transaction_mac.counter_limit, 1))
break;
}
success = true;
@@ -82,9 +82,12 @@ static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* in
FURI_LOG_D(TAG, "Read free memory success");
instance->state = MfDesfirePollerStateReadMasterKeySettings;
} else if(instance->error == MfDesfireErrorNotPresent) {
FURI_LOG_D(TAG, "Read free memoty is unsupported");
FURI_LOG_D(TAG, "Read free memory is not present");
instance->state = MfDesfirePollerStateReadMasterKeySettings;
command = NfcCommandReset;
} else if(instance->error == MfDesfireErrorCommandNotSupported) {
FURI_LOG_D(TAG, "Read free memory is unsupported");
instance->state = MfDesfirePollerStateReadMasterKeySettings;
} else {
FURI_LOG_E(TAG, "Failed to read free memory");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
@@ -25,6 +25,8 @@ MfDesfireError mf_desfire_process_status_code(uint8_t status_code) {
return MfDesfireErrorNone;
case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR:
return MfDesfireErrorAuthentication;
case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE:
return MfDesfireErrorCommandNotSupported;
default:
return MfDesfireErrorProtocol;
}
@@ -466,7 +468,7 @@ MfDesfireError mf_desfire_poller_read_file_data_multi(
file_type == MfDesfireFileTypeLinearRecord ||
file_type == MfDesfireFileTypeCyclicRecord) {
error = mf_desfire_poller_read_file_records(
instance, file_id, 0, file_settings_cur->data.size, file_data);
instance, file_id, 0, file_settings_cur->record.size, file_data);
}
}
+71 -55
View File
@@ -27,65 +27,51 @@ MfPlusError mf_plus_get_type_from_version(
MfPlusError error = MfPlusErrorProtocol;
if(mf_plus_data->version.hw_major == 0x02 || mf_plus_data->version.hw_major == 0x82) {
if(mf_plus_data->version.hw_type == 0x02 || mf_plus_data->version.hw_type == 0x82) {
error = MfPlusErrorNone;
if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) {
// Mifare Plus 2K SL2
mf_plus_data->type = MfPlusTypePlus;
// Mifare Plus EV1/EV2
// Revision
switch(mf_plus_data->version.hw_major) {
case 0x11:
mf_plus_data->type = MfPlusTypeEV1;
FURI_LOG_D(TAG, "Mifare Plus EV1");
break;
case 0x22:
mf_plus_data->type = MfPlusTypeEV2;
FURI_LOG_D(TAG, "Mifare Plus EV2");
break;
default:
mf_plus_data->type = MfPlusTypeUnknown;
FURI_LOG_D(TAG, "Unknown Mifare Plus EV type");
break;
}
// Storage size
switch(mf_plus_data->version.hw_storage) {
case 0x16:
mf_plus_data->size = MfPlusSize2K;
mf_plus_data->security_level = MfPlusSecurityLevel2;
FURI_LOG_D(TAG, "Mifare Plus 2K SL2");
} else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) {
// Mifare Plus 4K SL3
mf_plus_data->type = MfPlusTypePlus;
FURI_LOG_D(TAG, "2K");
break;
case 0x18:
mf_plus_data->size = MfPlusSize4K;
FURI_LOG_D(TAG, "4K");
break;
default:
mf_plus_data->size = MfPlusSizeUnknown;
FURI_LOG_D(TAG, "Unknown storage size");
break;
}
// Security level
if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) {
// Mifare Plus EV1/2 SL3
mf_plus_data->security_level = MfPlusSecurityLevel3;
FURI_LOG_D(TAG, "Mifare Plus 4K SL3");
FURI_LOG_D(TAG, "Mifare Plus EV1/2 SL3");
} else {
// Mifare Plus EV1/EV2
// Revision
switch(mf_plus_data->version.hw_major) {
case 0x11:
mf_plus_data->type = MfPlusTypeEV1;
FURI_LOG_D(TAG, "Mifare Plus EV1");
break;
case 0x22:
mf_plus_data->type = MfPlusTypeEV2;
FURI_LOG_D(TAG, "Mifare Plus EV2");
break;
default:
mf_plus_data->type = MfPlusTypeUnknown;
FURI_LOG_D(TAG, "Unknown Mifare Plus EV type");
break;
}
// Storage size
switch(mf_plus_data->version.hw_storage) {
case 0x16:
mf_plus_data->size = MfPlusSize2K;
FURI_LOG_D(TAG, "2K");
break;
case 0x18:
mf_plus_data->size = MfPlusSize4K;
FURI_LOG_D(TAG, "4K");
break;
default:
mf_plus_data->size = MfPlusSizeUnknown;
FURI_LOG_D(TAG, "Unknown storage size");
break;
}
// Security level
if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) {
// Mifare Plus EV1/2 SL3
mf_plus_data->security_level = MfPlusSecurityLevel3;
FURI_LOG_D(TAG, "Miare Plus EV1/2 SL3");
} else {
// Mifare Plus EV1/2 SL1
mf_plus_data->security_level = MfPlusSecurityLevel1;
FURI_LOG_D(TAG, "Miare Plus EV1/2 SL1");
}
// Mifare Plus EV1/2 SL1
mf_plus_data->security_level = MfPlusSecurityLevel1;
FURI_LOG_D(TAG, "Mifare Plus EV1/2 SL1");
}
}
@@ -148,6 +134,24 @@ MfPlusError
FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type");
}
break;
case 0x10:
// Mifare Plus X 2K SL2
mf_plus_data->type = MfPlusTypeX;
mf_plus_data->size = MfPlusSize2K;
mf_plus_data->security_level = MfPlusSecurityLevel2;
FURI_LOG_D(TAG, "Mifare Plus X 2K SL2");
error = MfPlusErrorNone;
break;
case 0x11:
// Mifare Plus X 4K SL2
mf_plus_data->type = MfPlusTypeX;
mf_plus_data->size = MfPlusSize4K;
mf_plus_data->security_level = MfPlusSecurityLevel2;
FURI_LOG_D(TAG, "Mifare Plus X 4K SL2");
error = MfPlusErrorNone;
break;
case 0x18:
if(memcmp(
@@ -234,10 +238,22 @@ MfPlusError
}
MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) {
const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion);
bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion);
if(can_parse) {
bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion));
} else if(
bit_buffer_get_size_bytes(buf) == 8 &&
bit_buffer_get_byte(buf, 0) == MF_PLUS_STATUS_ADDITIONAL_FRAME) {
// HACK(-nofl): There are supposed to be three parts to the GetVersion command,
// with the second and third parts fetched by sending the AdditionalFrame
// command. I don't know whether the entire MIFARE Plus line uses status as
// the first byte, so let's just assume we only have the first part of
// the response if it's size 8 and starts with the AF status. The second
// part of the response is the same size and status byte, but so far
// we're only reading one response.
can_parse = true;
bit_buffer_write_bytes_mid(buf, data, 1, bit_buffer_get_size_bytes(buf) - 1);
}
return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol;
+3
View File
@@ -4,6 +4,9 @@
#define MF_PLUS_FFF_PICC_PREFIX "PICC"
#define MF_PLUS_STATUS_OPERATION_OK (0x90)
#define MF_PLUS_STATUS_ADDITIONAL_FRAME (0xAF)
MfPlusError mf_plus_get_type_from_version(
const Iso14443_4aData* iso14443_4a_data,
MfPlusData* mf_plus_data);
@@ -37,7 +37,7 @@ MfUltralightError mf_ultralight_poller_auth_pwd(
furi_check(data);
uint8_t auth_cmd[5] = {MF_ULTRALIGHT_CMD_PWD_AUTH}; //-V1009
memccpy(&auth_cmd[1], data->password.data, 0, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE);
memcpy(&auth_cmd[1], data->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE);
bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd));
MfUltralightError ret = MfUltralightErrorNone;
@@ -23,6 +23,7 @@ typedef struct {
FuriThreadId thread_id;
MfUltralightError error;
MfUltralightPollerContextData data;
const MfUltralightPollerAuthContext* auth_context;
} MfUltralightPollerContext;
typedef MfUltralightError (*MfUltralightPollerCmdHandler)(
@@ -250,12 +251,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void
poller_context->error = mfu_event->data->error;
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
mfu_event->data->auth_context.skip_auth = true;
if(mf_ultralight_support_feature(
mfu_poller->feature_set, MfUltralightFeatureSupportAuthenticate)) {
mfu_event->data->auth_context.skip_auth = false;
memset(
mfu_poller->auth_context.tdes_key.data, 0x00, MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
if(poller_context->auth_context != NULL) {
mfu_event->data->auth_context = *poller_context->auth_context;
} else {
mfu_event->data->auth_context.skip_auth = true;
if(mfu_poller->data->type == MfUltralightTypeMfulC) {
mfu_event->data->auth_context.skip_auth = false;
memset(
mfu_poller->auth_context.tdes_key.data,
0x00,
MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
}
}
}
@@ -266,13 +272,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void
return command;
}
MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data) {
MfUltralightError mf_ultralight_poller_sync_read_card(
Nfc* nfc,
MfUltralightData* data,
const MfUltralightPollerAuthContext* auth_context) {
furi_check(nfc);
furi_check(data);
MfUltralightPollerContext poller_context = {};
poller_context.thread_id = furi_thread_get_current_id();
poller_context.data.data = mf_ultralight_alloc();
poller_context.auth_context = auth_context;
NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight);
nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context);
@@ -1,6 +1,7 @@
#pragma once
#include "mf_ultralight.h"
#include "mf_ultralight_poller.h"
#include <nfc/nfc.h>
#ifdef __cplusplus
@@ -27,7 +28,10 @@ MfUltralightError mf_ultralight_poller_sync_read_tearing_flag(
uint8_t flag_num,
MfUltralightTearingFlag* data);
MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data);
MfUltralightError mf_ultralight_poller_sync_read_card(
Nfc* nfc,
MfUltralightData* data,
const MfUltralightPollerAuthContext* auth_context);
#ifdef __cplusplus
}
+1 -1
View File
@@ -31,7 +31,7 @@
* When implementing a new protocol, add its implementation
* here under its own index defined in nfc_protocol.h.
*/
const NfcDeviceBase* nfc_devices[NfcProtocolNum] = {
const NfcDeviceBase* const nfc_devices[NfcProtocolNum] = {
[NfcProtocolIso14443_3a] = &nfc_device_iso14443_3a,
[NfcProtocolIso14443_3b] = &nfc_device_iso14443_3b,
[NfcProtocolIso14443_4a] = &nfc_device_iso14443_4a,
+1 -1
View File
@@ -6,7 +6,7 @@
extern "C" {
#endif
extern const NfcDeviceBase* nfc_devices[];
extern const NfcDeviceBase* const nfc_devices[];
#ifdef __cplusplus
}
+1 -1
View File
@@ -8,7 +8,7 @@
#include <nfc/protocols/slix/slix_listener_defs.h>
#include <nfc/protocols/felica/felica_listener_defs.h>
const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = {
const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = {
[NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a,
[NfcProtocolIso14443_3b] = NULL,
[NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a,
+1 -1
View File
@@ -7,7 +7,7 @@
extern "C" {
#endif
extern const NfcListenerBase* nfc_listeners_api[NfcProtocolNum];
extern const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum];
#ifdef __cplusplus
}
+1 -1
View File
@@ -13,7 +13,7 @@
#include <nfc/protocols/slix/slix_poller_defs.h>
#include <nfc/protocols/st25tb/st25tb_poller_defs.h>
const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = {
const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = {
[NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a,
[NfcProtocolIso14443_3b] = &nfc_poller_iso14443_3b,
[NfcProtocolIso14443_4a] = &nfc_poller_iso14443_4a,
+1 -1
View File
@@ -7,7 +7,7 @@
extern "C" {
#endif
extern const NfcPollerBase* nfc_pollers_api[NfcProtocolNum];
extern const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum];
#ifdef __cplusplus
}
+1 -1
View File
@@ -88,7 +88,7 @@ static NfcCommand st25tb_poller_request_mode_handler(St25tbPoller* instance) {
St25tbPollerEventDataModeRequest* mode_request_data =
&instance->st25tb_event_data.mode_request;
furi_assert(mode_request_data->mode < St25tbPollerModeNum);
furi_check(mode_request_data->mode < St25tbPollerModeNum);
if(mode_request_data->mode == St25tbPollerModeRead) {
instance->state = St25tbPollerStateRead;
+1
View File
@@ -317,6 +317,7 @@ SubGhzProtocolStatus
res = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.is_running = true;
res = SubGhzProtocolStatusOk;
+27 -17
View File
@@ -14,12 +14,13 @@
#define TAG "SubGhzProtocolCame"
#define CAME_12_COUNT_BIT 12
#define CAME_24_COUNT_BIT 24
#define PRASTEL_COUNT_BIT 25
#define PRASTEL_NAME "Prastel"
#define AIRFORCE_COUNT_BIT 18
#define AIRFORCE_NAME "Airforce"
#define CAME_12_COUNT_BIT 12
#define CAME_24_COUNT_BIT 24
#define PRASTEL_25_COUNT_BIT 25
#define PRASTEL_42_COUNT_BIT 42
#define PRASTEL_NAME "Prastel"
#define AIRFORCE_COUNT_BIT 18
#define AIRFORCE_NAME "Airforce"
static const SubGhzBlockConst subghz_protocol_came_const = {
.te_short = 320,
@@ -123,6 +124,7 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i
switch(instance->generic.data_count_bit) {
case CAME_24_COUNT_BIT:
case PRASTEL_42_COUNT_BIT:
// CAME 24 Bit = 24320 us
header_te = 76;
break;
@@ -131,7 +133,7 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i
// CAME 12 Bit Original only! and Airforce protocol = 15040 us
header_te = 47;
break;
case PRASTEL_COUNT_BIT:
case PRASTEL_25_COUNT_BIT:
// PRASTEL = 11520 us
header_te = 36;
break;
@@ -174,7 +176,7 @@ SubGhzProtocolStatus
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit > PRASTEL_COUNT_BIT) {
if(instance->generic.data_count_bit > PRASTEL_42_COUNT_BIT) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
@@ -268,7 +270,8 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat
if((instance->decoder.decode_count_bit ==
subghz_protocol_came_const.min_count_bit_for_found) ||
(instance->decoder.decode_count_bit == AIRFORCE_COUNT_BIT) ||
(instance->decoder.decode_count_bit == PRASTEL_COUNT_BIT) ||
(instance->decoder.decode_count_bit == PRASTEL_25_COUNT_BIT) ||
(instance->decoder.decode_count_bit == PRASTEL_42_COUNT_BIT) ||
(instance->decoder.decode_count_bit == CAME_24_COUNT_BIT)) {
instance->generic.serial = 0x0;
instance->generic.btn = 0x0;
@@ -337,7 +340,7 @@ SubGhzProtocolStatus
if(ret != SubGhzProtocolStatusOk) {
break;
}
if(instance->generic.data_count_bit > PRASTEL_COUNT_BIT) {
if(instance->generic.data_count_bit > PRASTEL_42_COUNT_BIT) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
ret = SubGhzProtocolStatusErrorValueBitCount;
break;
@@ -350,23 +353,30 @@ void subghz_protocol_decoder_came_get_string(void* context, FuriString* output)
furi_assert(context);
SubGhzProtocolDecoderCame* instance = context;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
uint32_t code_found_lo = instance->generic.data & 0x000003ffffffffff;
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff;
uint32_t code_found_reverse_lo = code_found_reverse & 0x000003ffffffffff;
const char* name = instance->generic.protocol_name;
switch(instance->generic.data_count_bit) {
case PRASTEL_25_COUNT_BIT:
case PRASTEL_42_COUNT_BIT:
name = PRASTEL_NAME;
break;
case AIRFORCE_COUNT_BIT:
name = AIRFORCE_NAME;
break;
}
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:0x%08lX\r\n"
"Yek:0x%08lX\r\n",
(instance->generic.data_count_bit == PRASTEL_COUNT_BIT ?
PRASTEL_NAME :
(instance->generic.data_count_bit == AIRFORCE_COUNT_BIT ?
AIRFORCE_NAME :
instance->generic.protocol_name)),
name,
instance->generic.data_count_bit,
code_found_lo,
code_found_reverse_lo);
+1 -1
View File
@@ -1,6 +1,6 @@
#include "protocol_items.h" // IWYU pragma: keep
const SubGhzProtocol* subghz_protocol_registry_items[] = {
const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&subghz_protocol_gate_tx,
&subghz_protocol_keeloq,
&subghz_protocol_star_line,
+1 -1
View File
@@ -228,7 +228,7 @@ static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* i
rolling = 0xE6000000;
}
if(fixed > 0xCFD41B90) {
FURI_LOG_E("TAG", "Encode wrong fixed data");
FURI_LOG_E(TAG, "Encode wrong fixed data");
return false;
}
+1 -1
View File
@@ -12,7 +12,7 @@ typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry;
typedef struct SubGhzProtocol SubGhzProtocol;
struct SubGhzProtocolRegistry {
const SubGhzProtocol** items;
const SubGhzProtocol* const* items;
const size_t size;
};
+4
View File
@@ -12,6 +12,10 @@ env.Append(
],
SDK_HEADERS=[
File("api_lock.h"),
File("cli/cli_ansi.h"),
File("cli/cli_command.h"),
File("cli/cli_registry.h"),
File("cli/shell/cli_shell.h"),
File("compress.h"),
File("manchester_decoder.h"),
File("manchester_encoder.h"),
+123
View File
@@ -0,0 +1,123 @@
#include "cli_ansi.h"
typedef enum {
CliAnsiParserStateInitial,
CliAnsiParserStateEscape,
CliAnsiParserStateEscapeBrace,
CliAnsiParserStateEscapeBraceOne,
CliAnsiParserStateEscapeBraceOneSemicolon,
CliAnsiParserStateEscapeBraceOneSemicolonModifiers,
} CliAnsiParserState;
struct CliAnsiParser {
CliAnsiParserState state;
CliModKey modifiers;
};
CliAnsiParser* cli_ansi_parser_alloc(void) {
CliAnsiParser* parser = malloc(sizeof(CliAnsiParser));
return parser;
}
void cli_ansi_parser_free(CliAnsiParser* parser) {
free(parser);
}
/**
* @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;
}
}
#define PARSER_RESET_AND_RETURN(parser, modifiers_val, key_val) \
do { \
parser->state = CliAnsiParserStateInitial; \
return (CliAnsiParserResult){ \
.is_done = true, \
.result = (CliKeyCombo){ \
.modifiers = modifiers_val, \
.key = key_val, \
}}; \
} while(0);
CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c) {
switch(parser->state) {
case CliAnsiParserStateInitial:
// <key> -> <key>
if(c != CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); // -V1048
// <ESC> ...
parser->state = CliAnsiParserStateEscape;
break;
case CliAnsiParserStateEscape:
// <ESC> <ESC> -> <ESC>
if(c == CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c);
// <ESC> <key> -> Alt + <key>
if(c != '[') PARSER_RESET_AND_RETURN(parser, CliModKeyAlt, c);
// <ESC> [ ...
parser->state = CliAnsiParserStateEscapeBrace;
break;
case CliAnsiParserStateEscapeBrace:
// <ESC> [ <key mnemonic> -> <key>
if(c != '1') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, cli_ansi_key_from_mnemonic(c));
// <ESC> [ 1 ...
parser->state = CliAnsiParserStateEscapeBraceOne;
break;
case CliAnsiParserStateEscapeBraceOne:
// <ESC> [ 1 <non-;> -> error
if(c != ';') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, CliKeyUnrecognized);
// <ESC> [ 1 ; ...
parser->state = CliAnsiParserStateEscapeBraceOneSemicolon;
break;
case CliAnsiParserStateEscapeBraceOneSemicolon:
// <ESC> [ 1 ; <modifiers> ...
parser->modifiers = (c - '0');
parser->modifiers &= ~1;
parser->state = CliAnsiParserStateEscapeBraceOneSemicolonModifiers;
break;
case CliAnsiParserStateEscapeBraceOneSemicolonModifiers:
// <ESC> [ 1 ; <modifiers> <key mnemonic> -> <modifiers> + <key>
PARSER_RESET_AND_RETURN(parser, parser->modifiers, cli_ansi_key_from_mnemonic(c));
}
return (CliAnsiParserResult){.is_done = false};
}
CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser) {
CliAnsiParserResult result = {.is_done = false};
if(parser->state == CliAnsiParserStateEscape) {
result.is_done = true;
result.result.key = CliKeyEsc;
result.result.modifiers = CliModKeyNo;
}
parser->state = CliAnsiParserStateInitial;
return result;
}
+153
View File
@@ -0,0 +1,153 @@
#pragma once
#include <furi.h>
#ifdef __cplusplus
extern "C" {
#endif
// text styling
#define ANSI_RESET "\e[0m"
#define ANSI_BOLD "\e[1m"
#define ANSI_FAINT "\e[2m"
#define ANSI_INVERT "\e[7m"
#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"
// cursor positioning
#define ANSI_CURSOR_UP_BY(rows) "\e[" rows "A"
#define ANSI_CURSOR_DOWN_BY(rows) "\e[" rows "B"
#define ANSI_CURSOR_RIGHT_BY(cols) "\e[" cols "C"
#define ANSI_CURSOR_LEFT_BY(cols) "\e[" cols "D"
#define ANSI_CURSOR_DOWN_BY_AND_FIRST_COLUMN(rows) "\e[" rows "E"
#define ANSI_CURSOR_UP_BY_AND_FIRST_COLUMN(rows) "\e[" rows "F"
#define ANSI_CURSOR_HOR_POS(pos) "\e[" pos "G"
#define ANSI_CURSOR_POS(row, col) "\e[" row ";" col "H"
// erasing
#define ANSI_ERASE_FROM_CURSOR_TO_END "0"
#define ANSI_ERASE_FROM_START_TO_CURSOR "1"
#define ANSI_ERASE_ENTIRE "2"
#define ANSI_ERASE_DISPLAY(portion) "\e[" portion "J"
#define ANSI_ERASE_LINE(portion) "\e[" portion "K"
#define ANSI_ERASE_SCROLLBACK_BUFFER ANSI_ERASE_DISPLAY("3")
// misc
#define ANSI_INSERT_MODE_ENABLE "\e[4h"
#define ANSI_INSERT_MODE_DISABLE "\e[4l"
typedef enum {
CliKeyUnrecognized = 0,
CliKeySOH = 0x01,
CliKeyETX = 0x03,
CliKeyEOT = 0x04,
CliKeyBell = 0x07,
CliKeyBackspace = 0x08,
CliKeyTab = 0x09,
CliKeyLF = 0x0A,
CliKeyFF = 0x0C,
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;
typedef struct CliAnsiParser CliAnsiParser;
typedef struct {
bool is_done;
CliKeyCombo result;
} CliAnsiParserResult;
/**
* @brief Allocates an ANSI parser
*/
CliAnsiParser* cli_ansi_parser_alloc(void);
/**
* @brief Frees an ANSI parser
*/
void cli_ansi_parser_free(CliAnsiParser* parser);
/**
* @brief Feeds an ANSI parser a character
*/
CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c);
/**
* @brief Feeds an ANSI parser a timeout event
*
* As a user of the ANSI parser API, you are responsible for calling this
* function some time after the last character was fed into the parser. The
* recommended timeout is about 10 ms. The exact value does not matter as long
* as it is small enough for the user not notice a delay, but big enough that
* when a terminal is sending an escape sequence, this function does not get
* called in between the characters of the sequence.
*/
CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser);
#ifdef __cplusplus
}
#endif
+17
View File
@@ -0,0 +1,17 @@
#include "cli_command.h"
#include "cli_ansi.h"
bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side) {
if(pipe_state(side) == PipeStateBroken) return true;
if(!pipe_bytes_available(side)) return false;
char c = getchar();
return c == CliKeyETX;
}
void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
furi_check(cmd);
furi_check(arg);
furi_check(usage);
printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage);
}
+103
View File
@@ -0,0 +1,103 @@
/**
* @file cli_command.h
* Command metadata and helpers
*/
#pragma once
#include <furi.h>
#include <toolbox/pipe.h>
#include <lib/flipper_application/flipper_application.h>
#ifdef __cplusplus
extern "C" {
#endif
#define CLI_PLUGIN_API_VERSION 1
typedef enum {
CliCommandFlagDefault = 0, /**< Default */
CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps */
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
CliCommandFlagDontAttachStdio = (1 << 2), /**< Do no attach I/O pipe to thread stdio */
CliCommandFlagUseShellThread =
(1
<< 3), /**< Don't start a separate thread to run the command in. Incompatible with DontAttachStdio */
// internal flags (do not set them yourselves!)
CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */
} CliCommandFlag;
/**
* @brief CLI command execution callback pointer
*
* This callback will be called from a separate thread spawned just for your
* command. The pipe will be installed as the thread's stdio, so you can use
* `printf`, `getchar` and other standard functions to communicate with the
* user.
*
* @param [in] pipe Pipe that can be used to send and receive data. If
* `CliCommandFlagDontAttachStdio` was not set, you can
* also use standard C functions (printf, getc, etc.) to
* access this pipe.
* @param [in] args String with what was passed after the command
* @param [in] context Whatever you provided to `cli_add_command`
*/
typedef void (*CliCommandExecuteCallback)(PipeSide* pipe, FuriString* args, void* context);
typedef struct {
char* name;
CliCommandExecuteCallback execute_callback;
CliCommandFlag flags;
size_t stack_depth;
} CliCommandDescriptor;
/**
* @brief Configuration for locating external commands
*/
typedef struct {
const char* search_directory; //<! The directory to look in
const char* fal_prefix; //<! File name prefix that commands should have
const char* appid; //<! Expected plugin-reported appid
} CliCommandExternalConfig;
/**
* @brief Detects if Ctrl+C has been pressed or session has been terminated
*
* @param [in] side Pointer to pipe side given to the command thread
* @warning This function also assumes that the pipe is installed as the
* thread's stdio
* @warning This function will consume 0 or 1 bytes from the pipe
*/
bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side);
/** Print unified cmd usage tip
*
* @param cmd cmd name
* @param usage usage tip
* @param arg arg passed by user
*/
void cli_print_usage(const char* cmd, const char* usage, const char* arg);
#define CLI_COMMAND_INTERFACE(name, execute_callback, flags, stack_depth, app_id) \
static const CliCommandDescriptor cli_##name##_desc = { \
#name, \
&execute_callback, \
flags, \
stack_depth, \
}; \
\
static const FlipperAppPluginDescriptor plugin_descriptor = { \
.appid = app_id, \
.ep_api_version = CLI_PLUGIN_API_VERSION, \
.entry_point = &cli_##name##_desc, \
}; \
\
const FlipperAppPluginDescriptor* cli_##name##_ep(void) { \
return &plugin_descriptor; \
}
#ifdef __cplusplus
}
#endif
+176
View File
@@ -0,0 +1,176 @@
#include "cli_registry.h"
#include "cli_registry_i.h"
#include <toolbox/pipe.h>
#include <storage/storage.h>
#define TAG "CliRegistry"
struct CliRegistry {
CliCommandDict_t commands;
FuriMutex* mutex;
};
CliRegistry* cli_registry_alloc(void) {
CliRegistry* registry = malloc(sizeof(CliRegistry));
CliCommandDict_init(registry->commands);
registry->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
return registry;
}
void cli_registry_free(CliRegistry* registry) {
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
furi_mutex_free(registry->mutex);
CliCommandDict_clear(registry->commands);
free(registry);
}
void cli_registry_add_command(
CliRegistry* registry,
const char* name,
CliCommandFlag flags,
CliCommandExecuteCallback callback,
void* context) {
cli_registry_add_command_ex(
registry, name, flags, callback, context, CLI_BUILTIN_COMMAND_STACK_SIZE);
}
void cli_registry_add_command_ex(
CliRegistry* registry,
const char* name,
CliCommandFlag flags,
CliCommandExecuteCallback callback,
void* context,
size_t stack_size) {
furi_check(registry);
furi_check(name);
furi_check(callback);
// the shell always attaches the pipe to the stdio, thus both flags can't be used at once
if(flags & CliCommandFlagUseShellThread) furi_check(!(flags & CliCommandFlagDontAttachStdio));
FuriString* name_str;
name_str = furi_string_alloc_set(name);
// command cannot contain spaces
furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE);
CliRegistryCommand command = {
.context = context,
.execute_callback = callback,
.flags = flags,
.stack_depth = stack_size,
};
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
CliCommandDict_set_at(registry->commands, name_str, command);
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
furi_string_free(name_str);
}
void cli_registry_delete_command(CliRegistry* registry, const char* name) {
furi_check(registry);
FuriString* name_str;
name_str = furi_string_alloc_set(name);
furi_string_trim(name_str);
size_t name_replace;
do {
name_replace = furi_string_replace(name_str, " ", "_");
} while(name_replace != FURI_STRING_FAILURE);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
CliCommandDict_erase(registry->commands, name_str);
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
furi_string_free(name_str);
}
bool cli_registry_get_command(
CliRegistry* registry,
FuriString* command,
CliRegistryCommand* result) {
furi_assert(registry);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
CliRegistryCommand* data = CliCommandDict_get(registry->commands, command);
if(data) *result = *data;
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
return !!data;
}
void cli_registry_remove_external_commands(CliRegistry* registry) {
furi_check(registry);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
CliCommandDict_t internal_cmds;
CliCommandDict_init(internal_cmds);
for
M_EACH(item, registry->commands, CliCommandDict_t) {
if(!(item->value.flags & CliCommandFlagExternal))
CliCommandDict_set_at(internal_cmds, item->key, item->value);
}
CliCommandDict_move(registry->commands, internal_cmds);
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
}
void cli_registry_reload_external_commands(
CliRegistry* registry,
const CliCommandExternalConfig* config) {
furi_check(registry);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
FURI_LOG_D(TAG, "Reloading ext commands");
cli_registry_remove_external_commands(registry);
// iterate over files in plugin directory
Storage* storage = furi_record_open(RECORD_STORAGE);
File* plugin_dir = storage_file_alloc(storage);
if(storage_dir_open(plugin_dir, config->search_directory)) {
char plugin_filename[64];
FuriString* plugin_name = furi_string_alloc();
while(storage_dir_read(plugin_dir, NULL, plugin_filename, sizeof(plugin_filename))) {
FURI_LOG_T(TAG, "Plugin: %s", plugin_filename);
furi_string_set_str(plugin_name, plugin_filename);
furi_check(furi_string_end_with_str(plugin_name, ".fal"));
furi_string_replace_all_str(plugin_name, ".fal", "");
furi_check(furi_string_start_with_str(plugin_name, config->fal_prefix));
furi_string_replace_at(plugin_name, 0, strlen(config->fal_prefix), "");
CliRegistryCommand command = {
.context = NULL,
.execute_callback = NULL,
.flags = CliCommandFlagExternal,
};
CliCommandDict_set_at(registry->commands, plugin_name, command);
}
furi_string_free(plugin_name);
}
storage_dir_close(plugin_dir);
storage_file_free(plugin_dir);
furi_record_close(RECORD_STORAGE);
FURI_LOG_D(TAG, "Done reloading ext commands");
furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk);
}
void cli_registry_lock(CliRegistry* registry) {
furi_assert(registry);
furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk);
}
void cli_registry_unlock(CliRegistry* registry) {
furi_assert(registry);
furi_mutex_release(registry->mutex);
}
CliCommandDict_t* cli_registry_get_commands(CliRegistry* registry) {
furi_assert(registry);
return &registry->commands;
}
+92
View File
@@ -0,0 +1,92 @@
/**
* @file cli_registry.h
* API for registering commands with a CLI shell
*/
#pragma once
#include <furi.h>
#include <m-array.h>
#include <toolbox/pipe.h>
#include "cli_command.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CliRegistry CliRegistry;
/**
* @brief Allocates a `CliRegistry`.
*/
CliRegistry* cli_registry_alloc(void);
/**
* @brief Frees a `CliRegistry`.
*/
void cli_registry_free(CliRegistry* registry);
/**
* @brief Registers a command with the registry. Provides less options than the
* `_ex` counterpart.
*
* @param [in] registry Pointer to registry instance
* @param [in] name Command name
* @param [in] flags see CliCommandFlag
* @param [in] callback Callback function
* @param [in] context Custom context
*/
void cli_registry_add_command(
CliRegistry* registry,
const char* name,
CliCommandFlag flags,
CliCommandExecuteCallback callback,
void* context);
/**
* @brief Registers a command with the registry. Provides more options than the
* non-`_ex` counterpart.
*
* @param [in] registry Pointer to registry instance
* @param [in] name Command name
* @param [in] flags see CliCommandFlag
* @param [in] callback Callback function
* @param [in] context Custom context
* @param [in] stack_size Thread stack size
*/
void cli_registry_add_command_ex(
CliRegistry* registry,
const char* name,
CliCommandFlag flags,
CliCommandExecuteCallback callback,
void* context,
size_t stack_size);
/**
* @brief Deletes a cli command
*
* @param [in] registry Pointer to registry instance
* @param [in] name Command name
*/
void cli_registry_delete_command(CliRegistry* registry, const char* name);
/**
* @brief Unregisters all external commands
*
* @param [in] registry Pointer to registry instance
*/
void cli_registry_remove_external_commands(CliRegistry* registry);
/**
* @brief Reloads the list of externally available commands
*
* @param [in] registry Pointer to registry instance
* @param [in] config See `CliCommandExternalConfig`
*/
void cli_registry_reload_external_commands(
CliRegistry* registry,
const CliCommandExternalConfig* config);
#ifdef __cplusplus
}
#endif
+45
View File
@@ -0,0 +1,45 @@
/**
* @file cli_registry_i.h
* Internal API for getting commands registered with the CLI
*/
#pragma once
#include <furi.h>
#include <m-dict.h>
#include "cli_registry.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CLI_BUILTIN_COMMAND_STACK_SIZE (4 * 1024U)
typedef struct {
void* context; //<! Context passed to callbacks
CliCommandExecuteCallback execute_callback; //<! Callback for command execution
CliCommandFlag flags;
size_t stack_depth;
} CliRegistryCommand;
DICT_DEF2(CliCommandDict, FuriString*, FURI_STRING_OPLIST, CliRegistryCommand, M_POD_OPLIST);
#define M_OPL_CliCommandDict_t() DICT_OPLIST(CliCommandDict, FURI_STRING_OPLIST, M_POD_OPLIST)
bool cli_registry_get_command(
CliRegistry* registry,
FuriString* command,
CliRegistryCommand* result);
void cli_registry_lock(CliRegistry* registry);
void cli_registry_unlock(CliRegistry* registry);
/**
* @warning Surround calls to this function with `cli_registry_[un]lock`
*/
CliCommandDict_t* cli_registry_get_commands(CliRegistry* registry);
#ifdef __cplusplus
}
#endif
+485
View File
@@ -0,0 +1,485 @@
#include "cli_shell.h"
#include "cli_shell_i.h"
#include "../cli_ansi.h"
#include "../cli_registry_i.h"
#include "../cli_command.h"
#include "cli_shell_line.h"
#include "cli_shell_completions.h"
#include <stdio.h>
#include <furi_hal_version.h>
#include <m-array.h>
#include <loader/loader.h>
#include <toolbox/pipe.h>
#include <flipper_application/plugins/plugin_manager.h>
#include <loader/firmware_api/firmware_api.h>
#include <storage/storage.h>
#define TAG "CliShell"
#define ANSI_TIMEOUT_MS 10
typedef enum {
CliShellComponentCompletions,
CliShellComponentLine,
CliShellComponentMAX, //<! do not use
} CliShellComponent;
CliShellKeyComboSet* component_key_combo_sets[] = {
[CliShellComponentCompletions] = &cli_shell_completions_key_combo_set,
[CliShellComponentLine] = &cli_shell_line_key_combo_set,
};
static_assert(CliShellComponentMAX == COUNT_OF(component_key_combo_sets));
typedef enum {
CliShellStorageEventMount = (1 << 0),
CliShellStorageEventUnmount = (1 << 1),
} CliShellStorageEvent;
#define CliShellStorageEventAll (CliShellStorageEventMount | CliShellStorageEventUnmount)
typedef struct {
Storage* storage;
FuriPubSubSubscription* subscription;
FuriEventFlag* event_flag;
} CliShellStorage;
struct CliShell {
// Set and freed by external thread
CliShellMotd motd;
void* callback_context;
PipeSide* pipe;
CliRegistry* registry;
const CliCommandExternalConfig* ext_config;
FuriThread* thread;
const char* prompt;
// Set and freed by shell thread
FuriEventLoop* event_loop;
CliAnsiParser* ansi_parser;
FuriEventLoopTimer* ansi_parsing_timer;
CliShellStorage storage;
void* components[CliShellComponentMAX];
};
typedef struct {
CliRegistryCommand* command;
PipeSide* pipe;
FuriString* args;
} CliCommandThreadData;
static void cli_shell_data_available(PipeSide* pipe, void* context);
static void cli_shell_pipe_broken(PipeSide* pipe, void* context);
static void cli_shell_install_pipe(CliShell* cli_shell) {
pipe_install_as_stdio(cli_shell->pipe);
pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop);
pipe_set_callback_context(cli_shell->pipe, cli_shell);
pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0);
pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0);
}
static void cli_shell_detach_pipe(CliShell* cli_shell) {
pipe_detach_from_event_loop(cli_shell->pipe);
furi_thread_set_stdin_callback(NULL, NULL);
furi_thread_set_stdout_callback(NULL, NULL);
}
// =================
// Built-in commands
// =================
void cli_command_reload_external(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
CliShell* shell = context;
furi_check(shell->ext_config);
cli_registry_reload_external_commands(shell->registry, shell->ext_config);
printf("OK!");
}
void cli_command_help(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
CliShell* shell = context;
CliRegistry* registry = shell->registry;
const size_t columns = 3;
printf("Available commands:\r\n" ANSI_FG_GREEN);
cli_registry_lock(registry);
CliCommandDict_t* commands = cli_registry_get_commands(registry);
size_t commands_count = CliCommandDict_size(*commands);
CliCommandDict_it_t iterator;
CliCommandDict_it(iterator, *commands);
for(size_t i = 0; i < commands_count; i++) {
const CliCommandDict_itref_t* item = CliCommandDict_cref(iterator);
printf("%-30s", furi_string_get_cstr(item->key));
CliCommandDict_next(iterator);
if(i % columns == columns - 1) printf("\r\n");
}
if(shell->ext_config)
printf(
ANSI_RESET
"\r\nIf you added a new external command and can't see it above, run `reload_ext_cmds`");
printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli");
cli_registry_unlock(registry);
}
void cli_command_exit(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
UNUSED(args);
CliShell* shell = context;
cli_shell_line_set_about_to_exit(shell->components[CliShellComponentLine]);
furi_event_loop_stop(shell->event_loop);
}
// ==================
// Internal functions
// ==================
static int32_t cli_command_thread(void* context) {
CliCommandThreadData* thread_data = context;
if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio))
pipe_install_as_stdio(thread_data->pipe);
thread_data->command->execute_callback(
thread_data->pipe, thread_data->args, thread_data->command->context);
fflush(stdout);
return 0;
}
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) {
// split command into command and args
size_t space = furi_string_search_char(command, ' ');
if(space == FURI_STRING_FAILURE) space = furi_string_size(command);
FuriString* command_name = furi_string_alloc_set(command);
furi_string_left(command_name, space);
FuriString* args = furi_string_alloc_set(command);
furi_string_right(args, space + 1);
PluginManager* plugin_manager = NULL;
Loader* loader = furi_record_open(RECORD_LOADER);
bool loader_locked = false;
CliRegistryCommand command_data;
do {
// find handler
if(!cli_registry_get_command(cli_shell->registry, command_name, &command_data)) {
printf(
ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET,
furi_string_get_cstr(command_name));
break;
}
// load external command
if(command_data.flags & CliCommandFlagExternal) {
const CliCommandExternalConfig* ext_config = cli_shell->ext_config;
plugin_manager = plugin_manager_alloc(
ext_config->appid, CLI_PLUGIN_API_VERSION, firmware_api_interface);
FuriString* path = furi_string_alloc_printf(
"%s/%s%s.fal",
ext_config->search_directory,
ext_config->fal_prefix,
furi_string_get_cstr(command_name));
uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager);
PluginManagerError error =
plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path));
furi_string_free(path);
if(error != PluginManagerErrorNone) {
printf(ANSI_FG_RED "failed to load external command" ANSI_RESET);
break;
}
const CliCommandDescriptor* plugin =
plugin_manager_get_ep(plugin_manager, plugin_cnt_last);
furi_assert(plugin);
furi_check(furi_string_cmp_str(command_name, plugin->name) == 0);
command_data.execute_callback = plugin->execute_callback;
command_data.flags = plugin->flags | CliCommandFlagExternal;
command_data.stack_depth = plugin->stack_depth;
// external commands have to run in an external thread
furi_check(!(command_data.flags & CliCommandFlagUseShellThread));
}
// lock loader
if(!(command_data.flags & CliCommandFlagParallelSafe)) {
loader_locked = loader_lock(loader);
if(!loader_locked) {
printf(ANSI_FG_RED
"this command cannot be run while an application is open" ANSI_RESET);
break;
}
}
if(command_data.flags & CliCommandFlagUseShellThread) {
// run command in this thread
command_data.execute_callback(cli_shell->pipe, args, command_data.context);
} else {
// run command in separate thread
cli_shell_detach_pipe(cli_shell);
CliCommandThreadData thread_data = {
.command = &command_data,
.pipe = cli_shell->pipe,
.args = args,
};
FuriThread* thread = furi_thread_alloc_ex(
furi_string_get_cstr(command_name),
command_data.stack_depth,
cli_command_thread,
&thread_data);
furi_thread_start(thread);
furi_thread_join(thread);
furi_thread_free(thread);
cli_shell_install_pipe(cli_shell);
}
} while(0);
furi_string_free(command_name);
furi_string_free(args);
// unlock loader
if(loader_locked) loader_unlock(loader);
furi_record_close(RECORD_LOADER);
// unload external command
if(plugin_manager) plugin_manager_free(plugin_manager);
}
const char* cli_shell_get_prompt(CliShell* cli_shell) {
return cli_shell->prompt;
}
// ==============
// Event handlers
// ==============
static void cli_shell_signal_storage_event(CliShell* cli_shell, CliShellStorageEvent event) {
furi_check(!(furi_event_flag_set(cli_shell->storage.event_flag, event) & FuriFlagError));
}
static void cli_shell_storage_event(const void* message, void* context) {
CliShell* cli_shell = context;
const StorageEvent* event = message;
if(event->type == StorageEventTypeCardMount) {
cli_shell_signal_storage_event(cli_shell, CliShellStorageEventMount);
} else if(event->type == StorageEventTypeCardUnmount) {
cli_shell_signal_storage_event(cli_shell, CliShellStorageEventUnmount);
}
}
static void cli_shell_storage_internal_event(FuriEventLoopObject* object, void* context) {
CliShell* cli_shell = context;
FuriEventFlag* event_flag = object;
CliShellStorageEvent event =
furi_event_flag_wait(event_flag, FuriFlagWaitAll, FuriFlagWaitAny, 0);
furi_check(!(event & FuriFlagError));
if(event & CliShellStorageEventUnmount) {
cli_registry_remove_external_commands(cli_shell->registry);
} else if(event & CliShellStorageEventMount) {
cli_registry_reload_external_commands(cli_shell->registry, cli_shell->ext_config);
} else {
furi_crash();
}
}
static void
cli_shell_process_parser_result(CliShell* cli_shell, CliAnsiParserResult parse_result) {
if(!parse_result.is_done) return;
CliKeyCombo key_combo = parse_result.result;
if(key_combo.key == CliKeyUnrecognized) return;
for(size_t i = 0; i < CliShellComponentMAX; i++) { // -V1008
CliShellKeyComboSet* set = component_key_combo_sets[i];
void* component_context = cli_shell->components[i];
for(size_t j = 0; j < set->count; j++) {
if(set->records[j].combo.modifiers == key_combo.modifiers &&
set->records[j].combo.key == key_combo.key)
if(set->records[j].action(key_combo, component_context)) return;
}
if(set->fallback)
if(set->fallback(key_combo, component_context)) return;
}
}
static void cli_shell_pipe_broken(PipeSide* pipe, void* context) {
// allow commands to be processed before we stop the shell
if(pipe_bytes_available(pipe)) return;
CliShell* cli_shell = context;
furi_event_loop_stop(cli_shell->event_loop);
}
static void cli_shell_data_available(PipeSide* pipe, void* context) {
UNUSED(pipe);
CliShell* cli_shell = context;
furi_event_loop_timer_start(cli_shell->ansi_parsing_timer, furi_ms_to_ticks(ANSI_TIMEOUT_MS));
// process ANSI escape sequences
int c = getchar();
furi_assert(c >= 0);
cli_shell_process_parser_result(cli_shell, cli_ansi_parser_feed(cli_shell->ansi_parser, c));
}
static void cli_shell_timer_expired(void* context) {
CliShell* cli_shell = context;
cli_shell_process_parser_result(
cli_shell, cli_ansi_parser_feed_timeout(cli_shell->ansi_parser));
}
// ===========
// Thread code
// ===========
static void cli_shell_init(CliShell* shell) {
cli_registry_add_command(
shell->registry,
"help",
CliCommandFlagUseShellThread | CliCommandFlagParallelSafe,
cli_command_help,
shell);
cli_registry_add_command(
shell->registry,
"?",
CliCommandFlagUseShellThread | CliCommandFlagParallelSafe,
cli_command_help,
shell);
cli_registry_add_command(
shell->registry,
"exit",
CliCommandFlagUseShellThread | CliCommandFlagParallelSafe,
cli_command_exit,
shell);
if(shell->ext_config) {
cli_registry_add_command(
shell->registry,
"reload_ext_cmds",
CliCommandFlagUseShellThread,
cli_command_reload_external,
shell);
cli_registry_reload_external_commands(shell->registry, shell->ext_config);
}
shell->components[CliShellComponentLine] = cli_shell_line_alloc(shell);
shell->components[CliShellComponentCompletions] = cli_shell_completions_alloc(
shell->registry, shell, shell->components[CliShellComponentLine]);
shell->ansi_parser = cli_ansi_parser_alloc();
shell->event_loop = furi_event_loop_alloc();
shell->ansi_parsing_timer = furi_event_loop_timer_alloc(
shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, shell);
shell->storage.event_flag = furi_event_flag_alloc();
furi_event_loop_subscribe_event_flag(
shell->event_loop,
shell->storage.event_flag,
FuriEventLoopEventIn,
cli_shell_storage_internal_event,
shell);
shell->storage.storage = furi_record_open(RECORD_STORAGE);
shell->storage.subscription = furi_pubsub_subscribe(
storage_get_pubsub(shell->storage.storage), cli_shell_storage_event, shell);
cli_shell_install_pipe(shell);
}
static void cli_shell_deinit(CliShell* shell) {
furi_pubsub_unsubscribe(
storage_get_pubsub(shell->storage.storage), shell->storage.subscription);
furi_record_close(RECORD_STORAGE);
furi_event_loop_unsubscribe(shell->event_loop, shell->storage.event_flag);
furi_event_flag_free(shell->storage.event_flag);
cli_shell_completions_free(shell->components[CliShellComponentCompletions]);
cli_shell_line_free(shell->components[CliShellComponentLine]);
cli_shell_detach_pipe(shell);
furi_event_loop_timer_free(shell->ansi_parsing_timer);
furi_event_loop_free(shell->event_loop);
cli_ansi_parser_free(shell->ansi_parser);
}
static int32_t cli_shell_thread(void* context) {
CliShell* shell = context;
// Sometimes, the other side closes the pipe even before our thread is started. Although the
// rest of the code will eventually find this out if this check is removed, there's no point in
// wasting time.
if(pipe_state(shell->pipe) == PipeStateBroken) return 0;
cli_shell_init(shell);
FURI_LOG_D(TAG, "Started");
shell->motd(shell->callback_context);
cli_shell_line_prompt(shell->components[CliShellComponentLine]);
furi_event_loop_run(shell->event_loop);
FURI_LOG_D(TAG, "Stopped");
cli_shell_deinit(shell);
return 0;
}
// ==========
// Public API
// ==========
CliShell* cli_shell_alloc(
CliShellMotd motd,
void* context,
PipeSide* pipe,
CliRegistry* registry,
const CliCommandExternalConfig* ext_config) {
furi_check(motd);
furi_check(pipe);
furi_check(registry);
CliShell* shell = malloc(sizeof(CliShell));
*shell = (CliShell){
.motd = motd,
.callback_context = context,
.pipe = pipe,
.registry = registry,
.ext_config = ext_config,
};
shell->thread =
furi_thread_alloc_ex("CliShell", CLI_SHELL_STACK_SIZE, cli_shell_thread, shell);
return shell;
}
void cli_shell_free(CliShell* shell) {
furi_check(shell);
furi_thread_free(shell->thread);
free(shell);
}
void cli_shell_start(CliShell* shell) {
furi_check(shell);
furi_thread_start(shell->thread);
}
void cli_shell_join(CliShell* shell) {
furi_check(shell);
furi_thread_join(shell->thread);
}
void cli_shell_set_prompt(CliShell* shell, const char* prompt) {
furi_check(shell);
furi_check(furi_thread_get_state(shell->thread) == FuriThreadStateStopped);
shell->prompt = prompt;
}
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#include <furi.h>
#include <toolbox/pipe.h>
#include "../cli_registry.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CLI_SHELL_STACK_SIZE (4 * 1024U)
typedef struct CliShell CliShell;
/**
* Called from the shell thread to print the Message of the Day when the shell
* is started.
*/
typedef void (*CliShellMotd)(void* context);
/**
* @brief Allocates a shell
*
* @param [in] motd Message of the Day callback
* @param [in] context Callback context
* @param [in] pipe Pipe side to be used by the shell
* @param [in] registry Command registry
* @param [in] ext_config External command configuration. See
* `CliCommandExternalConfig`. May be NULL if support for
* external commands is not required.
*
* @return Shell instance
*/
CliShell* cli_shell_alloc(
CliShellMotd motd,
void* context,
PipeSide* pipe,
CliRegistry* registry,
const CliCommandExternalConfig* ext_config);
/**
* @brief Frees a shell
*
* @param [in] shell Shell instance
*/
void cli_shell_free(CliShell* shell);
/**
* @brief Starts a shell
*
* The shell runs in a separate thread. This call is non-blocking.
*
* @param [in] shell Shell instance
*/
void cli_shell_start(CliShell* shell);
/**
* @brief Joins the shell thread
*
* @warning This call is blocking.
*
* @param [in] shell Shell instance
*/
void cli_shell_join(CliShell* shell);
/**
* @brief Sets optional text before prompt (`>:`)
*
* @param [in] shell Shell instance
*/
void cli_shell_set_prompt(CliShell* shell, const char* prompt);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,367 @@
#include "cli_shell_completions.h"
ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524
#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions)
struct CliShellCompletions {
CliRegistry* registry;
CliShell* shell;
CliShellLine* line;
CommandCompletions_t variants;
size_t selected;
bool is_displaying;
};
#define COMPLETION_COLUMNS 3
#define COMPLETION_COLUMN_WIDTH "30"
#define COMPLETION_COLUMN_WIDTH_I 30
/**
* @brief Update for the completions menu
*/
typedef enum {
CliShellCompletionsActionOpen,
CliShellCompletionsActionClose,
CliShellCompletionsActionUp,
CliShellCompletionsActionDown,
CliShellCompletionsActionLeft,
CliShellCompletionsActionRight,
CliShellCompletionsActionSelect,
CliShellCompletionsActionSelectNoClose,
} CliShellCompletionsAction;
typedef enum {
CliShellCompletionSegmentTypeCommand,
CliShellCompletionSegmentTypeArguments,
} CliShellCompletionSegmentType;
typedef struct {
CliShellCompletionSegmentType type;
size_t start;
size_t length;
} CliShellCompletionSegment;
// ==========
// Public API
// ==========
CliShellCompletions*
cli_shell_completions_alloc(CliRegistry* registry, CliShell* shell, CliShellLine* line) {
CliShellCompletions* completions = malloc(sizeof(CliShellCompletions));
completions->registry = registry;
completions->shell = shell;
completions->line = line;
CommandCompletions_init(completions->variants);
return completions;
}
void cli_shell_completions_free(CliShellCompletions* completions) {
CommandCompletions_clear(completions->variants);
free(completions);
}
// =======
// Helpers
// =======
CliShellCompletionSegment cli_shell_completions_segment(CliShellCompletions* completions) {
furi_assert(completions);
CliShellCompletionSegment segment;
FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(completions->line));
furi_string_left(input, cli_shell_line_get_line_position(completions->line));
// find index of first non-space character
size_t first_non_space = 0;
while(1) {
size_t ret = furi_string_search_char(input, ' ', first_non_space);
if(ret == FURI_STRING_FAILURE) break;
if(ret - first_non_space > 1) break;
first_non_space++;
}
size_t first_space_in_command = furi_string_search_char(input, ' ', first_non_space);
if(first_space_in_command == FURI_STRING_FAILURE) {
segment.type = CliShellCompletionSegmentTypeCommand;
segment.start = first_non_space;
segment.length = furi_string_size(input) - first_non_space;
} else {
segment.type = CliShellCompletionSegmentTypeArguments;
segment.start = 0;
segment.length = 0;
// support removed, might reimplement in the future
}
furi_string_free(input);
return segment;
}
void cli_shell_completions_fill_variants(CliShellCompletions* completions) {
furi_assert(completions);
CommandCompletions_reset(completions->variants);
CliShellCompletionSegment segment = cli_shell_completions_segment(completions);
FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(completions->line));
furi_string_right(input, segment.start);
furi_string_left(input, segment.length);
if(segment.type == CliShellCompletionSegmentTypeCommand) {
CliRegistry* registry = completions->registry;
cli_registry_lock(registry);
CliCommandDict_t* commands = cli_registry_get_commands(registry);
for
M_EACH(registered_command, *commands, CliCommandDict_t) {
FuriString* command_name = registered_command->key;
if(furi_string_start_with(command_name, input)) {
CommandCompletions_push_back(completions->variants, command_name);
}
}
cli_registry_unlock(registry);
} else {
// support removed, might reimplement in the future
}
furi_string_free(input);
}
static size_t cli_shell_completions_rows_at_column(CliShellCompletions* completions, size_t x) {
size_t completions_size = CommandCompletions_size(completions->variants);
size_t n_full_rows = completions_size / COMPLETION_COLUMNS;
size_t n_cols_in_last_row = completions_size % COMPLETION_COLUMNS;
size_t n_rows_at_x = n_full_rows + ((x >= n_cols_in_last_row) ? 0 : 1);
return n_rows_at_x;
}
void cli_shell_completions_render(
CliShellCompletions* completions,
CliShellCompletionsAction action) {
furi_assert(completions);
if(action == CliShellCompletionsActionOpen) furi_check(!completions->is_displaying);
if(action == CliShellCompletionsActionClose) furi_check(completions->is_displaying);
char prompt[64];
cli_shell_line_format_prompt(completions->line, prompt, sizeof(prompt));
if(action == CliShellCompletionsActionOpen) {
cli_shell_completions_fill_variants(completions);
completions->selected = 0;
if(CommandCompletions_size(completions->variants) == 1) {
cli_shell_completions_render(completions, CliShellCompletionsActionSelectNoClose);
return;
}
// show completions menu (full re-render)
printf("\n\r");
size_t position = 0;
for
M_EACH(completion, completions->variants, CommandCompletions_t) {
if(position == completions->selected) printf(ANSI_INVERT);
printf("%-" COMPLETION_COLUMN_WIDTH "s", furi_string_get_cstr(*completion));
if(position == completions->selected) printf(ANSI_RESET);
if((position % COMPLETION_COLUMNS == COMPLETION_COLUMNS - 1) &&
position != CommandCompletions_size(completions->variants)) {
printf("\r\n");
}
position++;
}
if(!position) {
printf(ANSI_FG_RED "no completions" ANSI_RESET);
}
size_t total_rows = (position / COMPLETION_COLUMNS) + 1;
printf(
ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) ANSI_CURSOR_UP_BY("%zu")
ANSI_CURSOR_HOR_POS("%zu"),
total_rows,
strlen(prompt) + cli_shell_line_get_line_position(completions->line) + 1);
completions->is_displaying = true;
} else if(action == CliShellCompletionsActionClose) {
// clear completions menu
printf(
ANSI_CURSOR_HOR_POS("%zu") ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END)
ANSI_CURSOR_HOR_POS("%zu"),
strlen(prompt) + furi_string_size(cli_shell_line_get_selected(completions->line)) + 1,
strlen(prompt) + cli_shell_line_get_line_position(completions->line) + 1);
completions->is_displaying = false;
} else if(
action == CliShellCompletionsActionUp || action == CliShellCompletionsActionDown ||
action == CliShellCompletionsActionLeft || action == CliShellCompletionsActionRight) {
if(CommandCompletions_empty_p(completions->variants)) return;
// move selection
size_t completions_size = CommandCompletions_size(completions->variants);
size_t old_selection = completions->selected;
int n_columns = (completions_size >= COMPLETION_COLUMNS) ? COMPLETION_COLUMNS :
completions_size;
int selection_unclamped = old_selection;
if(action == CliShellCompletionsActionLeft) {
selection_unclamped--;
} else if(action == CliShellCompletionsActionRight) {
selection_unclamped++;
} else {
int selection_x = old_selection % COMPLETION_COLUMNS;
int selection_y_unclamped = old_selection / COMPLETION_COLUMNS;
if(action == CliShellCompletionsActionUp) selection_y_unclamped--;
if(action == CliShellCompletionsActionDown) selection_y_unclamped++;
size_t selection_y = 0;
if(selection_y_unclamped < 0) {
selection_x = CLAMP_WRAPAROUND(selection_x - 1, n_columns - 1, 0);
selection_y =
cli_shell_completions_rows_at_column(completions, selection_x) - 1; // -V537
} else if(
(size_t)selection_y_unclamped >
cli_shell_completions_rows_at_column(completions, selection_x) - 1) {
selection_x = CLAMP_WRAPAROUND(selection_x + 1, n_columns - 1, 0);
selection_y = 0;
} else {
selection_y = selection_y_unclamped;
}
selection_unclamped = (selection_y * COMPLETION_COLUMNS) + selection_x;
}
size_t new_selection = CLAMP_WRAPAROUND(selection_unclamped, (int)completions_size - 1, 0);
completions->selected = new_selection;
if(new_selection != old_selection) {
// determine selection coordinates relative to top-left of suggestion menu
size_t old_x = (old_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I;
size_t old_y = old_selection / COMPLETION_COLUMNS;
size_t new_x = (new_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I;
size_t new_y = new_selection / COMPLETION_COLUMNS;
printf("\n\r");
// print old selection in normal colors
if(old_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), old_y);
printf(ANSI_CURSOR_HOR_POS("%zu"), old_x + 1);
printf(
"%-" COMPLETION_COLUMN_WIDTH "s",
furi_string_get_cstr(
*CommandCompletions_cget(completions->variants, old_selection)));
if(old_y) printf(ANSI_CURSOR_UP_BY("%zu"), old_y);
printf(ANSI_CURSOR_HOR_POS("1"));
// print new selection in inverted colors
if(new_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), new_y);
printf(ANSI_CURSOR_HOR_POS("%zu"), new_x + 1);
printf(
ANSI_INVERT "%-" COMPLETION_COLUMN_WIDTH "s" ANSI_RESET,
furi_string_get_cstr(
*CommandCompletions_cget(completions->variants, new_selection)));
// return cursor
printf(ANSI_CURSOR_UP_BY("%zu"), new_y + 1);
printf(
ANSI_CURSOR_HOR_POS("%zu"),
strlen(prompt) + furi_string_size(cli_shell_line_get_selected(completions->line)) +
1);
}
} else if(action == CliShellCompletionsActionSelectNoClose) {
if(!CommandCompletions_size(completions->variants)) return;
// insert selection into prompt
CliShellCompletionSegment segment = cli_shell_completions_segment(completions);
FuriString* input = cli_shell_line_get_selected(completions->line);
FuriString* completion =
*CommandCompletions_cget(completions->variants, completions->selected);
furi_string_replace_at(
input, segment.start, segment.length, furi_string_get_cstr(completion));
printf(
ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
strlen(prompt) + 1,
furi_string_get_cstr(input));
int position_change = (int)furi_string_size(completion) - (int)segment.length;
cli_shell_line_set_line_position(
completions->line,
MAX(0, (int)cli_shell_line_get_line_position(completions->line) + position_change));
} else if(action == CliShellCompletionsActionSelect) {
cli_shell_completions_render(completions, CliShellCompletionsActionSelectNoClose);
cli_shell_completions_render(completions, CliShellCompletionsActionClose);
} else {
furi_crash();
}
fflush(stdout);
}
// ==============
// Input handlers
// ==============
static bool hide_if_open_and_continue_handling(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellCompletions* completions = context;
if(completions->is_displaying)
cli_shell_completions_render(completions, CliShellCompletionsActionClose);
return false; // process other home events
}
static bool key_combo_cr(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellCompletions* completions = context;
if(!completions->is_displaying) return false;
cli_shell_completions_render(completions, CliShellCompletionsActionSelect);
return true;
}
static bool key_combo_up_down(CliKeyCombo combo, void* context) {
CliShellCompletions* completions = context;
if(!completions->is_displaying) return false;
cli_shell_completions_render(
completions,
(combo.key == CliKeyUp) ? CliShellCompletionsActionUp : CliShellCompletionsActionDown);
return true;
}
static bool key_combo_left_right(CliKeyCombo combo, void* context) {
CliShellCompletions* completions = context;
if(!completions->is_displaying) return false;
cli_shell_completions_render(
completions,
(combo.key == CliKeyLeft) ? CliShellCompletionsActionLeft :
CliShellCompletionsActionRight);
return true;
}
static bool key_combo_tab(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellCompletions* completions = context;
cli_shell_completions_render(
completions,
completions->is_displaying ? CliShellCompletionsActionRight :
CliShellCompletionsActionOpen);
return true;
}
static bool key_combo_esc(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellCompletions* completions = context;
if(!completions->is_displaying) return false;
cli_shell_completions_render(completions, CliShellCompletionsActionClose);
return true;
}
CliShellKeyComboSet cli_shell_completions_key_combo_set = {
.fallback = hide_if_open_and_continue_handling,
.count = 7,
.records =
{
{{CliModKeyNo, CliKeyCR}, key_combo_cr},
{{CliModKeyNo, CliKeyUp}, key_combo_up_down},
{{CliModKeyNo, CliKeyDown}, key_combo_up_down},
{{CliModKeyNo, CliKeyLeft}, key_combo_left_right},
{{CliModKeyNo, CliKeyRight}, key_combo_left_right},
{{CliModKeyNo, CliKeyTab}, key_combo_tab},
{{CliModKeyNo, CliKeyEsc}, key_combo_esc},
},
};
@@ -0,0 +1,25 @@
#pragma once
#include <furi.h>
#include <m-array.h>
#include "cli_shell_i.h"
#include "cli_shell_line.h"
#include "../cli_registry.h"
#include "../cli_registry_i.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CliShellCompletions CliShellCompletions;
CliShellCompletions*
cli_shell_completions_alloc(CliRegistry* registry, CliShell* shell, CliShellLine* line);
void cli_shell_completions_free(CliShellCompletions* completions);
extern CliShellKeyComboSet cli_shell_completions_key_combo_set;
#ifdef __cplusplus
}
#endif
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "../cli_ansi.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CliShell CliShell;
/**
* @brief Key combo handler
* @return true if the event was handled, false otherwise
*/
typedef bool (*CliShellKeyComboAction)(CliKeyCombo combo, void* context);
typedef struct {
CliKeyCombo combo;
CliShellKeyComboAction action;
} CliShellKeyComboRecord;
typedef struct {
CliShellKeyComboAction fallback;
size_t count;
CliShellKeyComboRecord records[];
} CliShellKeyComboSet;
void cli_shell_execute_command(CliShell* cli_shell, FuriString* command);
const char* cli_shell_get_prompt(CliShell* cli_shell);
#ifdef __cplusplus
}
#endif
+378
View File
@@ -0,0 +1,378 @@
#include "cli_shell_line.h"
#define HISTORY_DEPTH 10
struct CliShellLine {
size_t history_position;
size_t line_position;
FuriString* history[HISTORY_DEPTH];
size_t history_entries;
CliShell* shell;
bool about_to_exit;
};
// ==========
// Public API
// ==========
CliShellLine* cli_shell_line_alloc(CliShell* shell) {
CliShellLine* line = malloc(sizeof(CliShellLine));
line->shell = shell;
line->history[0] = furi_string_alloc();
line->history_entries = 1;
return line;
}
void cli_shell_line_free(CliShellLine* line) {
for(size_t i = 0; i < line->history_entries; i++)
furi_string_free(line->history[i]);
free(line);
}
FuriString* cli_shell_line_get_selected(CliShellLine* line) {
return line->history[line->history_position];
}
FuriString* cli_shell_line_get_editing(CliShellLine* line) {
return line->history[0];
}
void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length) {
UNUSED(line);
const char* prompt = cli_shell_get_prompt(line->shell);
snprintf(buf, length - 1, "%s>: ", prompt ? prompt : "");
}
size_t cli_shell_line_prompt_length(CliShellLine* line) {
char buffer[128];
cli_shell_line_format_prompt(line, buffer, sizeof(buffer));
return strlen(buffer);
}
void cli_shell_line_prompt(CliShellLine* line) {
char buffer[32];
cli_shell_line_format_prompt(line, buffer, sizeof(buffer));
printf("\r\n%s", buffer);
fflush(stdout);
}
void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line) {
if(line->history_position > 0) {
FuriString* source = cli_shell_line_get_selected(line);
FuriString* destination = cli_shell_line_get_editing(line);
furi_string_set(destination, source);
line->history_position = 0;
}
}
void cli_shell_line_set_about_to_exit(CliShellLine* line) {
line->about_to_exit = true;
}
size_t cli_shell_line_get_line_position(CliShellLine* line) {
return line->line_position;
}
void cli_shell_line_set_line_position(CliShellLine* line, size_t position) {
line->line_position = position;
}
// =======
// Helpers
// =======
typedef enum {
CliCharClassWord,
CliCharClassSpace,
CliCharClassOther,
} CliCharClass;
typedef enum {
CliSkipDirectionLeft,
CliSkipDirectionRight,
} CliSkipDirection;
CliCharClass cli_shell_line_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;
}
}
size_t
cli_shell_line_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_shell_line_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_shell_line_char_class(furi_string_get_char(string, position + look_offset)) !=
start_class)
break;
}
return MAX(0, position);
}
// ==============
// Input handlers
// ==============
static bool cli_shell_line_input_ctrl_c(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// reset input
furi_string_reset(cli_shell_line_get_editing(line));
line->line_position = 0;
line->history_position = 0;
printf("^C");
cli_shell_line_prompt(line);
return true;
}
static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
FuriString* command = cli_shell_line_get_selected(line);
furi_string_trim(command);
if(furi_string_empty(command)) {
cli_shell_line_prompt(line);
return true;
}
FuriString* command_copy = furi_string_alloc_set(command);
if(line->history_position == 0) {
for(size_t i = 1; i < line->history_entries; i++) {
if(furi_string_cmp(line->history[i], command) == 0) {
line->history_position = i;
command = cli_shell_line_get_selected(line);
furi_string_trim(command);
break;
}
}
}
// move selected command to the front
if(line->history_position > 0) {
size_t pos = line->history_position;
size_t len = line->history_entries;
memmove(
&line->history[pos], &line->history[pos + 1], (len - pos - 1) * sizeof(FuriString*));
furi_string_move(line->history[0], command);
line->history_entries--;
}
// insert empty command
if(line->history_entries == HISTORY_DEPTH) {
furi_string_free(line->history[HISTORY_DEPTH - 1]);
line->history_entries--;
}
memmove(&line->history[1], &line->history[0], line->history_entries * sizeof(FuriString*));
line->history[0] = furi_string_alloc();
line->history_entries++;
line->line_position = 0;
line->history_position = 0;
// execute command
printf("\r\n");
cli_shell_execute_command(line->shell, command_copy);
furi_string_free(command_copy);
if(!line->about_to_exit) cli_shell_line_prompt(line);
return true;
}
static bool cli_shell_line_input_up_down(CliKeyCombo combo, void* context) {
CliShellLine* line = context;
// go up and down in history
int increment = (combo.key == CliKeyUp) ? 1 : -1;
size_t new_pos =
CLAMP((int)line->history_position + increment, (int)line->history_entries - 1, 0);
// print prompt with selected command
if(new_pos != line->history_position) {
char prompt[64];
cli_shell_line_format_prompt(line, prompt, sizeof(prompt));
line->history_position = new_pos;
FuriString* command = cli_shell_line_get_selected(line);
printf(
ANSI_CURSOR_HOR_POS("1") "%s%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
prompt,
furi_string_get_cstr(command));
fflush(stdout);
line->line_position = furi_string_size(command);
}
return true;
}
static bool cli_shell_line_input_left_right(CliKeyCombo combo, void* context) {
CliShellLine* line = context;
// go left and right in the current line
FuriString* command = cli_shell_line_get_selected(line);
int increment = (combo.key == CliKeyRight) ? 1 : -1;
size_t new_pos =
CLAMP((int)line->line_position + increment, (int)furi_string_size(command), 0);
// move cursor
if(new_pos != line->line_position) {
line->line_position = new_pos;
printf("%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1"));
fflush(stdout);
}
return true;
}
static bool cli_shell_line_input_home(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// go to the start
line->line_position = 0;
printf(ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + 1);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_end(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// go to the end
line->line_position = furi_string_size(cli_shell_line_get_selected(line));
printf(
ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_bksp(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// erase one character
cli_shell_line_ensure_not_overwriting_history(line);
FuriString* editing_line = cli_shell_line_get_editing(line);
if(line->line_position == 0) {
putc(CliKeyBell, stdout);
fflush(stdout);
return true;
}
line->line_position--;
furi_string_replace_at(editing_line, line->line_position, 1, "");
// move cursor, print the rest of the line, restore cursor
printf(
ANSI_CURSOR_LEFT_BY("1") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END),
furi_string_get_cstr(editing_line) + line->line_position);
size_t left_by = furi_string_size(editing_line) - line->line_position;
if(left_by) // apparently LEFT_BY("0") still shifts left by one ._ .
printf(ANSI_CURSOR_LEFT_BY("%zu"), left_by);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_ctrl_l(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// clear screen
FuriString* command = cli_shell_line_get_selected(line);
char prompt[64];
cli_shell_line_format_prompt(line, prompt, sizeof(prompt));
printf(
ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS(
"1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%zu"),
prompt,
furi_string_get_cstr(command),
strlen(prompt) + line->line_position + 1 /* 1-based column indexing */);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_ctrl_left_right(CliKeyCombo combo, void* context) {
CliShellLine* line = context;
// skip run of similar chars to the left or right
FuriString* selected_line = cli_shell_line_get_selected(line);
CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft :
CliSkipDirectionRight;
line->line_position = cli_shell_line_skip_run(selected_line, line->line_position, direction);
printf(
ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_ctrl_bksp(CliKeyCombo combo, void* context) {
UNUSED(combo);
CliShellLine* line = context;
// delete run of similar chars to the left
cli_shell_line_ensure_not_overwriting_history(line);
FuriString* selected_line = cli_shell_line_get_selected(line);
size_t run_start =
cli_shell_line_skip_run(selected_line, line->line_position, CliSkipDirectionLeft);
furi_string_replace_at(selected_line, run_start, line->line_position - run_start, "");
line->line_position = run_start;
printf(
ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END)
ANSI_CURSOR_HOR_POS("%zu"),
cli_shell_line_prompt_length(line) + line->line_position + 1,
furi_string_get_cstr(selected_line) + run_start,
cli_shell_line_prompt_length(line) + run_start + 1);
fflush(stdout);
return true;
}
static bool cli_shell_line_input_normal(CliKeyCombo combo, void* context) {
CliShellLine* line = context;
if(combo.modifiers != CliModKeyNo) return false;
if(combo.key < CliKeySpace || combo.key >= CliKeyDEL) return false;
// insert character
cli_shell_line_ensure_not_overwriting_history(line);
FuriString* editing_line = cli_shell_line_get_editing(line);
if(line->line_position == furi_string_size(editing_line)) {
furi_string_push_back(editing_line, combo.key);
printf("%c", combo.key);
} else {
const char in_str[2] = {combo.key, 0};
furi_string_replace_at(editing_line, line->line_position, 0, in_str);
printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, combo.key);
}
fflush(stdout);
line->line_position++;
return true;
}
CliShellKeyComboSet cli_shell_line_key_combo_set = {
.fallback = cli_shell_line_input_normal,
.count = 14,
.records =
{
{{CliModKeyNo, CliKeyETX}, cli_shell_line_input_ctrl_c},
{{CliModKeyNo, CliKeyCR}, cli_shell_line_input_cr},
{{CliModKeyNo, CliKeyUp}, cli_shell_line_input_up_down},
{{CliModKeyNo, CliKeyDown}, cli_shell_line_input_up_down},
{{CliModKeyNo, CliKeyLeft}, cli_shell_line_input_left_right},
{{CliModKeyNo, CliKeyRight}, cli_shell_line_input_left_right},
{{CliModKeyNo, CliKeyHome}, cli_shell_line_input_home},
{{CliModKeyNo, CliKeyEnd}, cli_shell_line_input_end},
{{CliModKeyNo, CliKeyBackspace}, cli_shell_line_input_bksp},
{{CliModKeyNo, CliKeyDEL}, cli_shell_line_input_bksp},
{{CliModKeyNo, CliKeyFF}, cli_shell_line_input_ctrl_l},
{{CliModKeyCtrl, CliKeyLeft}, cli_shell_line_input_ctrl_left_right},
{{CliModKeyCtrl, CliKeyRight}, cli_shell_line_input_ctrl_left_right},
{{CliModKeyNo, CliKeyETB}, cli_shell_line_input_ctrl_bksp},
},
};
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <furi.h>
#include "cli_shell_i.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CliShellLine CliShellLine;
CliShellLine* cli_shell_line_alloc(CliShell* shell);
void cli_shell_line_free(CliShellLine* line);
FuriString* cli_shell_line_get_selected(CliShellLine* line);
FuriString* cli_shell_line_get_editing(CliShellLine* line);
size_t cli_shell_line_prompt_length(CliShellLine* line);
void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length);
void cli_shell_line_prompt(CliShellLine* line);
size_t cli_shell_line_get_line_position(CliShellLine* line);
void cli_shell_line_set_line_position(CliShellLine* line, size_t position);
/**
* @brief If a line from history has been selected, moves it into the active line
*/
void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line);
void cli_shell_line_set_about_to_exit(CliShellLine* line);
extern CliShellKeyComboSet cli_shell_line_key_combo_set;
#ifdef __cplusplus
}
#endif
+47 -16
View File
@@ -1,6 +1,8 @@
#include "pipe.h"
#include <furi.h>
#define PIPE_DEFAULT_STATE_CHECK_PERIOD furi_ms_to_ticks(100)
/**
* Data shared between both sides.
*/
@@ -23,6 +25,7 @@ struct PipeSide {
PipeSideDataArrivedCallback on_data_arrived;
PipeSideSpaceFreedCallback on_space_freed;
PipeSideBrokenCallback on_pipe_broken;
FuriWait state_check_period;
};
PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level) {
@@ -52,12 +55,14 @@ PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSetti
.shared = shared,
.sending = alice_to_bob,
.receiving = bob_to_alice,
.state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD,
};
*bobs_side = (PipeSide){
.role = PipeRoleBob,
.shared = shared,
.sending = bob_to_alice,
.receiving = alice_to_bob,
.state_check_period = PIPE_DEFAULT_STATE_CHECK_PERIOD,
};
return (PipeSideBundle){.alices_side = alices_side, .bobs_side = bobs_side};
@@ -96,36 +101,62 @@ void pipe_free(PipeSide* pipe) {
}
}
static void _pipe_stdout_cb(const char* data, size_t size, void* context) {
static void pipe_stdout_cb(const char* data, size_t size, void* context) {
furi_assert(context);
PipeSide* pipe = context;
while(size) {
size_t sent = pipe_send(pipe, data, size, FuriWaitForever);
data += sent;
size -= sent;
}
pipe_send(pipe, data, size);
}
static size_t _pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) {
static size_t pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) {
UNUSED(timeout);
furi_assert(context);
PipeSide* pipe = context;
return pipe_receive(pipe, data, size, timeout);
return pipe_receive(pipe, data, size);
}
void pipe_install_as_stdio(PipeSide* pipe) {
furi_check(pipe);
furi_thread_set_stdout_callback(_pipe_stdout_cb, pipe);
furi_thread_set_stdin_callback(_pipe_stdin_cb, pipe);
furi_thread_set_stdout_callback(pipe_stdout_cb, pipe);
furi_thread_set_stdin_callback(pipe_stdin_cb, pipe);
}
size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout) {
void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period) {
furi_check(pipe);
return furi_stream_buffer_receive(pipe->receiving, data, length, timeout);
pipe->state_check_period = check_period;
}
size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout) {
size_t pipe_receive(PipeSide* pipe, void* data, size_t length) {
furi_check(pipe);
return furi_stream_buffer_send(pipe->sending, data, length, timeout);
size_t received = 0;
while(length) {
size_t received_this_time =
furi_stream_buffer_receive(pipe->receiving, data, length, pipe->state_check_period);
if(!received_this_time && pipe_state(pipe) == PipeStateBroken) break;
received += received_this_time;
length -= received_this_time;
data += received_this_time;
}
return received;
}
size_t pipe_send(PipeSide* pipe, const void* data, size_t length) {
furi_check(pipe);
size_t sent = 0;
while(length) {
size_t sent_this_time =
furi_stream_buffer_send(pipe->sending, data, length, pipe->state_check_period);
if(!sent_this_time && pipe_state(pipe) == PipeStateBroken) break;
sent += sent_this_time;
length -= sent_this_time;
data += sent_this_time;
}
return sent;
}
size_t pipe_bytes_available(PipeSide* pipe) {
@@ -142,14 +173,14 @@ static void pipe_receiving_buffer_callback(FuriEventLoopObject* buffer, void* co
UNUSED(buffer);
PipeSide* pipe = context;
furi_assert(pipe);
if(pipe->on_space_freed) pipe->on_data_arrived(pipe, pipe->callback_context);
if(pipe->on_data_arrived) pipe->on_data_arrived(pipe, pipe->callback_context);
}
static void pipe_sending_buffer_callback(FuriEventLoopObject* buffer, void* context) {
UNUSED(buffer);
PipeSide* pipe = context;
furi_assert(pipe);
if(pipe->on_data_arrived) pipe->on_space_freed(pipe, pipe->callback_context);
if(pipe->on_space_freed) pipe->on_space_freed(pipe, pipe->callback_context);
}
static void pipe_semaphore_callback(FuriEventLoopObject* semaphore, void* context) {
+26 -6
View File
@@ -147,29 +147,49 @@ void pipe_free(PipeSide* pipe);
*/
void pipe_install_as_stdio(PipeSide* pipe);
/**
* @brief Sets the state check period for `send` and `receive` operations
*
* @note This value is set to 100 ms when the pipe is created
*
* `send` and `receive` will check the state of the pipe if exactly 0 bytes were
* sent or received during any given `check_period`. Read the documentation for
* `pipe_send` and `pipe_receive` for more info.
*
* @param [in] pipe Pipe side to set the check period of
* @param [in] check_period Period in ticks
*/
void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period);
/**
* @brief Receives data from the pipe.
*
* This function will try to receive all of the requested bytes from the pipe.
* If at some point during the operation the pipe becomes broken, this function
* will return prematurely, in which case the return value will be less than the
* requested `length`.
*
* @param [in] pipe The pipe side to read data out of
* @param [out] data The buffer to fill with data
* @param length Maximum length of data to read
* @param timeout The timeout (in ticks) after which the read operation is
* interrupted
* @returns The number of bytes actually written into the provided buffer
*/
size_t pipe_receive(PipeSide* pipe, void* data, size_t length, FuriWait timeout);
size_t pipe_receive(PipeSide* pipe, void* data, size_t length);
/**
* @brief Sends data into the pipe.
*
* This function will try to send all of the requested bytes to the pipe.
* If at some point during the operation the pipe becomes broken, this function
* will return prematurely, in which case the return value will be less than the
* requested `length`.
*
* @param [in] pipe The pipe side to send data into
* @param [out] data The buffer to get data from
* @param length Maximum length of data to send
* @param timeout The timeout (in ticks) after which the write operation is
* interrupted
* @returns The number of bytes actually read from the provided buffer
*/
size_t pipe_send(PipeSide* pipe, const void* data, size_t length, FuriWait timeout);
size_t pipe_send(PipeSide* pipe, const void* data, size_t length);
/**
* @brief Determines how many bytes there are in the pipe available to be read.
+7 -1
View File
@@ -36,11 +36,17 @@ void pretty_format_bytes_hex_canonical(
}
const size_t begin_idx = i;
const size_t end_idx = MIN(i + num_places, data_size);
const size_t wrap_idx = i + num_places;
const size_t end_idx = MIN(wrap_idx, data_size);
for(size_t j = begin_idx; j < end_idx; j++) {
furi_string_cat_printf(result, "%02X ", data[j]);
}
if(end_idx < wrap_idx) {
for(size_t j = end_idx; j < wrap_idx; j++) {
furi_string_cat_printf(result, " ");
}
}
furi_string_push_back(result, '|');
+2 -2
View File
@@ -2,12 +2,12 @@
#include "protocol_dict.h"
struct ProtocolDict {
const ProtocolBase** base;
const ProtocolBase* const* base;
size_t count;
void* data[];
};
ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t count) {
ProtocolDict* protocol_dict_alloc(const ProtocolBase* const* protocols, size_t count) {
furi_check(protocols);
ProtocolDict* dict = malloc(sizeof(ProtocolDict) + (sizeof(void*) * count));
+1 -1
View File
@@ -12,7 +12,7 @@ typedef int32_t ProtocolId;
#define PROTOCOL_NO (-1)
#define PROTOCOL_ALL_FEATURES (0xFFFFFFFF)
ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t protocol_count);
ProtocolDict* protocol_dict_alloc(const ProtocolBase* const* protocols, size_t protocol_count);
void protocol_dict_free(ProtocolDict* dict);
@@ -0,0 +1,103 @@
#include "submenu_based.h"
#include <archive/helpers/archive_favorites.h>
struct SubmenuSettingsHelper {
const SubmenuSettingsHelperDescriptor* descriptor;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
Submenu* submenu;
uint32_t submenu_view_id;
uint32_t main_scene_id;
};
SubmenuSettingsHelper*
submenu_settings_helpers_alloc(const SubmenuSettingsHelperDescriptor* descriptor) {
furi_check(descriptor);
SubmenuSettingsHelper* helper = malloc(sizeof(SubmenuSettingsHelper));
helper->descriptor = descriptor;
return helper;
}
void submenu_settings_helpers_assign_objects(
SubmenuSettingsHelper* helper,
ViewDispatcher* view_dispatcher,
SceneManager* scene_manager,
Submenu* submenu,
uint32_t submenu_view_id,
uint32_t main_scene_id) {
furi_check(helper);
furi_check(view_dispatcher);
furi_check(scene_manager);
furi_check(submenu);
helper->view_dispatcher = view_dispatcher;
helper->scene_manager = scene_manager;
helper->submenu = submenu;
helper->submenu_view_id = submenu_view_id;
helper->main_scene_id = main_scene_id;
}
void submenu_settings_helpers_free(SubmenuSettingsHelper* helper) {
free(helper);
}
bool submenu_settings_helpers_app_start(SubmenuSettingsHelper* helper, void* arg) {
furi_check(helper);
if(!arg) return false;
const char* option = arg;
for(size_t i = 0; i < helper->descriptor->options_cnt; i++) {
if(strcmp(helper->descriptor->options[i].name, option) == 0) {
scene_manager_next_scene(
helper->scene_manager, helper->descriptor->options[i].scene_id);
return true;
}
}
return false;
}
static void
submenu_settings_helpers_callback(void* context, InputType input_type, uint32_t index) {
SubmenuSettingsHelper* helper = context;
if(input_type == InputTypeShort) {
view_dispatcher_send_custom_event(helper->view_dispatcher, index);
} else if(input_type == InputTypeLong) {
archive_favorites_handle_setting_pin_unpin(
helper->descriptor->app_name, helper->descriptor->options[index].name);
}
}
void submenu_settings_helpers_scene_enter(SubmenuSettingsHelper* helper) {
furi_check(helper);
for(size_t i = 0; i < helper->descriptor->options_cnt; i++) {
submenu_add_item_ex(
helper->submenu,
helper->descriptor->options[i].name,
i,
submenu_settings_helpers_callback,
helper);
}
submenu_set_selected_item(
helper->submenu,
scene_manager_get_scene_state(helper->scene_manager, helper->main_scene_id));
view_dispatcher_switch_to_view(helper->view_dispatcher, helper->submenu_view_id);
}
bool submenu_settings_helpers_scene_event(SubmenuSettingsHelper* helper, SceneManagerEvent event) {
furi_check(helper);
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_next_scene(
helper->scene_manager, helper->descriptor->options[event.event].scene_id);
scene_manager_set_scene_state(helper->scene_manager, helper->main_scene_id, event.event);
return true;
}
return false;
}
void submenu_settings_helpers_scene_exit(SubmenuSettingsHelper* helper) {
furi_check(helper);
submenu_reset(helper->submenu);
}
@@ -0,0 +1,89 @@
#include <gui/modules/submenu.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*SubmenuSettingsHelpherCallback)(void* context, uint32_t index);
typedef struct {
const char* name;
uint32_t scene_id;
} SubmenuSettingsHelperOption;
typedef struct {
const char* app_name;
size_t options_cnt;
SubmenuSettingsHelperOption options[];
} SubmenuSettingsHelperDescriptor;
typedef struct SubmenuSettingsHelper SubmenuSettingsHelper;
/**
* @brief Allocates a submenu-based settings helper
* @param descriptor settings descriptor
*/
SubmenuSettingsHelper*
submenu_settings_helpers_alloc(const SubmenuSettingsHelperDescriptor* descriptor);
/**
* @brief Assigns dynamic objects to the submenu-based settings helper
* @param helper helper object
* @param view_dispatcher ViewDispatcher
* @param scene_manager SceneManager
* @param submenu Submenu
* @param submenu_view_id Submenu view id in the ViewDispatcher
* @param main_scene_id Main scene id in the SceneManager
*/
void submenu_settings_helpers_assign_objects(
SubmenuSettingsHelper* helper,
ViewDispatcher* view_dispatcher,
SceneManager* scene_manager,
Submenu* submenu,
uint32_t submenu_view_id,
uint32_t main_scene_id);
/**
* @brief Frees a submenu-based settings helper
* @param helper helper object
*/
void submenu_settings_helpers_free(SubmenuSettingsHelper* helper);
/**
* @brief App start callback for the submenu-based settings helper
*
* If an argument containing one of the options was provided, launches the
* corresponding scene.
*
* @param helper helper object
* @param arg app argument, may be NULL
* @returns true if a setting name was provided in the argument, false if normal
* app operation shall commence
*/
bool submenu_settings_helpers_app_start(SubmenuSettingsHelper* helper, void* arg);
/**
* @brief Main scene enter callback for the submenu-based settings helper
* @param helper helper object
*/
void submenu_settings_helpers_scene_enter(SubmenuSettingsHelper* helper);
/**
* @brief Main scene event callback for the submenu-based settings helper
* @param helper helper object
* @param event event data
* @returns true if the event was consumed, false otherwise
*/
bool submenu_settings_helpers_scene_event(SubmenuSettingsHelper* helper, SceneManagerEvent event);
/**
* @brief Main scene exit callback for the submenu-based settings helper
* @param helper helper object
*/
void submenu_settings_helpers_scene_exit(SubmenuSettingsHelper* helper);
#ifdef __cplusplus
}
#endif