Various changes, in expansion, rgb, js

by Willy-JL , Spooks4576 , Sil333033 , HaxSam
This commit is contained in:
MX 2024-03-11 00:37:29 +03:00
parent 1bd56fb190
commit c9434492b8
No known key found for this signature in database
GPG key ID: 7CCC66B7DBDD1C83
19 changed files with 1665 additions and 12 deletions

View file

@ -527,7 +527,7 @@ index 0000000..572e1df
+
+void SK6805_update(void) {
+ SK6805_init();
+ furi_kernel_lock();
+ FURI_CRITICAL_ENTER();
+ uint32_t end;
+ /* Последовательная отправка цветов светодиодов */
+ for(uint8_t lednumber = 0; lednumber < SK6805_LED_COUNT; lednumber++) {
@ -567,7 +567,7 @@ index 0000000..572e1df
+ }
+ }
+ }
+ furi_kernel_unlock();
+ FURI_CRITICAL_EXIT();
+}
diff --git a/lib/drivers/SK6805.h b/lib/drivers/SK6805.h
new file mode 100644

View file

@ -18,6 +18,7 @@ typedef enum {
ExpansionStateDisabled,
ExpansionStateEnabled,
ExpansionStateRunning,
ExpansionStateConnectionEstablished,
} ExpansionState;
typedef enum {
@ -26,10 +27,15 @@ typedef enum {
ExpansionMessageTypeSetListenSerial,
ExpansionMessageTypeModuleConnected,
ExpansionMessageTypeModuleDisconnected,
ExpansionMessageTypeConnectionEstablished,
ExpansionMessageTypeIsConnected,
} ExpansionMessageType;
typedef union {
FuriHalSerialId serial_id;
union {
FuriHalSerialId serial_id;
bool* is_connected;
};
} ExpansionMessageData;
typedef struct {
@ -68,13 +74,21 @@ static void expansion_detect_callback(void* context) {
UNUSED(status);
}
static void expansion_worker_callback(void* context) {
static void expansion_worker_callback(void* context, ExpansionWorkerCallbackReason reason) {
furi_assert(context);
Expansion* instance = context;
ExpansionMessage message = {
.type = ExpansionMessageTypeModuleDisconnected,
.api_lock = NULL, // Not locking the API here to avoid a deadlock
ExpansionMessage message;
switch(reason) {
case ExpansionWorkerCallbackReasonExit:
message.type = ExpansionMessageTypeModuleDisconnected;
message.api_lock = NULL; // Not locking the API here to avoid a deadlock
break;
case ExpansionWorkerCallbackReasonConnected:
message.type = ExpansionMessageTypeConnectionEstablished;
message.api_lock = api_lock_alloc_locked();
break;
};
const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever);
@ -105,7 +119,9 @@ static void
if(instance->state == ExpansionStateDisabled) {
return;
} else if(instance->state == ExpansionStateRunning) {
} else if(
instance->state == ExpansionStateRunning ||
instance->state == ExpansionStateConnectionEstablished) {
expansion_worker_stop(instance->worker);
expansion_worker_free(instance->worker);
} else {
@ -122,7 +138,8 @@ static void expansion_control_handler_set_listen_serial(
const ExpansionMessageData* data) {
furi_check(data->serial_id < FuriHalSerialIdMax);
if(instance->state == ExpansionStateRunning) {
if(instance->state == ExpansionStateRunning ||
instance->state == ExpansionStateConnectionEstablished) {
expansion_worker_stop(instance->worker);
expansion_worker_free(instance->worker);
@ -160,7 +177,8 @@ static void expansion_control_handler_module_disconnected(
Expansion* instance,
const ExpansionMessageData* data) {
UNUSED(data);
if(instance->state != ExpansionStateRunning) {
if(instance->state != ExpansionStateRunning &&
instance->state != ExpansionStateConnectionEstablished) {
return;
}
@ -170,6 +188,23 @@ static void expansion_control_handler_module_disconnected(
instance->serial_id, expansion_detect_callback, instance);
}
static void expansion_control_handler_connection_established(
Expansion* instance,
const ExpansionMessageData* data) {
UNUSED(data);
if(instance->state != ExpansionStateRunning &&
instance->state != ExpansionStateConnectionEstablished) {
return;
}
instance->state = ExpansionStateConnectionEstablished;
}
static void
expansion_control_handler_is_connected(Expansion* instance, const ExpansionMessageData* data) {
*data->is_connected = instance->state == ExpansionStateConnectionEstablished;
}
typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*);
static const ExpansionControlHandler expansion_control_handlers[] = {
@ -178,6 +213,8 @@ static const ExpansionControlHandler expansion_control_handlers[] = {
[ExpansionMessageTypeSetListenSerial] = expansion_control_handler_set_listen_serial,
[ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected,
[ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected,
[ExpansionMessageTypeConnectionEstablished] = expansion_control_handler_connection_established,
[ExpansionMessageTypeIsConnected] = expansion_control_handler_is_connected,
};
static int32_t expansion_control(void* context) {
@ -249,6 +286,22 @@ void expansion_disable(Expansion* instance) {
api_lock_wait_unlock_and_free(message.api_lock);
}
bool expansion_is_connected(Expansion* instance) {
furi_check(instance);
bool is_connected;
ExpansionMessage message = {
.type = ExpansionMessageTypeIsConnected,
.data.is_connected = &is_connected,
.api_lock = api_lock_alloc_locked(),
};
furi_message_queue_put(instance->queue, &message, FuriWaitForever);
api_lock_wait_unlock_and_free(message.api_lock);
return is_connected;
}
void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) {
furi_check(instance);
furi_check(serial_id < FuriHalSerialIdMax);

View file

@ -50,6 +50,15 @@ void expansion_enable(Expansion* instance);
*/
void expansion_disable(Expansion* instance);
/**
* @brief Check if an expansion module is connected.
*
* @param[in,out] instance pointer to the Expansion instance.
*
* @returns true if the module is connected and initialized, false otherwise.
*/
bool expansion_is_connected(Expansion* instance);
/**
* @brief Enable support for expansion modules on designated serial port.
*

View file

@ -223,6 +223,7 @@ static bool expansion_worker_handle_state_handshake(
if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) {
instance->state = ExpansionWorkerStateConnected;
instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonConnected);
// Send response at previous baud rate
if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break;
furi_hal_serial_set_br(instance->serial_handle, baud_rate);
@ -351,7 +352,7 @@ static int32_t expansion_worker(void* context) {
// Do not invoke worker callback on user-requested exit
if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) {
instance->callback(instance->cb_context);
instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonExit);
}
return 0;

View file

@ -17,14 +17,20 @@
*/
typedef struct ExpansionWorker ExpansionWorker;
typedef enum {
ExpansionWorkerCallbackReasonExit,
ExpansionWorkerCallbackReasonConnected,
} ExpansionWorkerCallbackReason;
/**
* @brief Worker callback type.
*
* @see expansion_worker_set_callback()
*
* @param[in,out] context pointer to a user-defined object.
* @param[in] reason reason for the callback.
*/
typedef void (*ExpansionWorkerCallback)(void* context);
typedef void (*ExpansionWorkerCallback)(void* context, ExpansionWorkerCallbackReason reason);
/**
* @brief Create an expansion worker instance.

View file

@ -47,3 +47,42 @@ App(
requires=["js_app"],
sources=["modules/js_usbdisk/*.c"],
)
App(
appid="js_submenu",
apptype=FlipperAppType.PLUGIN,
entry_point="js_submenu_ep",
requires=["js_app"],
sources=["modules/js_submenu.c"],
)
App(
appid="js_blebeacon",
apptype=FlipperAppType.PLUGIN,
entry_point="js_blebeacon_ep",
requires=["js_app"],
sources=["modules/js_blebeacon.c"],
)
App(
appid="js_math",
apptype=FlipperAppType.PLUGIN,
entry_point="js_math_ep",
requires=["js_app"],
sources=["modules/js_math.c"],
)
App(
appid="js_keyboard",
apptype=FlipperAppType.PLUGIN,
entry_point="js_keyboard_ep",
requires=["js_app"],
sources=["modules/js_keyboard.c"],
)
App(
appid="js_subghz",
apptype=FlipperAppType.PLUGIN,
entry_point="js_subghz_ep",
requires=["js_app"],
sources=["modules/js_subghz/*.c"],
)

View file

@ -0,0 +1,59 @@
let blebeacon = require("blebeacon");
// Stop if previous background beacon is active
if (blebeacon.isActive()) {
blebeacon.stop();
}
// Make sure it resets at script exit, true will keep advertising in background
// This is false by default, can be omitted
blebeacon.keepAlive(false);
let math = require("math");
let currentIndex = 0;
let watchValues = [
0x1A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x11, 0x12, 0x13, 0x14, 0x15,
0x16, 0x17, 0x18, 0xE4, 0xE5, 0x1B, 0x1C, 0x1D, 0x1E,
0x20, 0xEC, 0xEF
];
function generateRandomMac() {
let mac = [];
for (let i = 0; i < 6; i++) {
mac.push(math.floor(math.random() * 256));
}
return Uint8Array(mac);
}
function sendRandomModelAdvertisement() {
let model = watchValues[currentIndex];
let packet = [
14, 0xFF, 0x75, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x01, 0xFF, 0x00, 0x00, 0x43,
model
];
let intervalMs = 50;
// Power level, min interval and max interval are optional
blebeacon.setConfig(generateRandomMac(), 0x1F, intervalMs, intervalMs * 3);
blebeacon.setData(Uint8Array(packet));
blebeacon.start();
print("Sent data for model ID " + to_string(model));
currentIndex = (currentIndex + 1) % watchValues.length;
delay(intervalMs);
blebeacon.stop();
}
while (true) {
sendRandomModelAdvertisement();
}

View file

@ -0,0 +1,19 @@
let keyboard = require("keyboard");
keyboard.setHeader("Example Text Input");
// Default text is optional
let text = keyboard.text(100, "Default text", true);
print("Got text:", text);
keyboard.setHeader("Example Byte Input");
// Default data is optional
let data = keyboard.byte(6, Uint8Array([1, 2, 3, 4, 5, 6]));
data = Uint8Array(data);
let result = "0x";
for (let i = 0; i < data.byteLength; i++) {
if (data[i] < 0x10) result += "0";
result += to_hex_string(data[i]);
}
print("Got data:", result);

View file

@ -0,0 +1,47 @@
let math = require("math");
let absResult = math.abs(-5);
let acosResult = math.acos(0.5);
let acoshResult = math.acosh(2);
let asinResult = math.asin(0.5);
let asinhResult = math.asinh(2);
let atanResult = math.atan(1);
let atan2Result = math.atan2(1, 1);
let atanhResult = math.atanh(0.5);
let cbrtResult = math.cbrt(27);
let ceilResult = math.ceil(5.3);
let clz32Result = math.clz32(1);
let cosResult = math.cos(math.PI);
let expResult = math.exp(1);
let floorResult = math.floor(5.7);
let maxResult = math.max(3, 5);
let minResult = math.min(3, 5);
let powResult = math.pow(2, 3);
let randomResult = math.random();
let signResult = math.sign(-5);
let sinResult = math.sin(math.PI / 2);
let sqrtResult = math.sqrt(25);
let truncResult = math.trunc(5.7);
print("math.abs(-5):", absResult);
print("math.acos(0.5):", acosResult);
print("math.acosh(2):", acoshResult);
print("math.asin(0.5):", asinResult);
print("math.asinh(2):", asinhResult);
print("math.atan(1):", atanResult);
print("math.atan2(1, 1):", atan2Result);
print("math.atanh(0.5):", atanhResult);
print("math.cbrt(27):", cbrtResult);
print("math.ceil(5.3):", ceilResult);
print("math.clz32(1):", clz32Result);
print("math.cos(math.PI):", cosResult);
print("math.exp(1):", expResult);
print("math.floor(5.7):", floorResult);
print("math.max(3, 5):", maxResult);
print("math.min(3, 5):", minResult);
print("math.pow(2, 3):", powResult);
print("math.random():", randomResult);
print("math.sign(-5):", signResult);
print("math.sin(math.PI/2):", sinResult);
print("math.sqrt(25):", sqrtResult);
print("math.trunc(5.7):", truncResult);

View file

@ -0,0 +1,37 @@
let subghz = require("subghz");
subghz.setup();
function printRXline() {
if (subghz.getState() !== "RX") {
subghz.setRx(); // to RX
}
let rssi = subghz.getRssi();
let freq = subghz.getFrequency();
let ext = subghz.isExternal();
print("rssi: ", rssi, "dBm", "@", freq, "MHz", "ext: ", ext);
}
function changeFrequency(freq) {
if (subghz.getState() !== "IDLE") {
subghz.setIdle(); // need to be idle to change frequency
}
subghz.setFrequency(freq);
}
subghz.setIdle();
print(subghz.getState()); // "IDLE"
subghz.setRx();
print(subghz.getState()); // "RX"
changeFrequency(433920000);
printRXline();
delay(1000);
let result = subghz.transmitFile("/ext/subghz/0.sub");
print(result ? "Send success" : "Send failed");
delay(1000);
changeFrequency(315000000);
printRXline();

View file

@ -0,0 +1,11 @@
let submenu = require("submenu");
submenu.addItem("Item 1", 0);
submenu.addItem("Item 2", 1);
submenu.addItem("Item 3", 2);
submenu.setHeader("Select an option:");
let result = submenu.show();
print("Result:", result);

View file

@ -0,0 +1,245 @@
#include "../js_modules.h"
#include <furi_hal_bt.h>
#include <extra_beacon.h>
typedef struct {
bool saved_prev_cfg;
bool prev_cfg_set;
GapExtraBeaconConfig prev_cfg;
bool saved_prev_data;
uint8_t prev_data[EXTRA_BEACON_MAX_DATA_SIZE];
uint8_t prev_data_len;
bool saved_prev_active;
bool prev_active;
bool keep_alive;
} JsBlebeaconInst;
static JsBlebeaconInst* get_this_ctx(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsBlebeaconInst* storage = mjs_get_ptr(mjs, obj_inst);
furi_assert(storage);
return storage;
}
static void ret_bad_args(struct mjs* mjs, const char* error) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
mjs_return(mjs, MJS_UNDEFINED);
}
static void ret_int_err(struct mjs* mjs, const char* error) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "%s", error);
mjs_return(mjs, MJS_UNDEFINED);
}
static bool check_arg_count(struct mjs* mjs, size_t count) {
size_t num_args = mjs_nargs(mjs);
if(num_args != count) {
ret_bad_args(mjs, "Wrong argument count");
return false;
}
return true;
}
static bool get_int_arg(struct mjs* mjs, size_t index, uint8_t* value, bool error) {
mjs_val_t int_obj = mjs_arg(mjs, index);
if(!mjs_is_number(int_obj)) {
if(error) ret_bad_args(mjs, "Argument must be a number");
return false;
}
*value = mjs_get_int(mjs, int_obj);
return true;
}
static void js_blebeacon_is_active(struct mjs* mjs) {
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
if(!check_arg_count(mjs, 0)) return;
UNUSED(blebeacon);
mjs_return(mjs, mjs_mk_boolean(mjs, furi_hal_bt_extra_beacon_is_active()));
}
static void js_blebeacon_set_config(struct mjs* mjs) {
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
if(mjs_nargs(mjs) < 1 || mjs_nargs(mjs) > 4) {
ret_bad_args(mjs, "Wrong argument count");
return;
}
char* mac = NULL;
size_t mac_len = 0;
mjs_val_t mac_arg = mjs_arg(mjs, 0);
if(mjs_is_typed_array(mac_arg)) {
if(mjs_is_data_view(mac_arg)) {
mac_arg = mjs_dataview_get_buf(mjs, mac_arg);
}
mac = mjs_array_buf_get_ptr(mjs, mac_arg, &mac_len);
}
if(!mac || mac_len != EXTRA_BEACON_MAC_ADDR_SIZE) {
ret_bad_args(mjs, "Wrong MAC address");
return;
}
uint8_t power = GapAdvPowerLevel_0dBm;
get_int_arg(mjs, 1, &power, false);
power = CLAMP(power, GapAdvPowerLevel_6dBm, GapAdvPowerLevel_Neg40dBm);
uint8_t intv_min = 50;
get_int_arg(mjs, 2, &intv_min, false);
intv_min = MAX(intv_min, 20);
uint8_t intv_max = 150;
get_int_arg(mjs, 3, &intv_max, false);
intv_max = MAX(intv_max, intv_min);
GapExtraBeaconConfig config = {
.min_adv_interval_ms = intv_min,
.max_adv_interval_ms = intv_max,
.adv_channel_map = GapAdvChannelMapAll,
.adv_power_level = power,
.address_type = GapAddressTypePublic,
};
memcpy(config.address, (uint8_t*)mac, sizeof(config.address));
if(!blebeacon->saved_prev_cfg) {
blebeacon->saved_prev_cfg = true;
const GapExtraBeaconConfig* prev_cfg_ptr = furi_hal_bt_extra_beacon_get_config();
if(prev_cfg_ptr) {
blebeacon->prev_cfg_set = true;
memcpy(&blebeacon->prev_cfg, prev_cfg_ptr, sizeof(blebeacon->prev_cfg));
} else {
blebeacon->prev_cfg_set = false;
}
}
if(!furi_hal_bt_extra_beacon_set_config(&config)) {
ret_int_err(mjs, "Failed setting beacon config");
return;
}
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_blebeacon_set_data(struct mjs* mjs) {
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
if(!check_arg_count(mjs, 1)) return;
char* data = NULL;
size_t data_len = 0;
mjs_val_t data_arg = mjs_arg(mjs, 0);
if(mjs_is_typed_array(data_arg)) {
if(mjs_is_data_view(data_arg)) {
data_arg = mjs_dataview_get_buf(mjs, data_arg);
}
data = mjs_array_buf_get_ptr(mjs, data_arg, &data_len);
}
if(!data) {
ret_bad_args(mjs, "Data must be a Uint8Array");
return;
}
if(!blebeacon->saved_prev_data) {
blebeacon->saved_prev_data = true;
blebeacon->prev_data_len = furi_hal_bt_extra_beacon_get_data(blebeacon->prev_data);
}
if(!furi_hal_bt_extra_beacon_set_data((uint8_t*)data, data_len)) {
ret_int_err(mjs, "Failed setting beacon data");
return;
}
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_blebeacon_start(struct mjs* mjs) {
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
if(!check_arg_count(mjs, 0)) return;
if(!blebeacon->saved_prev_active) {
blebeacon->saved_prev_active = true;
blebeacon->prev_active = furi_hal_bt_extra_beacon_is_active();
}
if(!furi_hal_bt_extra_beacon_start()) {
ret_int_err(mjs, "Failed starting beacon");
return;
}
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_blebeacon_stop(struct mjs* mjs) {
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
if(!check_arg_count(mjs, 0)) return;
UNUSED(blebeacon);
if(!blebeacon->saved_prev_active) {
blebeacon->saved_prev_active = true;
blebeacon->prev_active = furi_hal_bt_extra_beacon_is_active();
}
if(!furi_hal_bt_extra_beacon_stop()) {
ret_int_err(mjs, "Failed stopping beacon");
return;
}
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_blebeacon_keep_alive(struct mjs* mjs) {
JsBlebeaconInst* blebeacon = get_this_ctx(mjs);
if(!check_arg_count(mjs, 1)) return;
mjs_val_t bool_obj = mjs_arg(mjs, 0);
blebeacon->keep_alive = mjs_get_bool(mjs, bool_obj);
mjs_return(mjs, MJS_UNDEFINED);
}
static void* js_blebeacon_create(struct mjs* mjs, mjs_val_t* object) {
JsBlebeaconInst* blebeacon = malloc(sizeof(JsBlebeaconInst));
mjs_val_t blebeacon_obj = mjs_mk_object(mjs);
mjs_set(mjs, blebeacon_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, blebeacon));
mjs_set(mjs, blebeacon_obj, "isActive", ~0, MJS_MK_FN(js_blebeacon_is_active));
mjs_set(mjs, blebeacon_obj, "setConfig", ~0, MJS_MK_FN(js_blebeacon_set_config));
mjs_set(mjs, blebeacon_obj, "setData", ~0, MJS_MK_FN(js_blebeacon_set_data));
mjs_set(mjs, blebeacon_obj, "start", ~0, MJS_MK_FN(js_blebeacon_start));
mjs_set(mjs, blebeacon_obj, "stop", ~0, MJS_MK_FN(js_blebeacon_stop));
mjs_set(mjs, blebeacon_obj, "keepAlive", ~0, MJS_MK_FN(js_blebeacon_keep_alive));
*object = blebeacon_obj;
return blebeacon;
}
static void js_blebeacon_destroy(void* inst) {
JsBlebeaconInst* blebeacon = inst;
if(!blebeacon->keep_alive) {
if(furi_hal_bt_extra_beacon_is_active()) {
furi_check(furi_hal_bt_extra_beacon_stop());
}
if(blebeacon->saved_prev_cfg && blebeacon->prev_cfg_set) {
furi_check(furi_hal_bt_extra_beacon_set_config(&blebeacon->prev_cfg));
}
if(blebeacon->saved_prev_data) {
furi_check(
furi_hal_bt_extra_beacon_set_data(blebeacon->prev_data, blebeacon->prev_data_len));
}
if(blebeacon->prev_active) {
furi_check(furi_hal_bt_extra_beacon_start());
}
}
free(blebeacon);
}
static const JsModuleDescriptor js_blebeacon_desc = {
"blebeacon",
js_blebeacon_create,
js_blebeacon_destroy,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_blebeacon_desc,
};
const FlipperAppPluginDescriptor* js_blebeacon_ep(void) {
return &plugin_descriptor;
}

View file

@ -0,0 +1,195 @@
#include "../js_modules.h"
#include <gui/modules/text_input.h>
#include <gui/modules/byte_input.h>
#include <gui/view_dispatcher.h>
#define membersof(x) (sizeof(x) / sizeof(x[0]))
typedef struct {
char* data;
TextInput* text_input;
ByteInput* byte_input;
ViewDispatcher* view_dispatcher;
uint8_t* byteinput;
} JsKeyboardInst;
typedef enum {
JsKeyboardViewTextInput,
JsKeyboardViewByteInput,
} JsKeyboardView;
static void ret_bad_args(struct mjs* mjs, const char* error) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
mjs_return(mjs, MJS_UNDEFINED);
}
static bool get_str_arg(struct mjs* mjs, size_t index, const char** value, bool error) {
mjs_val_t str_obj = mjs_arg(mjs, index);
if(!mjs_is_string(str_obj)) {
if(error) ret_bad_args(mjs, "Argument must be a string");
return false;
}
size_t str_len = 0;
*value = mjs_get_string(mjs, &str_obj, &str_len);
if((str_len == 0) || (*value == NULL)) {
if(error) ret_bad_args(mjs, "Bad string argument");
return false;
}
return true;
}
static bool get_int_arg(struct mjs* mjs, size_t index, size_t* value, bool error) {
mjs_val_t int_obj = mjs_arg(mjs, index);
if(!mjs_is_number(int_obj)) {
if(error) ret_bad_args(mjs, "Argument must be a number");
return false;
}
*value = mjs_get_int(mjs, int_obj);
return true;
}
static JsKeyboardInst* get_this_ctx(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsKeyboardInst* storage = mjs_get_ptr(mjs, obj_inst);
furi_assert(storage);
return storage;
}
void text_input_callback(void* context) {
JsKeyboardInst* keyboard = (JsKeyboardInst*)context;
view_dispatcher_stop(keyboard->view_dispatcher);
}
void byte_input_callback(void* context) {
JsKeyboardInst* keyboard = (JsKeyboardInst*)context;
view_dispatcher_stop(keyboard->view_dispatcher);
}
static void js_keyboard_set_header(struct mjs* mjs) {
JsKeyboardInst* keyboard = get_this_ctx(mjs);
const char* header;
if(!get_str_arg(mjs, 0, &header, true)) return;
text_input_set_header_text(keyboard->text_input, header);
byte_input_set_header_text(keyboard->byte_input, header);
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_keyboard_text(struct mjs* mjs) {
JsKeyboardInst* keyboard = get_this_ctx(mjs);
size_t input_length;
if(!get_int_arg(mjs, 0, &input_length, true)) return;
char* buffer = malloc(input_length);
const char* default_text = "";
bool clear_default = false;
if(get_str_arg(mjs, 1, &default_text, false)) {
strlcpy(buffer, default_text, input_length);
mjs_val_t bool_obj = mjs_arg(mjs, 2);
clear_default = mjs_get_bool(mjs, bool_obj);
}
view_dispatcher_attach_to_gui(
keyboard->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen);
furi_record_close(RECORD_GUI);
text_input_set_result_callback(
keyboard->text_input, text_input_callback, keyboard, buffer, input_length, clear_default);
view_dispatcher_switch_to_view(keyboard->view_dispatcher, JsKeyboardViewTextInput);
view_dispatcher_run(keyboard->view_dispatcher);
text_input_reset(keyboard->text_input);
mjs_return(mjs, mjs_mk_string(mjs, buffer, ~0, true));
free(buffer);
}
static void js_keyboard_byte(struct mjs* mjs) {
JsKeyboardInst* keyboard = get_this_ctx(mjs);
size_t input_length;
if(!get_int_arg(mjs, 0, &input_length, true)) return;
uint8_t* buffer = malloc(input_length);
mjs_val_t default_data_arg = mjs_arg(mjs, 1);
if(mjs_is_typed_array(default_data_arg)) {
if(mjs_is_data_view(default_data_arg)) {
default_data_arg = mjs_dataview_get_buf(mjs, default_data_arg);
}
size_t default_data_len = 0;
char* default_data = mjs_array_buf_get_ptr(mjs, default_data_arg, &default_data_len);
memcpy(buffer, (uint8_t*)default_data, MIN((size_t)input_length, default_data_len));
}
view_dispatcher_attach_to_gui(
keyboard->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen);
furi_record_close(RECORD_GUI);
byte_input_set_result_callback(
keyboard->byte_input, byte_input_callback, NULL, keyboard, buffer, input_length);
view_dispatcher_switch_to_view(keyboard->view_dispatcher, JsKeyboardViewByteInput);
view_dispatcher_run(keyboard->view_dispatcher);
byte_input_set_result_callback(keyboard->byte_input, NULL, NULL, NULL, NULL, 0);
byte_input_set_header_text(keyboard->byte_input, "");
mjs_return(mjs, mjs_mk_array_buf(mjs, (char*)buffer, input_length));
free(buffer);
}
static void* js_keyboard_create(struct mjs* mjs, mjs_val_t* object) {
JsKeyboardInst* keyboard = malloc(sizeof(JsKeyboardInst));
mjs_val_t keyboard_obj = mjs_mk_object(mjs);
mjs_set(mjs, keyboard_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, keyboard));
mjs_set(mjs, keyboard_obj, "setHeader", ~0, MJS_MK_FN(js_keyboard_set_header));
mjs_set(mjs, keyboard_obj, "text", ~0, MJS_MK_FN(js_keyboard_text));
mjs_set(mjs, keyboard_obj, "byte", ~0, MJS_MK_FN(js_keyboard_byte));
keyboard->byte_input = byte_input_alloc();
keyboard->text_input = text_input_alloc();
keyboard->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(keyboard->view_dispatcher);
view_dispatcher_add_view(
keyboard->view_dispatcher,
JsKeyboardViewTextInput,
text_input_get_view(keyboard->text_input));
view_dispatcher_add_view(
keyboard->view_dispatcher,
JsKeyboardViewByteInput,
byte_input_get_view(keyboard->byte_input));
*object = keyboard_obj;
return keyboard;
}
static void js_keyboard_destroy(void* inst) {
JsKeyboardInst* keyboard = inst;
view_dispatcher_remove_view(keyboard->view_dispatcher, JsKeyboardViewByteInput);
byte_input_free(keyboard->byte_input);
view_dispatcher_remove_view(keyboard->view_dispatcher, JsKeyboardViewTextInput);
text_input_free(keyboard->text_input);
view_dispatcher_free(keyboard->view_dispatcher);
free(keyboard->data);
free(keyboard);
}
static const JsModuleDescriptor js_keyboard_desc = {
"keyboard",
js_keyboard_create,
js_keyboard_destroy,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_keyboard_desc,
};
const FlipperAppPluginDescriptor* js_keyboard_ep(void) {
return &plugin_descriptor;
}

View file

@ -0,0 +1,309 @@
#include "../js_modules.h"
#include "furi_hal_random.h"
#define JS_MATH_PI (double)3.14159265358979323846
#define JS_MATH_E (double)2.7182818284590452354
static void ret_bad_args(struct mjs* mjs, const char* error) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
mjs_return(mjs, mjs_mk_undefined());
}
static bool check_arg_count(struct mjs* mjs, size_t count) {
size_t num_args = mjs_nargs(mjs);
if(num_args != count) {
ret_bad_args(mjs, "Wrong argument count");
return false;
}
return true;
}
void js_math_abs(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, x < 0 ? mjs_mk_number(mjs, -x) : mjs_arg(mjs, 0));
}
void js_math_acos(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
if(x < -1 || x > 1) {
ret_bad_args(mjs, "Invalid input value for Math.acos");
mjs_return(mjs, MJS_UNDEFINED);
}
mjs_return(mjs, mjs_mk_number(mjs, JS_MATH_PI / (double)2 - atan(x / sqrt(1 - x * x))));
}
void js_math_acosh(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
if(x < 1) {
ret_bad_args(mjs, "Invalid input value for Math.acosh");
mjs_return(mjs, MJS_UNDEFINED);
}
mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - 1))));
}
void js_math_asin(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, atan(x / sqrt(1 - x * x))));
}
void js_math_asinh(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + 1))));
}
void js_math_atan(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, atan(x)));
}
void js_math_atan2(struct mjs* mjs) {
if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) ||
!mjs_is_number(mjs_arg(mjs, 1))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double y = mjs_get_double(mjs, mjs_arg(mjs, 0));
double x = mjs_get_double(mjs, mjs_arg(mjs, 1));
mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x)));
}
void js_math_atanh(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
if(x <= -1 || x >= 1) {
ret_bad_args(mjs, "Invalid input value for Math.atanh");
mjs_return(mjs, MJS_UNDEFINED);
}
mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log((1 + x) / (1 - x))));
}
void js_math_cbrt(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, pow(x, 1.0 / 3.0)));
}
void js_math_ceil(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, (int)(x + (double)0.5)));
}
void js_math_clz32(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0));
int count = 0;
while(x) {
x >>= 1;
count++;
}
mjs_return(mjs, mjs_mk_number(mjs, 32 - count));
}
void js_math_cos(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, cos(x)));
}
void js_math_exp(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
double result = 1;
double term = 1;
for(int i = 1; i < 100; i++) {
term *= x / i;
result += term;
}
mjs_return(mjs, mjs_mk_number(mjs, result));
}
void js_math_floor(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, (int)x));
}
void js_math_log(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
if(x <= 0) {
ret_bad_args(mjs, "Invalid input value for Math.log");
mjs_return(mjs, MJS_UNDEFINED);
}
double result = 0;
while(x >= JS_MATH_E) {
x /= JS_MATH_E;
result++;
}
mjs_return(mjs, mjs_mk_number(mjs, result + log(x)));
}
void js_math_max(struct mjs* mjs) {
if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) ||
!mjs_is_number(mjs_arg(mjs, 1))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
double y = mjs_get_double(mjs, mjs_arg(mjs, 1));
mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y));
}
void js_math_min(struct mjs* mjs) {
if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) ||
!mjs_is_number(mjs_arg(mjs, 1))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
double y = mjs_get_double(mjs, mjs_arg(mjs, 1));
mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y));
}
void js_math_pow(struct mjs* mjs) {
if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) ||
!mjs_is_number(mjs_arg(mjs, 1))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double base = mjs_get_double(mjs, mjs_arg(mjs, 0));
double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1));
double result = 1;
for(int i = 0; i < exponent; i++) {
result *= base;
}
mjs_return(mjs, mjs_mk_number(mjs, result));
}
void js_math_random(struct mjs* mjs) {
if(!check_arg_count(mjs, 0)) {
mjs_return(mjs, MJS_UNDEFINED);
}
const uint32_t random_val = furi_hal_random_get();
double rnd = (double)random_val / RAND_MAX;
mjs_return(mjs, mjs_mk_number(mjs, rnd));
}
void js_math_sign(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, x == 0 ? 0 : (x < 0 ? -1 : 1)));
}
void js_math_sin(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
double result = x;
double term = x;
for(int i = 1; i < 10; i++) {
term *= -x * x / ((2 * i) * (2 * i + 1));
result += term;
}
mjs_return(mjs, mjs_mk_number(mjs, result));
}
void js_math_sqrt(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
if(x < 0) {
ret_bad_args(mjs, "Invalid input value for Math.sqrt");
mjs_return(mjs, MJS_UNDEFINED);
}
double result = 1;
while(result * result < x) {
result += (double)0.001;
}
mjs_return(mjs, mjs_mk_number(mjs, result));
}
void js_math_trunc(struct mjs* mjs) {
if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) {
mjs_return(mjs, MJS_UNDEFINED);
}
double x = mjs_get_double(mjs, mjs_arg(mjs, 0));
mjs_return(mjs, mjs_mk_number(mjs, x < 0 ? ceil(x) : floor(x)));
}
static void* js_math_create(struct mjs* mjs, mjs_val_t* object) {
mjs_val_t math_obj = mjs_mk_object(mjs);
mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs));
mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos));
mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh));
mjs_set(mjs, math_obj, "asin", ~0, MJS_MK_FN(js_math_asin));
mjs_set(mjs, math_obj, "asinh", ~0, MJS_MK_FN(js_math_asinh));
mjs_set(mjs, math_obj, "atan", ~0, MJS_MK_FN(js_math_atan));
mjs_set(mjs, math_obj, "atan2", ~0, MJS_MK_FN(js_math_atan2));
mjs_set(mjs, math_obj, "atanh", ~0, MJS_MK_FN(js_math_atanh));
mjs_set(mjs, math_obj, "cbrt", ~0, MJS_MK_FN(js_math_cbrt));
mjs_set(mjs, math_obj, "ceil", ~0, MJS_MK_FN(js_math_ceil));
mjs_set(mjs, math_obj, "clz32", ~0, MJS_MK_FN(js_math_clz32));
mjs_set(mjs, math_obj, "cos", ~0, MJS_MK_FN(js_math_cos));
mjs_set(mjs, math_obj, "exp", ~0, MJS_MK_FN(js_math_exp));
mjs_set(mjs, math_obj, "floor", ~0, MJS_MK_FN(js_math_floor));
mjs_set(mjs, math_obj, "log", ~0, MJS_MK_FN(js_math_log));
mjs_set(mjs, math_obj, "max", ~0, MJS_MK_FN(js_math_max));
mjs_set(mjs, math_obj, "min", ~0, MJS_MK_FN(js_math_min));
mjs_set(mjs, math_obj, "pow", ~0, MJS_MK_FN(js_math_pow));
mjs_set(mjs, math_obj, "random", ~0, MJS_MK_FN(js_math_random));
mjs_set(mjs, math_obj, "sign", ~0, MJS_MK_FN(js_math_sign));
mjs_set(mjs, math_obj, "sin", ~0, MJS_MK_FN(js_math_sin));
mjs_set(mjs, math_obj, "sqrt", ~0, MJS_MK_FN(js_math_sqrt));
mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc));
mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI));
mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E));
*object = math_obj;
return (void*)1;
}
static const JsModuleDescriptor js_math_desc = {
"math",
js_math_create,
NULL,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_math_desc,
};
const FlipperAppPluginDescriptor* js_math_ep(void) {
return &plugin_descriptor;
}

View file

@ -0,0 +1,391 @@
#include "../../js_modules.h"
#include "radio_device_loader.h"
#include <lib/subghz/transmitter.h>
#include <lib/subghz/devices/devices.h>
#include <lib/subghz/protocols/protocol_items.h>
#include <flipper_format/flipper_format_i.h>
#define tag "js_subghz"
typedef enum {
JsSubghzRadioStateRX,
JsSubghzRadioStateTX,
JsSubghzRadioStateIDLE,
} JsSubghzRadioState;
typedef struct {
const SubGhzDevice* radio_device;
int frequency;
bool is_external;
JsSubghzRadioState state;
} JsSubghzInst;
// from subghz cli
static FuriHalSubGhzPreset js_subghz_get_preset_name(const char* preset_name) {
FuriHalSubGhzPreset preset = FuriHalSubGhzPresetIDLE;
if(!strcmp(preset_name, "FuriHalSubGhzPresetOok270Async")) {
preset = FuriHalSubGhzPresetOok270Async;
} else if(!strcmp(preset_name, "FuriHalSubGhzPresetOok650Async")) {
preset = FuriHalSubGhzPresetOok650Async;
} else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev238Async")) {
preset = FuriHalSubGhzPreset2FSKDev238Async;
} else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev476Async")) {
preset = FuriHalSubGhzPreset2FSKDev476Async;
} else if(!strcmp(preset_name, "FuriHalSubGhzPresetCustom")) {
preset = FuriHalSubGhzPresetCustom;
} else {
FURI_LOG_I(tag, "unknown preset");
}
return preset;
}
static int32_t get_int_arg(struct mjs* mjs, size_t index, int32_t* value) {
mjs_val_t int_obj = mjs_arg(mjs, index);
if(!mjs_is_number(int_obj)) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Argument must be a number");
mjs_return(mjs, MJS_UNDEFINED);
return false;
}
*value = mjs_get_int(mjs, int_obj);
return true;
}
static void js_subghz_set_rx(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
if(js_subghz->state == JsSubghzRadioStateRX) {
mjs_return(mjs, MJS_UNDEFINED);
return;
}
subghz_devices_set_rx(js_subghz->radio_device);
js_subghz->state = JsSubghzRadioStateRX;
}
static void js_subgjz_set_idle(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
if(js_subghz->state == JsSubghzRadioStateIDLE) {
mjs_return(mjs, MJS_UNDEFINED);
return;
}
subghz_devices_idle(js_subghz->radio_device);
js_subghz->state = JsSubghzRadioStateIDLE;
}
static void js_subghz_get_rssi(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
if(js_subghz->state != JsSubghzRadioStateRX) {
mjs_return(mjs, MJS_UNDEFINED);
return;
}
float rssi = subghz_devices_get_rssi(js_subghz->radio_device);
mjs_return(mjs, mjs_mk_number(mjs, (double)rssi));
}
static void js_subghz_get_state(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
const char* state;
switch(js_subghz->state) {
case JsSubghzRadioStateRX:
state = "RX";
break;
case JsSubghzRadioStateTX:
state = "TX";
break;
case JsSubghzRadioStateIDLE:
state = "IDLE";
break;
default:
state = "";
break;
}
mjs_return(mjs, mjs_mk_string(mjs, state, ~0, true));
}
static void js_subghz_is_external(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
mjs_return(mjs, mjs_mk_boolean(mjs, js_subghz->is_external));
}
static void js_subghz_set_frequency(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
if(js_subghz->state != JsSubghzRadioStateIDLE) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Radio is not in IDLE state");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
int32_t frequency;
if(!get_int_arg(mjs, 0, &frequency)) return;
if(!subghz_devices_is_frequency_valid(js_subghz->radio_device, frequency)) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Invalid frequency");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
js_subghz->frequency = subghz_devices_set_frequency(js_subghz->radio_device, frequency);
mjs_return(mjs, mjs_mk_number(mjs, (double)js_subghz->frequency));
}
static void js_subghz_get_frequency(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
mjs_return(mjs, mjs_mk_number(mjs, (double)js_subghz->frequency));
}
static void js_subghz_transmit_file(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
mjs_val_t file = mjs_arg(mjs, 0);
if(!mjs_is_string(file)) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "File must be a string");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
const char* file_path = mjs_get_string(mjs, &file, NULL);
if(!file_path) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to get file path");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff_file = flipper_format_file_alloc(storage);
FlipperFormat* fff_data_raw = flipper_format_string_alloc();
if(!flipper_format_file_open_existing(fff_file, file_path)) {
flipper_format_free(fff_file);
furi_record_close(RECORD_STORAGE);
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to open file");
mjs_return(mjs, MJS_UNDEFINED);
return;
}
SubGhzEnvironment* environment = subghz_environment_alloc();
if(!subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) {
FURI_LOG_I(tag, "Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n");
}
if(!subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) {
FURI_LOG_I(tag, "Load_keystore keeloq_mfcodes_user \033[0;33mAbsent\033[0m\r\n");
}
subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME);
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
environment, SUBGHZ_NICE_FLOR_S_DIR_NAME);
subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry);
FuriString* temp_str = furi_string_alloc();
SubGhzTransmitter* transmitter = NULL;
bool is_init_protocol = true;
bool is_sent = false;
uint32_t frequency = 0;
uint32_t repeat = 10;
do {
//Load frequency
if(!flipper_format_read_uint32(fff_file, "Frequency", &frequency, 1)) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to read frequency from file");
mjs_return(mjs, MJS_UNDEFINED);
break;
}
if(!subghz_devices_is_frequency_valid(js_subghz->radio_device, frequency)) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Invalid frequency");
mjs_return(mjs, MJS_UNDEFINED);
break;
}
if(!flipper_format_read_string(fff_file, "Preset", temp_str)) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to read preset from file");
mjs_return(mjs, MJS_UNDEFINED);
break;
}
subghz_devices_reset(js_subghz->radio_device);
if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Custom presets are not supported (yet)");
mjs_return(mjs, MJS_UNDEFINED);
break;
} else {
subghz_devices_load_preset(
js_subghz->radio_device,
js_subghz_get_preset_name(furi_string_get_cstr(temp_str)),
NULL);
}
js_subghz->frequency = subghz_devices_set_frequency(js_subghz->radio_device, frequency);
if(!flipper_format_read_string(fff_file, "Protocol", temp_str)) {
mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Failed to read protocol from file");
mjs_return(mjs, MJS_UNDEFINED);
break;
}
SubGhzProtocolStatus status;
bool is_raw = false;
if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
subghz_protocol_raw_gen_fff_data(
fff_data_raw, file_path, subghz_devices_get_name(js_subghz->radio_device));
is_raw = true;
}
transmitter = subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str));
if(transmitter == NULL) {
is_init_protocol = false;
}
if(is_init_protocol) {
status = subghz_transmitter_deserialize(transmitter, is_raw ? fff_data_raw : fff_file);
if(status != SubGhzProtocolStatusOk) {
FURI_LOG_I(tag, "failed to deserialize transmitter");
is_init_protocol = false;
}
} else {
FURI_LOG_I(tag, "failed to allocate transmitter");
subghz_devices_idle(js_subghz->radio_device);
js_subghz->state = JsSubghzRadioStateIDLE;
}
} while(false);
if(is_init_protocol) {
if(!js_subghz->is_external) {
furi_hal_power_suppress_charge_enter();
}
FURI_LOG_I(tag, "transmitting file %s", file_path);
do {
furi_delay_ms(200);
if(subghz_devices_start_async_tx(
js_subghz->radio_device, subghz_transmitter_yield, transmitter)) {
while(!subghz_devices_is_async_complete_tx(js_subghz->radio_device)) {
furi_delay_ms(333);
}
subghz_devices_stop_async_tx(js_subghz->radio_device);
is_sent = true;
} else {
FURI_LOG_E(tag, "failed to start async tx");
}
} while(repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW"));
subghz_devices_idle(js_subghz->radio_device);
js_subghz->state = JsSubghzRadioStateIDLE;
if(!js_subghz->is_external) {
furi_hal_power_suppress_charge_exit();
}
}
furi_string_free(temp_str);
flipper_format_free(fff_file);
flipper_format_free(fff_data_raw);
furi_record_close(RECORD_STORAGE);
subghz_environment_reset_keeloq(environment);
subghz_environment_free(environment);
subghz_transmitter_free(transmitter);
mjs_return(mjs, mjs_mk_boolean(mjs, is_sent));
}
static void js_subghz_setup(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubghzInst* js_subghz = mjs_get_ptr(mjs, obj_inst);
furi_assert(js_subghz);
js_subghz->radio_device =
radio_device_loader_set(js_subghz->radio_device, SubGhzRadioDeviceTypeExternalCC1101);
if(!subghz_devices_is_connect(js_subghz->radio_device)) {
js_subghz->is_external = true;
} else {
js_subghz->is_external = false;
}
js_subghz->state = JsSubghzRadioStateIDLE;
js_subghz->frequency = 433920000;
subghz_devices_reset(js_subghz->radio_device);
subghz_devices_idle(js_subghz->radio_device);
mjs_return(mjs, MJS_UNDEFINED);
}
static void* js_subghz_create(struct mjs* mjs, mjs_val_t* object) {
JsSubghzInst* js_subghz = malloc(sizeof(JsSubghzInst));
mjs_val_t subghz_obj = mjs_mk_object(mjs);
subghz_devices_init();
mjs_set(mjs, subghz_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_subghz));
mjs_set(mjs, subghz_obj, "setup", ~0, MJS_MK_FN(js_subghz_setup));
mjs_set(mjs, subghz_obj, "setRx", ~0, MJS_MK_FN(js_subghz_set_rx));
mjs_set(mjs, subghz_obj, "setIdle", ~0, MJS_MK_FN(js_subgjz_set_idle));
mjs_set(mjs, subghz_obj, "getRssi", ~0, MJS_MK_FN(js_subghz_get_rssi));
mjs_set(mjs, subghz_obj, "getState", ~0, MJS_MK_FN(js_subghz_get_state));
mjs_set(mjs, subghz_obj, "getFrequency", ~0, MJS_MK_FN(js_subghz_get_frequency));
mjs_set(mjs, subghz_obj, "setFrequency", ~0, MJS_MK_FN(js_subghz_set_frequency));
mjs_set(mjs, subghz_obj, "isExternal", ~0, MJS_MK_FN(js_subghz_is_external));
mjs_set(mjs, subghz_obj, "transmitFile", ~0, MJS_MK_FN(js_subghz_transmit_file));
*object = subghz_obj;
return js_subghz;
}
static void js_subghz_destroy(void* inst) {
JsSubghzInst* js_subghz = inst;
subghz_devices_deinit();
free(js_subghz);
}
static const JsModuleDescriptor js_subghz_desc = {
"subghz",
js_subghz_create,
js_subghz_destroy,
};
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_subghz_desc,
};
const FlipperAppPluginDescriptor* js_subghz_ep(void) {
return &plugin_descriptor;
}

View file

@ -0,0 +1,64 @@
#include "radio_device_loader.h"
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
static void radio_device_loader_power_on() {
uint8_t attempts = 0;
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
furi_hal_power_enable_otg();
//CC1101 power-up time
furi_delay_ms(10);
}
}
static void radio_device_loader_power_off() {
if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
}
bool radio_device_loader_is_connect_external(const char* name) {
bool is_connect = false;
bool is_otg_enabled = furi_hal_power_is_otg_enabled();
if(!is_otg_enabled) {
radio_device_loader_power_on();
}
const SubGhzDevice* device = subghz_devices_get_by_name(name);
if(device) {
is_connect = subghz_devices_is_connect(device);
}
if(!is_otg_enabled) {
radio_device_loader_power_off();
}
return is_connect;
}
const SubGhzDevice* radio_device_loader_set(
const SubGhzDevice* current_radio_device,
SubGhzRadioDeviceType radio_device_type) {
const SubGhzDevice* radio_device;
if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
radio_device_loader_power_on();
radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
subghz_devices_begin(radio_device);
} else if(current_radio_device == NULL) {
radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
} else {
radio_device_loader_end(current_radio_device);
radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
}
return radio_device;
}
void radio_device_loader_end(const SubGhzDevice* radio_device) {
furi_assert(radio_device);
radio_device_loader_power_off();
if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) {
subghz_devices_end(radio_device);
}
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <lib/subghz/devices/devices.h>
/** SubGhzRadioDeviceType */
typedef enum {
SubGhzRadioDeviceTypeInternal,
SubGhzRadioDeviceTypeExternalCC1101,
} SubGhzRadioDeviceType;
const SubGhzDevice* radio_device_loader_set(
const SubGhzDevice* current_radio_device,
SubGhzRadioDeviceType radio_device_type);
void radio_device_loader_end(const SubGhzDevice* radio_device);

View file

@ -0,0 +1,152 @@
#include <gui/modules/submenu.h>
#include <gui/view_dispatcher.h>
#include <gui/view.h>
#include "../js_modules.h"
typedef struct {
Submenu* submenu;
ViewDispatcher* view_dispatcher;
uint32_t result;
} JsSubmenuInst;
typedef enum {
JsSubmenuViewSubmenu,
} JsSubmenuView;
static JsSubmenuInst* get_this_ctx(struct mjs* mjs) {
mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0);
JsSubmenuInst* storage = mjs_get_ptr(mjs, obj_inst);
furi_assert(storage);
return storage;
}
static void ret_bad_args(struct mjs* mjs, const char* error) {
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
mjs_return(mjs, MJS_UNDEFINED);
}
static bool check_arg_count(struct mjs* mjs, size_t count) {
size_t num_args = mjs_nargs(mjs);
if(num_args != count) {
ret_bad_args(mjs, "Wrong argument count");
return false;
}
return true;
}
static bool get_str_arg(struct mjs* mjs, size_t index, const char** value) {
mjs_val_t str_obj = mjs_arg(mjs, index);
if(!mjs_is_string(str_obj)) {
ret_bad_args(mjs, "Argument must be a string");
return false;
}
size_t str_len = 0;
*value = mjs_get_string(mjs, &str_obj, &str_len);
if((str_len == 0) || (*value == NULL)) {
ret_bad_args(mjs, "Bad string argument");
return false;
}
return true;
}
static int32_t get_int_arg(struct mjs* mjs, size_t index, int32_t* value) {
mjs_val_t int_obj = mjs_arg(mjs, index);
if(!mjs_is_number(int_obj)) {
ret_bad_args(mjs, "Argument must be a number");
return false;
}
*value = mjs_get_int32(mjs, int_obj);
return true;
}
static void submenu_callback(void* context, uint32_t id) {
UNUSED(id);
JsSubmenuInst* submenu = context;
submenu->result = id;
view_dispatcher_stop(submenu->view_dispatcher);
}
static void js_submenu_add_item(struct mjs* mjs) {
JsSubmenuInst* submenu = get_this_ctx(mjs);
if(!check_arg_count(mjs, 2)) return;
const char* label;
if(!get_str_arg(mjs, 0, &label)) return;
int32_t id;
if(!get_int_arg(mjs, 1, &id)) return;
submenu_add_item(submenu->submenu, label, id, submenu_callback, submenu);
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_submenu_set_header(struct mjs* mjs) {
JsSubmenuInst* submenu = get_this_ctx(mjs);
if(!check_arg_count(mjs, 1)) return;
const char* header;
if(!get_str_arg(mjs, 0, &header)) return;
submenu_set_header(submenu->submenu, header);
mjs_return(mjs, MJS_UNDEFINED);
}
static void js_submenu_show(struct mjs* mjs) {
JsSubmenuInst* submenu = get_this_ctx(mjs);
if(!check_arg_count(mjs, 0)) return;
submenu->result = 0;
view_dispatcher_attach_to_gui(
submenu->view_dispatcher, furi_record_open(RECORD_GUI), ViewDispatcherTypeFullscreen);
furi_record_close(RECORD_GUI);
view_dispatcher_switch_to_view(submenu->view_dispatcher, JsSubmenuViewSubmenu);
view_dispatcher_run(submenu->view_dispatcher);
submenu_reset(submenu->submenu);
mjs_return(mjs, mjs_mk_number(mjs, submenu->result));
}
static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) {
JsSubmenuInst* submenu = malloc(sizeof(JsSubmenuInst));
mjs_val_t submenu_obj = mjs_mk_object(mjs);
mjs_set(mjs, submenu_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, submenu));
mjs_set(mjs, submenu_obj, "addItem", ~0, MJS_MK_FN(js_submenu_add_item));
mjs_set(mjs, submenu_obj, "setHeader", ~0, MJS_MK_FN(js_submenu_set_header));
mjs_set(mjs, submenu_obj, "show", ~0, MJS_MK_FN(js_submenu_show));
submenu->submenu = submenu_alloc();
submenu->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(submenu->view_dispatcher);
view_dispatcher_add_view(
submenu->view_dispatcher, JsSubmenuViewSubmenu, submenu_get_view(submenu->submenu));
*object = submenu_obj;
return submenu;
}
static void js_submenu_destroy(void* inst) {
JsSubmenuInst* submenu = inst;
view_dispatcher_remove_view(submenu->view_dispatcher, JsSubmenuViewSubmenu);
submenu_free(submenu->submenu);
view_dispatcher_free(submenu->view_dispatcher);
free(submenu);
}
static const JsModuleDescriptor js_submenu_desc = {
"submenu",
js_submenu_create,
js_submenu_destroy,
};
static const FlipperAppPluginDescriptor submenu_plugin_descriptor = {
.appid = PLUGIN_APP_ID,
.ep_api_version = PLUGIN_API_VERSION,
.entry_point = &js_submenu_desc,
};
const FlipperAppPluginDescriptor* js_submenu_ep(void) {
return &submenu_plugin_descriptor;
}

View file

@ -991,6 +991,7 @@ Function,-,exp2f,float,float
Function,-,exp2l,long double,long double
Function,+,expansion_disable,void,Expansion*
Function,+,expansion_enable,void,Expansion*
Function,+,expansion_is_connected,_Bool,Expansion*
Function,+,expansion_set_listen_serial,void,"Expansion*, FuriHalSerialId"
Function,-,expf,float,float
Function,-,expl,long double,long double

1 entry status name type params
991 Function - exp2l long double long double
992 Function + expansion_disable void Expansion*
993 Function + expansion_enable void Expansion*
994 Function + expansion_is_connected _Bool Expansion*
995 Function + expansion_set_listen_serial void Expansion*, FuriHalSerialId
996 Function - expf float float
997 Function - expl long double long double