Thread provisioning and ping works
This commit is contained in:
@@ -7,6 +7,11 @@ CONFIG_KERNEL_SHELL=y
|
||||
CONFIG_DEVICE_SHELL=y
|
||||
CONFIG_REBOOT=y
|
||||
|
||||
# --- STACK SIZE UPDATES (Fixes the Hard Fault) ---
|
||||
CONFIG_MAIN_STACK_SIZE=4096
|
||||
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
|
||||
CONFIG_BT_RX_STACK_SIZE=2048
|
||||
|
||||
# Storage and Settings (NVS)
|
||||
CONFIG_FLASH=y
|
||||
CONFIG_FLASH_MAP=y
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/uuid.h>
|
||||
#include <zephyr/bluetooth/gatt.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <lasertag_utils.h>
|
||||
#include <ble_mgmt.h>
|
||||
|
||||
@@ -12,7 +13,6 @@ LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);
|
||||
|
||||
/**
|
||||
* Base UUID: 03afe2cf-6c64-4a22-9289-c3ae820cbcxx
|
||||
* Service ends in 00 to match the Web App filter.
|
||||
*/
|
||||
#define LT_UUID_BASE_VAL \
|
||||
BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820cbc00)
|
||||
@@ -21,70 +21,108 @@ LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);
|
||||
#define BT_UUID_LT_NAME_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820cbc01))
|
||||
#define BT_UUID_LT_PANID_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820cbc02))
|
||||
#define BT_UUID_LT_CHAN_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820cbc03))
|
||||
#define BT_UUID_LT_EXTPAN_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820cbc04))
|
||||
#define BT_UUID_LT_NETKEY_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820cbc05))
|
||||
#define BT_UUID_LT_NETNAME_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820cbc06))
|
||||
|
||||
/* --- GATT Callbacks --- */
|
||||
|
||||
static ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
static ssize_t read_lasertag_val(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset)
|
||||
{
|
||||
const char *name = lasertag_get_device_name();
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, name, strlen(name));
|
||||
const char *val_ptr = NULL;
|
||||
size_t val_len = 0;
|
||||
|
||||
if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_NAME_CHAR) == 0) {
|
||||
val_ptr = lasertag_get_device_name();
|
||||
val_len = strlen(val_ptr);
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PANID_CHAR) == 0) {
|
||||
static uint16_t pan_id;
|
||||
pan_id = lasertag_get_thread_pan_id();
|
||||
val_ptr = (char *)&pan_id;
|
||||
val_len = sizeof(pan_id);
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_CHAN_CHAR) == 0) {
|
||||
static uint8_t chan;
|
||||
chan = lasertag_get_thread_channel();
|
||||
val_ptr = (char *)&chan;
|
||||
val_len = sizeof(chan);
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_EXTPAN_CHAR) == 0) {
|
||||
val_ptr = (char *)lasertag_get_thread_ext_pan_id();
|
||||
val_len = 8;
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_NETKEY_CHAR) == 0) {
|
||||
val_ptr = (char *)lasertag_get_thread_network_key();
|
||||
val_len = 16;
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_NETNAME_CHAR) == 0) {
|
||||
val_ptr = lasertag_get_thread_network_name();
|
||||
val_len = strlen(val_ptr);
|
||||
}
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, val_ptr, val_len);
|
||||
}
|
||||
|
||||
static ssize_t write_name(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
static ssize_t write_lasertag_val(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
|
||||
{
|
||||
LOG_INF("BLE: New name received (len %d)", len);
|
||||
int rc = 0;
|
||||
if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_NAME_CHAR) == 0) {
|
||||
rc = lasertag_set_device_name(buf, len);
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PANID_CHAR) == 0) {
|
||||
if (len != 2) return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||
rc = lasertag_set_thread_pan_id(*(uint16_t*)buf);
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_CHAN_CHAR) == 0) {
|
||||
if (len != 1) return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||
rc = lasertag_set_thread_channel(*(uint8_t*)buf);
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_EXTPAN_CHAR) == 0) {
|
||||
if (len != 8) return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||
rc = lasertag_set_thread_ext_pan_id(buf);
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_NETKEY_CHAR) == 0) {
|
||||
if (len != 16) return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
||||
rc = lasertag_set_thread_network_key(buf);
|
||||
} else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_NETNAME_CHAR) == 0) {
|
||||
rc = lasertag_set_thread_network_name(buf, len);
|
||||
}
|
||||
|
||||
if (rc) return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t read_panid(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset)
|
||||
{
|
||||
uint16_t pan_id = lasertag_get_thread_pan_id();
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, &pan_id, sizeof(pan_id));
|
||||
}
|
||||
|
||||
static ssize_t read_chan(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
||||
void *buf, uint16_t len, uint16_t offset)
|
||||
{
|
||||
uint8_t chan = lasertag_get_thread_channel();
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, &chan, sizeof(chan));
|
||||
}
|
||||
|
||||
/* Service and Characteristic Definition */
|
||||
/* Service Definition */
|
||||
BT_GATT_SERVICE_DEFINE(provisioning_svc,
|
||||
BT_GATT_PRIMARY_SERVICE(BT_UUID_LT_SERVICE),
|
||||
|
||||
/* Device Name Characteristic */
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_LT_NAME_CHAR,
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
||||
read_name, write_name, NULL),
|
||||
read_lasertag_val, write_lasertag_val, NULL),
|
||||
|
||||
/* Thread PAN ID Characteristic */
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_LT_PANID_CHAR,
|
||||
BT_GATT_CHRC_READ,
|
||||
BT_GATT_PERM_READ,
|
||||
read_panid, NULL, NULL),
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
||||
read_lasertag_val, write_lasertag_val, NULL),
|
||||
|
||||
/* Thread Channel Characteristic */
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_LT_CHAN_CHAR,
|
||||
BT_GATT_CHRC_READ,
|
||||
BT_GATT_PERM_READ,
|
||||
read_chan, NULL, NULL),
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
||||
read_lasertag_val, write_lasertag_val, NULL),
|
||||
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_LT_EXTPAN_CHAR,
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
||||
read_lasertag_val, write_lasertag_val, NULL),
|
||||
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_LT_NETKEY_CHAR,
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
||||
read_lasertag_val, write_lasertag_val, NULL),
|
||||
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_LT_NETNAME_CHAR,
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
||||
read_lasertag_val, write_lasertag_val, NULL),
|
||||
);
|
||||
|
||||
/**
|
||||
* Advertising Data
|
||||
* * Note: Legacy advertising is limited to 31 bytes.
|
||||
* Flags: 3 bytes
|
||||
* UUID128: 18 bytes
|
||||
* Total: 21 bytes (Fits within limit)
|
||||
*/
|
||||
static const struct bt_data ad[] = {
|
||||
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
|
||||
/* We put the Service UUID here so the Web App can filter for it */
|
||||
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
|
||||
0x00, 0xbc, 0x0c, 0x82, 0xae, 0xc3, 0x89, 0x92,
|
||||
0x22, 0x4a, 0x64, 0x6c, 0xcf, 0xe2, 0xaf, 0x03),
|
||||
@@ -93,11 +131,7 @@ static const struct bt_data ad[] = {
|
||||
int ble_mgmt_init(void)
|
||||
{
|
||||
int err = bt_enable(NULL);
|
||||
if (err) {
|
||||
LOG_ERR("Bluetooth init failed (err %d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (err) return err;
|
||||
LOG_INF("Bluetooth initialized");
|
||||
return 0;
|
||||
}
|
||||
@@ -105,47 +139,31 @@ int ble_mgmt_init(void)
|
||||
int ble_mgmt_adv_start(void)
|
||||
{
|
||||
const char *name = lasertag_get_device_name();
|
||||
|
||||
/* Set the stack name */
|
||||
bt_set_name(name);
|
||||
|
||||
/**
|
||||
* Scan Response contains the full complete name.
|
||||
* This is sent in a separate packet when requested by the scanner.
|
||||
*/
|
||||
struct bt_data dynamic_sd[] = {
|
||||
BT_DATA(BT_DATA_NAME_COMPLETE, name, strlen(name)),
|
||||
};
|
||||
|
||||
struct bt_le_adv_param adv_param = {
|
||||
.id = BT_ID_DEFAULT,
|
||||
.sid = 0,
|
||||
.secondary_max_skip = 0,
|
||||
.options = (BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_SCANNABLE),
|
||||
.interval_min = BT_GAP_ADV_FAST_INT_MIN_2,
|
||||
.interval_max = BT_GAP_ADV_FAST_INT_MAX_2,
|
||||
.peer = NULL,
|
||||
};
|
||||
|
||||
int err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad),
|
||||
dynamic_sd, ARRAY_SIZE(dynamic_sd));
|
||||
if (err) {
|
||||
LOG_ERR("Advertising failed to start (err %d)", err);
|
||||
return err;
|
||||
int err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), dynamic_sd, ARRAY_SIZE(dynamic_sd));
|
||||
if (!err) {
|
||||
LOG_INF("Advertising started as: %s", name);
|
||||
}
|
||||
|
||||
LOG_INF("Advertising started as: %s", name);
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
int ble_mgmt_adv_stop(void)
|
||||
{
|
||||
int err = bt_le_adv_stop();
|
||||
if (err) {
|
||||
LOG_ERR("Advertising failed to stop (err %d)", err);
|
||||
return err;
|
||||
if (!err) {
|
||||
LOG_INF("Advertising stopped");
|
||||
}
|
||||
|
||||
LOG_INF("Advertising stopped");
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
@@ -7,13 +7,12 @@
|
||||
#include <stdlib.h>
|
||||
#include <lasertag_utils.h>
|
||||
#include <ble_mgmt.h>
|
||||
#include <thread_mgmt.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LOG_MODULE_REGISTER(lasertag_utils, CONFIG_LASERTAG_UTILS_LOG_LEVEL);
|
||||
|
||||
/* Application-level persistence storage */
|
||||
static char device_name[32] = "UnknownDevice";
|
||||
|
||||
/* Thread configuration parameters */
|
||||
static uint16_t thread_pan_id = 0xabcd;
|
||||
static char thread_network_name[17] = "OpenThread-nRF";
|
||||
static uint8_t thread_channel = 15;
|
||||
@@ -23,58 +22,44 @@ static uint8_t thread_network_key[16] = {
|
||||
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
|
||||
};
|
||||
|
||||
/* --- Settings Handling (Persistent Storage) --- */
|
||||
/* --- Settings Handler --- */
|
||||
|
||||
static int lasertag_settings_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg)
|
||||
{
|
||||
const char *next;
|
||||
|
||||
if (settings_name_steq(name, "name", &next) && !next) {
|
||||
if (len > sizeof(device_name) - 1) return -EINVAL;
|
||||
ssize_t rc = read_cb(cb_arg, device_name, len);
|
||||
if (rc >= 0) { device_name[rc] = '\0'; return 0; }
|
||||
return (int)rc;
|
||||
}
|
||||
|
||||
if (settings_name_steq(name, "pan_id", &next) && !next) {
|
||||
return read_cb(cb_arg, &thread_pan_id, sizeof(thread_pan_id)) >= 0 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
if (settings_name_steq(name, "net_name", &next) && !next) {
|
||||
if (len > sizeof(thread_network_name) - 1) return -EINVAL;
|
||||
ssize_t rc = read_cb(cb_arg, thread_network_name, len);
|
||||
if (rc >= 0) { thread_network_name[rc] = '\0'; return 0; }
|
||||
return (int)rc;
|
||||
}
|
||||
|
||||
if (settings_name_steq(name, "channel", &next) && !next) {
|
||||
return read_cb(cb_arg, &thread_channel, sizeof(thread_channel)) >= 0 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
if (settings_name_steq(name, "ext_pan_id", &next) && !next) {
|
||||
return read_cb(cb_arg, thread_ext_pan_id, sizeof(thread_ext_pan_id)) >= 0 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
if (settings_name_steq(name, "net_key", &next) && !next) {
|
||||
return read_cb(cb_arg, thread_network_key, sizeof(thread_network_key)) >= 0 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
struct settings_handler lasertag_conf = {
|
||||
.name = "lasertag",
|
||||
.h_set = lasertag_settings_set
|
||||
};
|
||||
struct settings_handler lasertag_conf = { .name = "lasertag", .h_set = lasertag_settings_set };
|
||||
|
||||
void lasertag_utils_init(void)
|
||||
{
|
||||
LOG_INF("==========================================");
|
||||
LOG_INF("Lasertag System - Common Lib v0.0.1");
|
||||
|
||||
int rc = settings_subsys_init();
|
||||
if (rc) LOG_ERR("Settings subsys init failed (err %d)", rc);
|
||||
|
||||
settings_subsys_init();
|
||||
settings_register(&lasertag_conf);
|
||||
settings_load();
|
||||
|
||||
@@ -85,6 +70,7 @@ void lasertag_utils_init(void)
|
||||
LOG_INF("==========================================");
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
const char* lasertag_get_device_name(void) { return device_name; }
|
||||
uint16_t lasertag_get_thread_pan_id(void) { return thread_pan_id; }
|
||||
const char* lasertag_get_thread_network_name(void) { return thread_network_name; }
|
||||
@@ -92,14 +78,40 @@ uint8_t lasertag_get_thread_channel(void) { return thread_channel; }
|
||||
const uint8_t* lasertag_get_thread_ext_pan_id(void) { return thread_ext_pan_id; }
|
||||
const uint8_t* lasertag_get_thread_network_key(void) { return thread_network_key; }
|
||||
|
||||
/* Setters */
|
||||
int lasertag_set_device_name(const char *name, size_t len) {
|
||||
if (len >= sizeof(device_name)) len = sizeof(device_name) - 1;
|
||||
memcpy(device_name, name, len);
|
||||
device_name[len] = '\0';
|
||||
return settings_save_one("lasertag/name", device_name, len);
|
||||
}
|
||||
int lasertag_set_thread_pan_id(uint16_t pan_id) {
|
||||
thread_pan_id = pan_id;
|
||||
return settings_save_one("lasertag/pan_id", &thread_pan_id, sizeof(thread_pan_id));
|
||||
}
|
||||
int lasertag_set_thread_network_name(const char *name, size_t len) {
|
||||
if (len >= sizeof(thread_network_name)) len = sizeof(thread_network_name) - 1;
|
||||
memcpy(thread_network_name, name, len);
|
||||
thread_network_name[len] = '\0';
|
||||
return settings_save_one("lasertag/net_name", thread_network_name, len);
|
||||
}
|
||||
int lasertag_set_thread_channel(uint8_t channel) {
|
||||
thread_channel = channel;
|
||||
return settings_save_one("lasertag/channel", &thread_channel, sizeof(thread_channel));
|
||||
}
|
||||
int lasertag_set_thread_ext_pan_id(const uint8_t *ext_id) {
|
||||
memcpy(thread_ext_pan_id, ext_id, 8);
|
||||
return settings_save_one("lasertag/ext_pan_id", thread_ext_pan_id, 8);
|
||||
}
|
||||
int lasertag_set_thread_network_key(const uint8_t *key) {
|
||||
memcpy(thread_network_key, key, 16);
|
||||
return settings_save_one("lasertag/net_key", thread_network_key, 16);
|
||||
}
|
||||
|
||||
/* --- Shell Commands --- */
|
||||
|
||||
#if CONFIG_LASERTAG_SHELL
|
||||
|
||||
/**
|
||||
* @brief Helper to convert hex string to binary.
|
||||
* Renamed to lasertag_hex2bin to avoid conflict with Zephyr sys/util.h
|
||||
*/
|
||||
static int lasertag_hex2bin(const char *hex, uint8_t *bin, size_t bin_len) {
|
||||
for (size_t i = 0; i < bin_len; i++) {
|
||||
char buf[3] = { hex[i*2], hex[i*2+1], '\0' };
|
||||
@@ -108,80 +120,64 @@ static int lasertag_hex2bin(const char *hex, uint8_t *bin, size_t bin_len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_ble_start(const struct shell *sh, size_t argc, char **argv) {
|
||||
shell_print(sh, "Starting BLE Advertising...");
|
||||
return ble_mgmt_adv_start();
|
||||
}
|
||||
|
||||
static int cmd_ble_stop(const struct shell *sh, size_t argc, char **argv) {
|
||||
shell_print(sh, "Stopping BLE Advertising...");
|
||||
return ble_mgmt_adv_stop();
|
||||
}
|
||||
|
||||
static int cmd_thread_set_chan(const struct shell *sh, size_t argc, char **argv) {
|
||||
thread_channel = (uint8_t)strtoul(argv[1], NULL, 10);
|
||||
settings_save_one("lasertag/channel", &thread_channel, sizeof(thread_channel));
|
||||
shell_print(sh, "Thread Channel saved: %d", thread_channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_thread_set_extpan(const struct shell *sh, size_t argc, char **argv) {
|
||||
if (strlen(argv[1]) != 16) { shell_error(sh, "ExtPANID must be 16 hex chars"); return -EINVAL; }
|
||||
lasertag_hex2bin(argv[1], thread_ext_pan_id, 8);
|
||||
settings_save_one("lasertag/ext_pan_id", thread_ext_pan_id, 8);
|
||||
shell_print(sh, "Thread Extended PAN ID saved.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_thread_set_key(const struct shell *sh, size_t argc, char **argv) {
|
||||
if (strlen(argv[1]) != 32) { shell_error(sh, "Network Key must be 32 hex chars"); return -EINVAL; }
|
||||
lasertag_hex2bin(argv[1], thread_network_key, 16);
|
||||
settings_save_one("lasertag/net_key", thread_network_key, 16);
|
||||
shell_print(sh, "Thread Network Key saved.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_name_set(const struct shell *sh, size_t argc, char **argv) {
|
||||
strncpy(device_name, argv[1], sizeof(device_name) - 1);
|
||||
device_name[sizeof(device_name) - 1] = '\0';
|
||||
settings_save_one("lasertag/name", device_name, strlen(device_name));
|
||||
shell_print(sh, "Device name saved: %s", device_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_reboot(const struct shell *sh, size_t argc, char **argv) {
|
||||
ARG_UNUSED(argc);
|
||||
ARG_UNUSED(argv);
|
||||
shell_print(sh, "Rebooting...");
|
||||
sys_reboot(SYS_REBOOT_COLD);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Subcommands for 'lasertag thread' */
|
||||
static int cmd_name_set(const struct shell *sh, size_t argc, char **argv) {
|
||||
lasertag_set_device_name(argv[1], strlen(argv[1]));
|
||||
shell_print(sh, "Name gespeichert.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_thread_ping(const struct shell *sh, size_t argc, char **argv) {
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "Ping von %s", device_name);
|
||||
shell_print(sh, "Sende Multicast-Ping an ff03::1...");
|
||||
int rc = thread_mgmt_send_udp("ff03::1", (uint8_t*)msg, strlen(msg));
|
||||
if (rc) shell_error(sh, "Ping fehlgeschlagen (%d)", rc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Subcommands definitions omitted for brevity, but they should include 'ping' */
|
||||
static int cmd_thread_set_panid(const struct shell *sh, size_t argc, char **argv) {
|
||||
uint16_t pan = (uint16_t)strtoul(argv[1], NULL, 0);
|
||||
lasertag_set_thread_pan_id(pan);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_thread_set_chan(const struct shell *sh, size_t argc, char **argv) {
|
||||
uint8_t chan = (uint8_t)strtoul(argv[1], NULL, 10);
|
||||
lasertag_set_thread_channel(chan);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SHELL_STATIC_SUBCMD_SET_CREATE(sub_thread,
|
||||
SHELL_CMD_ARG(panid, NULL, "Set PAN ID <id>", NULL, 2, 0),
|
||||
SHELL_CMD_ARG(name, NULL, "Set Net Name <name>", NULL, 2, 0),
|
||||
SHELL_CMD_ARG(chan, NULL, "Set Channel <11-26>", cmd_thread_set_chan, 2, 0),
|
||||
SHELL_CMD_ARG(extid, NULL, "Set ExtPANID <16 hex chars>", cmd_thread_set_extpan, 2, 0),
|
||||
SHELL_CMD_ARG(key, NULL, "Set NetKey <32 hex chars>", cmd_thread_set_key, 2, 0),
|
||||
SHELL_CMD_ARG(panid, NULL, "PAN ID setzen", cmd_thread_set_panid, 2, 0),
|
||||
SHELL_CMD_ARG(chan, NULL, "Kanal setzen", cmd_thread_set_chan, 2, 0),
|
||||
SHELL_CMD(ping, NULL, "Multicast Ping senden", cmd_thread_ping),
|
||||
SHELL_SUBCMD_SET_END
|
||||
);
|
||||
|
||||
static int cmd_ble_start(const struct shell *sh, size_t argc, char **argv) {
|
||||
return ble_mgmt_adv_start();
|
||||
}
|
||||
|
||||
SHELL_STATIC_SUBCMD_SET_CREATE(sub_ble,
|
||||
SHELL_CMD(start, NULL, "Start BLE advertising", cmd_ble_start),
|
||||
SHELL_CMD(stop, NULL, "Stop BLE advertising", cmd_ble_stop),
|
||||
SHELL_CMD(start, NULL, "Start BLE", cmd_ble_start),
|
||||
SHELL_SUBCMD_SET_END
|
||||
);
|
||||
|
||||
/* Main command 'lasertag' with thread + ble helpers */
|
||||
SHELL_STATIC_SUBCMD_SET_CREATE(sub_lasertag,
|
||||
SHELL_CMD_ARG(name, NULL, "Set device name <name>", cmd_name_set, 2, 0),
|
||||
SHELL_CMD(thread, &sub_thread, "Thread network configuration", NULL),
|
||||
SHELL_CMD(ble, &sub_ble, "Bluetooth management", NULL),
|
||||
SHELL_CMD(reboot, NULL, "Reboot the device", cmd_reboot),
|
||||
SHELL_CMD_ARG(name, NULL, "Name setzen", cmd_name_set, 2, 0),
|
||||
SHELL_CMD(thread, &sub_thread, "Thread Konfiguration", NULL),
|
||||
SHELL_CMD(ble, &sub_ble, "BLE Management", NULL),
|
||||
SHELL_CMD(reboot, NULL, "Reboot", cmd_reboot),
|
||||
SHELL_SUBCMD_SET_END
|
||||
);
|
||||
|
||||
SHELL_CMD_REGISTER(lasertag, &sub_lasertag, "Lasertag control commands", NULL);
|
||||
SHELL_CMD_REGISTER(lasertag, &sub_lasertag, "Lasertag Befehle", NULL);
|
||||
|
||||
#endif /* CONFIG_LASERTAG_SHELL */
|
||||
#endif
|
||||
@@ -4,74 +4,148 @@
|
||||
#include <openthread/thread.h>
|
||||
#include <openthread/dataset.h>
|
||||
#include <openthread/instance.h>
|
||||
#include <openthread/udp.h>
|
||||
#include <openthread/ip6.h>
|
||||
#include <lasertag_utils.h>
|
||||
#include <thread_mgmt.h>
|
||||
#include <string.h>
|
||||
|
||||
LOG_MODULE_REGISTER(thread_mgmt, CONFIG_THREAD_MGMT_LOG_LEVEL);
|
||||
|
||||
#define UDP_PORT 1234
|
||||
|
||||
static otUdpSocket s_udp_socket;
|
||||
|
||||
/**
|
||||
* @brief Callback für empfangene UDP-Nachrichten.
|
||||
*/
|
||||
static void udp_receive_cb(void *context, otMessage *message, const otMessageInfo *message_info)
|
||||
{
|
||||
uint8_t buf[64];
|
||||
uint16_t length = otMessageGetLength(message) - otMessageGetOffset(message);
|
||||
|
||||
if (length > sizeof(buf) - 1) {
|
||||
length = sizeof(buf) - 1;
|
||||
}
|
||||
|
||||
int read = otMessageRead(message, otMessageGetOffset(message), buf, length);
|
||||
buf[read] = '\0';
|
||||
|
||||
char addr_str[OT_IP6_ADDRESS_STRING_SIZE];
|
||||
otIp6AddressToString(&message_info->mPeerAddr, addr_str, sizeof(addr_str));
|
||||
|
||||
/* Deutliche Log-Ausgabe für das Testen */
|
||||
LOG_INF("------------------------------------------");
|
||||
LOG_INF("UDP DATA RECEIVED!");
|
||||
LOG_INF("From: [%s]", addr_str);
|
||||
LOG_INF("Payload: %s", buf);
|
||||
LOG_INF("------------------------------------------");
|
||||
}
|
||||
|
||||
int thread_mgmt_send_udp(const char *addr_str, uint8_t *payload, uint16_t len)
|
||||
{
|
||||
struct otInstance *instance = openthread_get_default_instance();
|
||||
otError error = OT_ERROR_NONE;
|
||||
otMessage *message;
|
||||
otMessageInfo message_info;
|
||||
|
||||
if (!instance) return -ENODEV;
|
||||
|
||||
memset(&message_info, 0, sizeof(message_info));
|
||||
otIp6AddressFromString(addr_str, &message_info.mPeerAddr);
|
||||
message_info.mPeerPort = UDP_PORT;
|
||||
|
||||
message = otUdpNewMessage(instance, NULL);
|
||||
if (message == NULL) return -ENOMEM;
|
||||
|
||||
error = otMessageAppend(message, payload, len);
|
||||
if (error != OT_ERROR_NONE) {
|
||||
otMessageFree(message);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
error = otUdpSend(instance, &s_udp_socket, message, &message_info);
|
||||
if (error != OT_ERROR_NONE) {
|
||||
otMessageFree(message);
|
||||
LOG_ERR("UDP Senden fehlgeschlagen (err %d)", error);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
LOG_INF("UDP gesendet an %s: %d Bytes", addr_str, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int thread_mgmt_init(void)
|
||||
{
|
||||
struct otInstance *instance = openthread_get_default_instance();
|
||||
otOperationalDataset dataset;
|
||||
int rc;
|
||||
otError error;
|
||||
|
||||
if (!instance) {
|
||||
LOG_ERR("Failed to get OpenThread instance");
|
||||
LOG_ERR("OpenThread Instanz nicht gefunden");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
LOG_INF("Configuring Thread stack with stored settings...");
|
||||
LOG_INF("Thread stack wird konfiguriert...");
|
||||
|
||||
/* Initialize dataset structure */
|
||||
/* Dataset-Struktur initialisieren */
|
||||
memset(&dataset, 0, sizeof(otOperationalDataset));
|
||||
|
||||
/* 1. Set Network Name */
|
||||
/* 0. Active Timestamp - Wichtig für die Netzwerksynchronisation */
|
||||
dataset.mActiveTimestamp.mSeconds = 1;
|
||||
dataset.mComponents.mIsActiveTimestampPresent = true;
|
||||
|
||||
/* 1. Netzwerkname */
|
||||
const char *net_name = lasertag_get_thread_network_name();
|
||||
size_t name_len = strlen(net_name);
|
||||
memcpy(dataset.mNetworkName.m8, net_name, name_len);
|
||||
memcpy(dataset.mNetworkName.m8, net_name, strlen(net_name));
|
||||
dataset.mComponents.mIsNetworkNamePresent = true;
|
||||
|
||||
/* 2. Set PAN ID */
|
||||
/* 2. PAN ID */
|
||||
dataset.mPanId = lasertag_get_thread_pan_id();
|
||||
dataset.mComponents.mIsPanIdPresent = true;
|
||||
|
||||
/* 3. Set Channel */
|
||||
/* 3. Kanal */
|
||||
dataset.mChannel = lasertag_get_thread_channel();
|
||||
dataset.mComponents.mIsChannelPresent = true;
|
||||
|
||||
/* 4. Set Extended PAN ID */
|
||||
/* 4. Extended PAN ID */
|
||||
memcpy(dataset.mExtendedPanId.m8, lasertag_get_thread_ext_pan_id(), 8);
|
||||
dataset.mComponents.mIsExtendedPanIdPresent = true;
|
||||
|
||||
/* 5. Set Network Key */
|
||||
/* 5. Netzwerk Key */
|
||||
memcpy(dataset.mNetworkKey.m8, lasertag_get_thread_network_key(), 16);
|
||||
dataset.mComponents.mIsNetworkKeyPresent = true;
|
||||
|
||||
/* 6. Set Mesh Local Prefix (Default for OpenThread) */
|
||||
/* 6. Mesh Local Prefix */
|
||||
uint8_t ml_prefix[] = {0xfd, 0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0x00};
|
||||
memcpy(dataset.mMeshLocalPrefix.m8, ml_prefix, 8);
|
||||
dataset.mComponents.mIsMeshLocalPrefixPresent = true;
|
||||
|
||||
/* Apply the dataset as the Active Dataset */
|
||||
rc = otDatasetSetActive(instance, &dataset);
|
||||
if (rc != OT_ERROR_NONE) {
|
||||
LOG_ERR("Failed to set Active Dataset (err %d)", rc);
|
||||
/* Dataset aktivieren */
|
||||
otDatasetSetActive(instance, &dataset);
|
||||
|
||||
/* Interface und Stack starten */
|
||||
otIp6SetEnabled(instance, true);
|
||||
otThreadSetEnabled(instance, true);
|
||||
|
||||
/* UDP Socket initialisieren */
|
||||
otSockAddr listen_addr;
|
||||
memset(&listen_addr, 0, sizeof(listen_addr));
|
||||
listen_addr.mPort = UDP_PORT;
|
||||
|
||||
error = otUdpOpen(instance, &s_udp_socket, udp_receive_cb, NULL);
|
||||
if (error != OT_ERROR_NONE) {
|
||||
LOG_ERR("UDP Socket konnte nicht geoeffnet werden (err %d)", error);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Start the interface */
|
||||
rc = otIp6SetEnabled(instance, true);
|
||||
if (rc != OT_ERROR_NONE) {
|
||||
LOG_ERR("Failed to enable IPv6 (err %d)", rc);
|
||||
/* otUdpBind für SDK v3.2.1 mit 4 Argumenten */
|
||||
error = otUdpBind(instance, &s_udp_socket, &listen_addr, OT_NETIF_UNSPECIFIED);
|
||||
if (error != OT_ERROR_NONE) {
|
||||
LOG_ERR("UDP Bind fehlgeschlagen (err %d)", error);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
rc = otThreadSetEnabled(instance, true);
|
||||
if (rc != OT_ERROR_NONE) {
|
||||
LOG_ERR("Failed to enable Thread (err %d)", rc);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
LOG_INF("Thread stack initialized and interface enabled.");
|
||||
LOG_INF("Thread MGMT: Initialisiert, UDP Port %d offen.", UDP_PORT);
|
||||
return 0;
|
||||
}
|
||||
12
lasertag.code-workspace
Normal file
12
lasertag.code-workspace
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"nrf-connect.applications": [
|
||||
"${workspaceFolder}/firmware/apps/leader"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,346 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lasertag Provisioning</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; padding: 2rem; background: #f0f2f5; }
|
||||
.card { background: white; padding: 2rem; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 100%; max-width: 400px; }
|
||||
button { background: #007bff; color: white; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-size: 1rem; width: 100%; }
|
||||
button:disabled { background: #ccc; }
|
||||
.status { margin-top: 1rem; color: #666; font-size: 0.9rem; }
|
||||
.device-info { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid #eee; display: none; }
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; flex-direction: column; align-items: center; padding: 2rem; background: #f0f2f5; color: #333; }
|
||||
.card { background: white; padding: 2rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); width: 100%; max-width: 550px; }
|
||||
h1 { font-size: 1.5rem; margin-bottom: 1rem; color: #1a73e8; }
|
||||
.status-box { padding: 0.75rem; border-radius: 6px; margin-bottom: 1.5rem; font-size: 0.9rem; background: #e8f0fe; color: #1967d2; border: 1px solid #d2e3fc; min-height: 20px; }
|
||||
.button-group { display: flex; gap: 10px; width: 100%; flex-wrap: wrap; }
|
||||
button { background: #1a73e8; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 1rem; width: 100%; font-weight: 600; transition: background 0.2s; margin-top: 10px; }
|
||||
button:hover { background: #1557b0; }
|
||||
button:disabled { background: #ccc; cursor: not-allowed; }
|
||||
.btn-danger { background: #d93025; }
|
||||
.btn-danger:hover { background: #a50e0e; }
|
||||
.btn-secondary { background: #5f6368; }
|
||||
.btn-secondary:hover { background: #474a4d; }
|
||||
.btn-success { background: #188038; }
|
||||
.btn-success:hover { background: #13652c; }
|
||||
|
||||
.device-info { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 2px solid #f0f2f5; display: none; }
|
||||
.config-section { margin-top: 1.5rem; }
|
||||
.config-group { margin-bottom: 1rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; }
|
||||
.config-group:last-child { border-bottom: none; }
|
||||
label { display: block; font-size: 0.85rem; color: #666; margin-bottom: 4px; font-weight: bold; }
|
||||
.input-row { display: flex; gap: 8px; }
|
||||
input { flex-grow: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 0.9rem; font-family: monospace; }
|
||||
.btn-small { width: auto; font-size: 0.85rem; padding: 8px 16px; margin-top: 0; }
|
||||
.hint { font-size: 0.8rem; color: #888; margin-top: 1rem; font-style: italic; }
|
||||
.action-bar { display: flex; gap: 10px; margin-bottom: 15px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Lasertag Setup</h1>
|
||||
<p>Connect to your device via Bluetooth to configure Thread settings.</p>
|
||||
<button id="connectBtn">Search for Device</button>
|
||||
<div class="status" id="status">Ready to connect...</div>
|
||||
<h1>Lasertag Konfiguration</h1>
|
||||
<div class="status-box" id="status">Bereit zum Scannen...</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="connectBtn">Gerät suchen</button>
|
||||
<button id="disconnectBtn" class="btn-danger" style="display: none;">Trennen</button>
|
||||
</div>
|
||||
|
||||
<div class="config-section" id="globalActions" style="margin-top: 20px;">
|
||||
<label>Globale Aktionen</label>
|
||||
<div class="button-group">
|
||||
<button id="genParamsBtn" class="btn-secondary">Zufallsparameter generieren</button>
|
||||
<button id="saveToCookieBtn" class="btn-secondary">Netzwerk im Browser merken</button>
|
||||
<button id="loadFromCookieBtn" class="btn-success" style="display: none;">Gemerktes Netzwerk auf Node schreiben</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="device-info" id="deviceInfo">
|
||||
<strong>Connected to:</strong> <span id="connectedName"></span><br>
|
||||
<p>Next step: Implement Characteristic Write/Read.</p>
|
||||
<div class="config-section">
|
||||
<!-- Device Name -->
|
||||
<div class="config-group">
|
||||
<label>Gerätename (ID)</label>
|
||||
<div class="input-row">
|
||||
<input type="text" id="nameInput" maxlength="31" placeholder="z.B. Alpha-Wolf">
|
||||
<button class="btn-small" id="saveNameBtn">Setzen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thread Network Name -->
|
||||
<div class="config-group">
|
||||
<label>Thread Netzwerk Name</label>
|
||||
<div class="input-row">
|
||||
<input type="text" id="netNameInput" maxlength="16" placeholder="z.B. MyThreadNet">
|
||||
<button class="btn-small" id="saveNetNameBtn">Setzen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thread PAN ID -->
|
||||
<div class="config-group">
|
||||
<label>Thread PAN ID (Hex, 4 Stellen)</label>
|
||||
<div class="input-row">
|
||||
<input type="text" id="panInput" maxlength="4" placeholder="abcd">
|
||||
<button class="btn-small" id="savePanBtn">Setzen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thread Channel -->
|
||||
<div class="config-group">
|
||||
<label>Thread Kanal (11-26)</label>
|
||||
<div class="input-row">
|
||||
<input type="number" id="chanInput" min="11" max="26">
|
||||
<button class="btn-small" id="saveChanBtn">Setzen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Extended PAN ID -->
|
||||
<div class="config-group">
|
||||
<label>Extended PAN ID (Hex, 16 Stellen)</label>
|
||||
<div class="input-row">
|
||||
<input type="text" id="extPanInput" maxlength="16" placeholder="deadbeefcafebabe">
|
||||
<button class="btn-small" id="saveExtPanBtn">Setzen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Network Key -->
|
||||
<div class="config-group">
|
||||
<label>Network Key (Hex, 32 Stellen)</label>
|
||||
<div class="input-row">
|
||||
<input type="text" id="keyInput" maxlength="32" placeholder="00112233445566778899aabbccddeeff">
|
||||
<button class="btn-small" id="saveKeyBtn">Setzen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="hint">Hinweis: Alle Änderungen werden im NVS gespeichert. Ein Reboot ist erforderlich, um Thread neu zu starten.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const connectBtn = document.getElementById('connectBtn');
|
||||
const disconnectBtn = document.getElementById('disconnectBtn');
|
||||
const status = document.getElementById('status');
|
||||
const deviceInfo = document.getElementById('deviceInfo');
|
||||
const connectedName = document.getElementById('connectedName');
|
||||
const saveToCookieBtn = document.getElementById('saveToCookieBtn');
|
||||
const loadFromCookieBtn = document.getElementById('loadFromCookieBtn');
|
||||
const genParamsBtn = document.getElementById('genParamsBtn');
|
||||
|
||||
const nameInput = document.getElementById('nameInput');
|
||||
const netNameInput = document.getElementById('netNameInput');
|
||||
const panInput = document.getElementById('panInput');
|
||||
const chanInput = document.getElementById('chanInput');
|
||||
const extPanInput = document.getElementById('extPanInput');
|
||||
const keyInput = document.getElementById('keyInput');
|
||||
|
||||
const PROVISIONING_SERVICE_UUID = '03afe2cf-6c64-4a22-9289-c3ae820cbc00';
|
||||
let bluetoothDevice = null;
|
||||
let provService = null;
|
||||
|
||||
// UUIDs passend zur Firmware
|
||||
const SERVICE_UUID = '03afe2cf-6c64-4a22-9289-c3ae820cbc00';
|
||||
const NAME_CHAR = '03afe2cf-6c64-4a22-9289-c3ae820cbc01';
|
||||
const PANID_CHAR = '03afe2cf-6c64-4a22-9289-c3ae820cbc02';
|
||||
const CHAN_CHAR = '03afe2cf-6c64-4a22-9289-c3ae820cbc03';
|
||||
const EXTPAN_CHAR = '03afe2cf-6c64-4a22-9289-c3ae820cbc04';
|
||||
const NETKEY_CHAR = '03afe2cf-6c64-4a22-9289-c3ae820cbc05';
|
||||
const NETNAME_CHAR = '03afe2cf-6c64-4a22-9289-c3ae820cbc06';
|
||||
|
||||
// Check if we have saved network settings
|
||||
function updateLoadButtonVisibility() {
|
||||
const saved = localStorage.getItem('lasertag_net_config');
|
||||
loadFromCookieBtn.style.display = (saved && bluetoothDevice && bluetoothDevice.gatt.connected) ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
function hexToBytes(hex) {
|
||||
let bytes = [];
|
||||
for (let c = 0; c < hex.length; c += 2)
|
||||
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||
return new Uint8Array(bytes);
|
||||
}
|
||||
|
||||
function bytesToHex(uint8arr) {
|
||||
return Array.from(uint8arr).map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase();
|
||||
}
|
||||
|
||||
function generateRandomHex(len) {
|
||||
const arr = new Uint8Array(len / 2);
|
||||
window.crypto.getRandomValues(arr);
|
||||
return bytesToHex(arr);
|
||||
}
|
||||
|
||||
async function readAll() {
|
||||
try {
|
||||
status.textContent = 'Lese Konfiguration...';
|
||||
|
||||
// Name
|
||||
const nameData = await (await provService.getCharacteristic(NAME_CHAR)).readValue();
|
||||
nameInput.value = new TextDecoder().decode(nameData);
|
||||
|
||||
// Thread Net Name
|
||||
const netNameData = await (await provService.getCharacteristic(NETNAME_CHAR)).readValue();
|
||||
netNameInput.value = new TextDecoder().decode(netNameData);
|
||||
|
||||
// PAN ID
|
||||
const panData = await (await provService.getCharacteristic(PANID_CHAR)).readValue();
|
||||
panInput.value = panData.getUint16(0, true).toString(16).padStart(4, '0').toUpperCase();
|
||||
|
||||
// Kanal
|
||||
const chanData = await (await provService.getCharacteristic(CHAN_CHAR)).readValue();
|
||||
chanInput.value = chanData.getUint8(0);
|
||||
|
||||
// Ext PAN ID
|
||||
const extData = await (await provService.getCharacteristic(EXTPAN_CHAR)).readValue();
|
||||
extPanInput.value = bytesToHex(new Uint8Array(extData.buffer));
|
||||
|
||||
// Net Key
|
||||
const keyData = await (await provService.getCharacteristic(NETKEY_CHAR)).readValue();
|
||||
keyInput.value = bytesToHex(new Uint8Array(keyData.buffer));
|
||||
|
||||
status.textContent = 'Verbunden mit ' + bluetoothDevice.name;
|
||||
updateLoadButtonVisibility();
|
||||
} catch (e) {
|
||||
status.textContent = 'Fehler beim Lesen: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
connectBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
status.textContent = 'Requesting Bluetooth Device...';
|
||||
const device = await navigator.bluetooth.requestDevice({
|
||||
filters: [{ services: [PROVISIONING_SERVICE_UUID] }],
|
||||
optionalServices: [PROVISIONING_SERVICE_UUID]
|
||||
bluetoothDevice = await navigator.bluetooth.requestDevice({
|
||||
filters: [{ services: [SERVICE_UUID] }]
|
||||
});
|
||||
|
||||
status.textContent = 'Connecting to GATT Server...';
|
||||
const server = await device.gatt.connect();
|
||||
status.textContent = 'Verbinde...';
|
||||
const server = await bluetoothDevice.gatt.connect();
|
||||
provService = await server.getPrimaryService(SERVICE_UUID);
|
||||
|
||||
status.textContent = 'Connected!';
|
||||
connectedName.textContent = device.name || 'Unnamed Device';
|
||||
deviceInfo.style.display = 'block';
|
||||
connectBtn.disabled = true;
|
||||
disconnectBtn.style.display = 'inline-block';
|
||||
connectBtn.style.display = 'none';
|
||||
|
||||
device.addEventListener('gattserverdisconnected', () => {
|
||||
status.textContent = 'Disconnected.';
|
||||
deviceInfo.style.display = 'none';
|
||||
connectBtn.disabled = false;
|
||||
});
|
||||
await readAll();
|
||||
|
||||
bluetoothDevice.addEventListener('gattserverdisconnected', onDisconnected);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
status.textContent = 'Error: ' + error.message;
|
||||
status.textContent = 'Suche abgebrochen oder Fehler: ' + error.message;
|
||||
}
|
||||
});
|
||||
|
||||
disconnectBtn.addEventListener('click', () => {
|
||||
if (bluetoothDevice && bluetoothDevice.gatt.connected) {
|
||||
bluetoothDevice.gatt.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
function onDisconnected() {
|
||||
status.textContent = 'Verbindung getrennt.';
|
||||
deviceInfo.style.display = 'none';
|
||||
disconnectBtn.style.display = 'none';
|
||||
connectBtn.style.display = 'inline-block';
|
||||
provService = null;
|
||||
updateLoadButtonVisibility();
|
||||
}
|
||||
|
||||
async function writeValue(uuid, data) {
|
||||
if (!provService) return;
|
||||
try {
|
||||
status.textContent = 'Speichere...';
|
||||
const char = await provService.getCharacteristic(uuid);
|
||||
await char.writeValue(data);
|
||||
status.textContent = 'Erfolgreich gespeichert!';
|
||||
setTimeout(() => { if(provService) status.textContent = 'Verbunden.'; }, 2000);
|
||||
} catch (e) {
|
||||
status.textContent = 'Speichern fehlgeschlagen: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
// Global Action: Generate Random Params
|
||||
genParamsBtn.addEventListener('click', () => {
|
||||
panInput.value = generateRandomHex(4);
|
||||
extPanInput.value = generateRandomHex(16);
|
||||
keyInput.value = generateRandomHex(32);
|
||||
chanInput.value = Math.floor(Math.random() * (26 - 11 + 1)) + 11;
|
||||
status.textContent = 'Zufallsparameter generiert.';
|
||||
});
|
||||
|
||||
// Global Action: Save to Local Storage
|
||||
saveToCookieBtn.addEventListener('click', () => {
|
||||
const config = {
|
||||
netName: netNameInput.value,
|
||||
panId: panInput.value,
|
||||
channel: chanInput.value,
|
||||
extPanId: extPanInput.value,
|
||||
netKey: keyInput.value
|
||||
};
|
||||
localStorage.setItem('lasertag_net_config', JSON.stringify(config));
|
||||
status.textContent = 'Netzwerk-Einstellungen lokal gemerkt.';
|
||||
updateLoadButtonVisibility();
|
||||
});
|
||||
|
||||
// Global Action: Load and Write to Node
|
||||
loadFromCookieBtn.addEventListener('click', async () => {
|
||||
const saved = localStorage.getItem('lasertag_net_config');
|
||||
if (!saved || !provService) return;
|
||||
|
||||
try {
|
||||
const config = JSON.parse(saved);
|
||||
status.textContent = 'Schreibe gemerktes Netzwerk...';
|
||||
|
||||
// Write each setting
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Net Name
|
||||
await (await provService.getCharacteristic(NETNAME_CHAR)).writeValue(encoder.encode(config.netName));
|
||||
|
||||
// PAN ID
|
||||
const panView = new DataView(new ArrayBuffer(2));
|
||||
panView.setUint16(0, parseInt(config.panId, 16), true);
|
||||
await (await provService.getCharacteristic(PANID_CHAR)).writeValue(panView.buffer);
|
||||
|
||||
// Channel
|
||||
const chanView = new DataView(new ArrayBuffer(1));
|
||||
chanView.setUint8(0, parseInt(config.channel));
|
||||
await (await provService.getCharacteristic(CHAN_CHAR)).writeValue(chanView.buffer);
|
||||
|
||||
// Ext PAN
|
||||
await (await provService.getCharacteristic(EXTPAN_CHAR)).writeValue(hexToBytes(config.extPanId));
|
||||
|
||||
// Net Key
|
||||
await (await provService.getCharacteristic(NETKEY_CHAR)).writeValue(hexToBytes(config.netKey));
|
||||
|
||||
status.textContent = 'Netzwerk vollständig übertragen!';
|
||||
|
||||
// Update UI fields to show what was written
|
||||
netNameInput.value = config.netName;
|
||||
panInput.value = config.panId;
|
||||
chanInput.value = config.channel;
|
||||
extPanInput.value = config.extPanId;
|
||||
keyInput.value = config.netKey;
|
||||
|
||||
} catch (e) {
|
||||
status.textContent = 'Fehler beim Batch-Schreiben: ' + e.message;
|
||||
}
|
||||
});
|
||||
|
||||
// Manual Saves
|
||||
document.getElementById('saveNameBtn').addEventListener('click', () =>
|
||||
writeValue(NAME_CHAR, new TextEncoder().encode(nameInput.value)));
|
||||
|
||||
document.getElementById('saveNetNameBtn').addEventListener('click', () =>
|
||||
writeValue(NETNAME_CHAR, new TextEncoder().encode(netNameInput.value)));
|
||||
|
||||
document.getElementById('savePanBtn').addEventListener('click', () => {
|
||||
const view = new DataView(new ArrayBuffer(2));
|
||||
view.setUint16(0, parseInt(panInput.value, 16), true);
|
||||
writeValue(PANID_CHAR, view.buffer);
|
||||
});
|
||||
|
||||
document.getElementById('saveChanBtn').addEventListener('click', () => {
|
||||
const view = new DataView(new ArrayBuffer(1));
|
||||
view.setUint8(0, parseInt(chanInput.value));
|
||||
writeValue(CHAN_CHAR, view.buffer);
|
||||
});
|
||||
|
||||
document.getElementById('saveExtPanBtn').addEventListener('click', () =>
|
||||
writeValue(EXTPAN_CHAR, hexToBytes(extPanInput.value)));
|
||||
|
||||
document.getElementById('saveKeyBtn').addEventListener('click', () =>
|
||||
writeValue(NETKEY_CHAR, hexToBytes(keyInput.value)));
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user