diff --git a/applications/system/js_app/examples/apps/Scripts/blebeacon.js b/applications/system/js_app/examples/apps/Scripts/blebeacon.js index f324fafd9..5e29ccbc1 100644 --- a/applications/system/js_app/examples/apps/Scripts/blebeacon.js +++ b/applications/system/js_app/examples/apps/Scripts/blebeacon.js @@ -1,7 +1,7 @@ -let bleBeacon = require("blebeacon"); +let blebeacon = require("blebeacon"); +let math = require("math"); let currentIndex = 0; -let currentByteValue = 0; let watchValues = [ 0x1A, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x11, 0x12, 0x13, 0x14, 0x15, @@ -9,50 +9,15 @@ let watchValues = [ 0x20, 0xEC, 0xEF ]; -function byteToHex(byte) { - let hexChars = '0123456789abcdef'; - let hex = ''; - if (byte >= 0 && byte <= 255) { - hex = hexChars[(byte >> 4) & 0x0F] + hexChars[byte & 0x0F]; - } - return hex; -} - -function getNextByteValue() { - let value = currentByteValue; - currentByteValue = (currentByteValue + 1) % 256; - return value; -} - function generateRandomMac() { - let mac = ''; + let mac = []; for (let i = 0; i < 6; i++) { - if (mac.length) mac += ':'; - let byte = getNextByteValue(); - mac += byteToHex(byte); + mac.push(math.floor(math.random() * 256)); } - return mac; -} - -function bytesToHexString(bytes) { - if (!bytes) { - print("Invalid input for bytesToHexString"); - return ''; - } - - let hexString = ''; - for (let i = 0; i < bytes.length; i++) { - hexString += byteToHex(bytes[i]); - } - return hexString; + return Uint8Array(mac); } function sendRandomModelAdvertisement() { - if (!watchValues || watchValues.length === 0) { - print("watchValues array is empty or undefined."); - return; - } - let model = watchValues[currentIndex]; let packet = [ @@ -60,28 +25,27 @@ function sendRandomModelAdvertisement() { model ]; - let packetString = bytesToHexString(packet); - if (!packetString) { - print("Failed to generate packet string."); - return; - } + let intervalMs = 50; - bleBeacon.setMac(generateRandomMac()); - bleBeacon.setData(packetString); - bleBeacon.send(); + // 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(500); + delay(intervalMs); - bleBeacon.stop(); - - bleBeacon + blebeacon.stop(); } -while (true) -{ +// Make sure it resets at script exit, true will keep advertising in background +blebeacon.keepAlive(true); + +while (true) { sendRandomModelAdvertisement(); } \ No newline at end of file diff --git a/applications/system/js_app/modules/js_blebeacon.c b/applications/system/js_app/modules/js_blebeacon.c index 946a99bab..35dbbb47b 100644 --- a/applications/system/js_app/modules/js_blebeacon.c +++ b/applications/system/js_app/modules/js_blebeacon.c @@ -3,81 +3,20 @@ #include typedef struct { - char* data; - char* mac_addr; - size_t beacon_data_len; - GapExtraBeaconConfig beacon_config; + 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; -struct OUI_MAP_ENTRY { - const char* brand; - const char* oui; -}; - -struct OUI_MAP_ENTRY OUI_MAP[] = { - {"Apple", "00:1F:7F"}, - {"Dell", "00:14:5F"}, - {"HP", "00:4C:6F"}, - {"Lenovo", "00:50:C2"}, - {"Microsoft", "00:0C:29"}, - {"Samsung", "00:1C:42"}, - {"Sony", "00:0A:95"}, - {"Acer", "00:26:A9"}, - {"Asus", "00:19:D8"}, - {"Google", "08:00:27"}, - {"HTC", "00:1F:B5"}, - {"Intel", "00:19:5D"}, - {"LG", "00:1C:61"}, - {"Motorola", "00:1F:42"}, - {"Toshiba", "00:1E:67"}, - {"Xiaomi", "00:26:A8"}, -}; - -#define OUI_MAP_SIZE (sizeof(OUI_MAP) / sizeof(OUI_MAP[0])) - -int rand_range(int min, int max) { - return min + rand() / (RAND_MAX / (max - min + 1) + 1); -} - -void byte_to_hex(char* output, unsigned char byte) { - static const char hex_chars[] = "0123456789ABCDEF"; - output[0] = hex_chars[byte >> 4]; - output[1] = hex_chars[byte & 0x0F]; -} - -static char* generate_mac_address(const char* brand) { - char* mac_address = (char*)malloc(18 * sizeof(char)); - if(mac_address == NULL) { - FURI_LOG_D("BLE", "Memory allocation failed.\n"); - return NULL; - } - - const char* oui = NULL; - for(unsigned int i = 0; i < OUI_MAP_SIZE; ++i) { - if(strcmp(brand, OUI_MAP[i].brand) == 0) { - oui = OUI_MAP[i].oui; - break; - } - } - - if(oui == NULL) { - FURI_LOG_D("BLE", "Brand not found.\n"); - free(mac_address); - return NULL; - } - - char last_bytes[6]; - for(int i = 0; i < 3; ++i) { - unsigned char byte = rand_range(0x00, 0xFF); - byte_to_hex(&last_bytes[i * 2], byte); - } - - strcpy(mac_address, oui); - strcat(mac_address, ":"); - strcat(mac_address, last_bytes); - return mac_address; -} - 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); @@ -99,177 +38,162 @@ static bool check_arg_count(struct mjs* mjs, size_t count) { return true; } -static bool get_str_arg(struct mjs* mjs, size_t index, 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"); +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; } - size_t str_len; - const char* temp = mjs_get_string(mjs, &str_obj, &str_len); - if(str_len == 0 || !temp) { - ret_bad_args(mjs, "Bad string argument"); - return false; - } - - *value = (char*)malloc(str_len + 1); - if(!*value) { - ret_bad_args(mjs, "Memory allocation failed"); - return false; - } - strncpy(*value, temp, str_len); - (*value)[str_len] = '\0'; // Ensure null termination + *value = mjs_get_int(mjs, int_obj); return true; } -static uint8_t hex_char_to_uint(char c) { - if(c >= '0' && c <= '9') return c - '0'; - if(c >= 'a' && c <= 'f') return 10 + c - 'a'; - if(c >= 'A' && c <= 'F') return 10 + c - 'A'; - return 0; -} +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; + } -static uint8_t* macstr_to_uint8(const char* macstr) { - if(strlen(macstr) != 17) return NULL; + 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* mac_bytes = (uint8_t*)malloc(6 * sizeof(uint8_t)); - if(!mac_bytes) return NULL; + uint8_t power = GapAdvPowerLevel_0dBm; + get_int_arg(mjs, 1, &power, false); + power = CLAMP(power, GapAdvPowerLevel_6dBm, GapAdvPowerLevel_Neg40dBm); - for(size_t i = 0, j = 0; i < 17; i += 3, ++j) { - mac_bytes[j] = (hex_char_to_uint(macstr[i]) << 4) | hex_char_to_uint(macstr[i + 1]); + uint8_t intv_min = 50; + get_int_arg(mjs, 2, &intv_min, false); + intv_min = MAX(intv_min, 20); - if(i < 15 && macstr[i + 2] != ':' && macstr[i + 2] != '-') { - free(mac_bytes); - return NULL; + 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; } } - - return mac_bytes; -} - -static uint8_t* hexstr_to_uint8(const char* hexstr, size_t* out_length) { - size_t len = strlen(hexstr); - if(len % 2 != 0) return NULL; - - if(len > EXTRA_BEACON_MAX_DATA_SIZE + 1) return NULL; - - *out_length = len / 2; - uint8_t* bytes = (uint8_t*)malloc(*out_length); - if(!bytes) return NULL; - - for(size_t i = 0; i < *out_length; ++i) { - bytes[i] = (hex_char_to_uint(hexstr[i * 2]) << 4) | hex_char_to_uint(hexstr[i * 2 + 1]); - } - - return bytes; + furi_check(furi_hal_bt_extra_beacon_set_config(&config)); } static void js_blebeacon_set_data(struct mjs* mjs) { - FURI_LOG_D("BLE", "Setting data"); + JsBlebeaconInst* blebeacon = get_this_ctx(mjs); if(!check_arg_count(mjs, 1)) return; - JsBlebeaconInst* inst = get_this_ctx(mjs); - if(!inst) { - FURI_LOG_D("BLE", "Beacon instance is null"); - ret_bad_args(mjs, "Beacon instance is null"); - return; - } - - if(inst->data) { - FURI_LOG_D("BLE", "Freeing existing data"); - free(inst->data); - inst->data = NULL; - } - - if(!get_str_arg(mjs, 0, &(inst->data))) return; // get_str_arg now modifies inst->data directly + char* data = NULL; size_t data_len = 0; - uint8_t* beacon_data = hexstr_to_uint8(inst->data, &data_len); - if(!beacon_data) { - FURI_LOG_D("BLE", "Failed to convert data to hex"); - ret_bad_args(mjs, "Failed to convert data to hex"); + 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; } - FURI_LOG_D("BLE", "Successfully set beacon data"); - furi_hal_bt_extra_beacon_set_data(beacon_data, data_len); - free(beacon_data); + if(!blebeacon->saved_prev_data) { + blebeacon->saved_prev_data = true; + blebeacon->prev_data_len = furi_hal_bt_extra_beacon_get_data(blebeacon->prev_data); + } + furi_check(furi_hal_bt_extra_beacon_set_data((uint8_t*)data, data_len)); + mjs_return(mjs, MJS_UNDEFINED); } -static void js_blebeacon_generate_mac(struct mjs* mjs) { - char* company = ""; - if(!get_str_arg(mjs, 0, &company)) return; +static void js_blebeacon_start(struct mjs* mjs) { + JsBlebeaconInst* blebeacon = get_this_ctx(mjs); + if(!check_arg_count(mjs, 0)) return; - char* mac = generate_mac_address(company); - - mjs_val_t js_mac_address = mjs_mk_string(mjs, mac, strlen(mac), 1); - - return mjs_return(mjs, js_mac_address); -} - -static void js_blebeacon_set_mac(struct mjs* mjs) { - FURI_LOG_D("BLE", "Setting Mac"); - if(!check_arg_count(mjs, 1)) { - ret_bad_args(mjs, "Bad args"); - return; + if(!blebeacon->saved_prev_active) { + blebeacon->saved_prev_active = true; + blebeacon->prev_active = furi_hal_bt_extra_beacon_is_active(); } - - JsBlebeaconInst* inst = get_this_ctx(mjs); - char* mac_addr = ""; - if(!get_str_arg(mjs, 0, &mac_addr)) return; - inst->mac_addr = mac_addr; - inst->beacon_config.min_adv_interval_ms = 50; - inst->beacon_config.max_adv_interval_ms = 150; - - inst->beacon_config.adv_channel_map = GapAdvChannelMapAll; - inst->beacon_config.adv_power_level = GapAdvPowerLevel_0dBm; - - inst->beacon_config.address_type = GapAddressTypePublic; - - uint8_t* mac = macstr_to_uint8(mac_addr); - if(mac) { - memcpy(inst->beacon_config.address, mac, 6); - furi_hal_bt_extra_beacon_set_config(&inst->beacon_config); - mjs_return(mjs, MJS_UNDEFINED); - } else { - FURI_LOG_D("BLE", "Bad MacAddress"); - ret_bad_args(mjs, "Bad Mac Address"); - return; - } -} - -static void js_blebeacon_send(struct mjs* mjs) { - furi_hal_bt_extra_beacon_start(); + furi_check(furi_hal_bt_extra_beacon_start()); 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); + furi_hal_bt_extra_beacon_stop(); mjs_return(mjs, MJS_UNDEFINED); } -static void* js_blebeacon_create(struct mjs* mjs, mjs_val_t* object) { - JsBlebeaconInst* inst = 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, inst)); - mjs_set(mjs, blebeacon_obj, "setData", ~0, MJS_MK_FN(js_blebeacon_set_data)); - mjs_set(mjs, blebeacon_obj, "setMac", ~0, MJS_MK_FN(js_blebeacon_set_mac)); - mjs_set(mjs, blebeacon_obj, "send", ~0, MJS_MK_FN(js_blebeacon_send)); - mjs_set(mjs, blebeacon_obj, "stop", ~0, MJS_MK_FN(js_blebeacon_stop)); - mjs_set(mjs, blebeacon_obj, "genMac", ~0, MJS_MK_FN(js_blebeacon_generate_mac)); - *object = blebeacon_obj; - return inst; +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_destroy(void* ptr) { - JsBlebeaconInst* inst = (JsBlebeaconInst*)ptr; - if(inst) { - free(inst->data); - free(inst->mac_addr); - free(inst); +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, "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_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 = { diff --git a/applications/system/js_app/modules/js_keyboard.c b/applications/system/js_app/modules/js_keyboard.c index e6c0b5a54..8958dcaf8 100644 --- a/applications/system/js_app/modules/js_keyboard.c +++ b/applications/system/js_app/modules/js_keyboard.c @@ -168,7 +168,7 @@ static void* js_keyboard_create(struct mjs* mjs, mjs_val_t* object) { } static void js_keyboard_destroy(void* inst) { - JsKeyboardInst* keyboard = (JsKeyboardInst*)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);